ついカッとなって、飲み会座席のくじ引きアプリを作った話

この記事は、 JSL (日本システム技研) Advent Calendar 2019 - Qiita 12/2の記事です。

昨年同様、今年もJSLメンバーによるアドベントカレンダーをお届けします。

 
今回の記事は、ついカッとなって作成したアプリについて、ゆるく書いていきます。

目次

 

作ったもの

歓迎会の前日の雑談で「自分はなぜか最近の飲み会で役付きの方の隣が多いですー」という話題になりました。

そこで、ついカッとなって、翌朝までに、飲み会座席のくじ引きアプリを作ることにしました。

 

作成するまでの流れ

まず、欲しい機能としては

  • 自分の社員番号を入力したら、席番号を表示する
  • ランダムで席番号を割り当てる
  • 一回表示した社員番号の場合は、同じ席番号を表示する
  • 存在しない社員番号の場合は、エラーを表示する

があれば良さそうでした。

 
そこで、Python & Python製WebフレームワークのDjangoを使うことにより、各機能を実現できそうでした。

 

一方、最低限動けばいいだろうと考え、

あたりは、がんばらない方針としました。

 
機能と実現方法さえ決まれば後は実装するだけなので、夕食後に実装し始めて22時半くらいには完成しました。

ただ、翌朝までという時間制限を加えることで、ハッカソン的になったことから

と、作るだけで満足してしまいました。。。

 

アプリの画像

トップ

f:id:thinkAmi:20191202184442p:plain:w200

 

座席決定

f:id:thinkAmi:20191202184457p:plain:w200

 

存在しない場合

f:id:thinkAmi:20191202184514p:plain:w200

 

社内への公開から運用へ

満足したとはいえ、お蔵入りするのもなーと迷う気持ちがありました。

社内Slackでは分報を運用しているため、気軽に

最近の飲み会、くじ引きから自由席になった結果、なぜか役付きの人の近くにしか行けず。。。 ランダム性がほしいと思って、昨夜ついカッとなってくじ引きアプリを作ったけど、アプリ作ったら満足してしまった。。。

と書いてみました。

するとありがたいことに、@_sin_tanaka より

一旦ランダムでやってみる感じでいいと思います!せっかく作ったものなので!

と後押しがありました。

そこで、社内の雑談チャンネルにて

f:id:thinkAmi:20191202183005p:plain:w450

と書いたところ、ありがたいことに @chinoppy0727 の手により運用してもらえることになりました。

 

とはいえ、上記の通り、かなり機能を絞ったまま公開してしまったため

  • アプリ仕様や使い方のドキュメントが無い
  • 社員場号だけが使われるので、社員番号が不明だと、誰か分からない
  • Django adminによる管理画面作ってなかった
    • fixtureは用意していた
  • 「実際の物理席に、座席番号を書いて置く必要がある」という制約

など、かなり運用はつらい感じだったと思います。。。

 

運用に乗ってみて

ランダムで座席を決めているはずなのに、役付の人のまわりには変わり映えしないメンバーが集まるという、現実不具合がありました*1

そのため、歓迎会が始まるまではつらい気持ちになりました*2

 
とはいえ、自分が作ったものが使われると嬉しかったこともあり、カッとなって作るというのもたまには良いなーと感じました。

あと、今年度は開発合宿もあるので、このようなものがまた作れるかもしれないのも楽しみです。

 

ソースコード

GitHubに上げました。
https://github.com/thinkAmi/nomikai_kuji

*1:自分はおすそわけできた

*2:歓迎会で飲み始めたら忘れました

AWS CDKで、cdk deployしたらエラー「NoSuchBucket: The specified bucket does not exist」

先日、S3に不要なバケットがたまっていたため、バケットを全削除をするスクリプトを作成・実行しました。

その後、AWS CDK + Pythonで、 cdk deploy したところ、

 ❌  <your_backet_name> failed: NoSuchBucket: The specified bucket does not exist
The specified bucket does not exist

とエラーになり、AWS CDKを使ったデプロイができなくなりました。

 
この時の対応でしばらく悩んだため、メモを残します。

 

目次

 

環境

 

調査と対応

前述の通りバケットを全削除してしまったため、必要なバケット <your_backet_name> が無いせいかと考えました。

そこで、手動でエラーが出ている名称のバケットを作成したものの、エラーは変わりませんでした。

これにより、他にも消してはマズいバケットを消してしまったのかなと考えました。

 

CloudFormationのスタックを見たところ、 CDKToolkit という気になるスタックがありました。

スタックの説明には

The CDK Toolkit Stack. It was created by `cdk bootstrap` and manages resources necessary for managing your Cloud Applications with AWS CDK.

とありました。

Getting Startedページに書かれていないため忘れていましたが、AWS CDKでデプロイする前に、 cdk bootstrap をやっていたことを思い出しました。
Getting Started With the AWS CDK - AWS Cloud Development Kit (AWS CDK)

 

そこで、改めて cdk bootstrap したところ、エラーが変わりました。

$ cdk bootstrap
 ⏳  Bootstrapping environment aws://<account>/<region>...
CDKToolkit: creating CloudFormation changeset...
 0/2 | 8:22:48 PM | UPDATE_IN_PROGRESS   | AWS::S3::Bucket | StagingBucket 
0/2 Currently in progress: StagingBucket
 1/2 | 8:24:34 PM | UPDATE_FAILED        | AWS::S3::Bucket | StagingBucket The specified bucket does not exist (Service: Amazon S3; Status Code: 404; Error Code: NoSuchBucket; Request ID: xxx; S3 Extended Request ID: xxx)
 1/2 | 8:24:34 PM | UPDATE_ROLLBACK_IN_P | AWS::CloudFormation::Stack | CDKToolkit The following resource(s) failed to update: [StagingBucket]. 
 ❌  Environment aws://<account>>/<region> failed bootstrapping: Error: The stack named CDKToolkit is in a failed state: UPDATE_ROLLBACK_COMPLETE
The stack named CDKToolkit is in a failed state: UPDATE_ROLLBACK_COMPLETE

 

ただ、新規で cdk bootstrap を行ったつもりが、ログには UPDATE_IN_PROGRESS と出ていました。

そのため、もしかしたら、スタック CDKToolkit が存在しているせいかもしれないと考えました。

スタック CDKToolkit について、AWSドキュメントの記載は以下です。

Bootstrapping your AWS Environment
Before you can use the AWS CDK you must bootstrap your AWS environment to create the infrastructure that the AWS CDK CLI needs to deploy your AWS CDK app. Currently the bootstrap command creates only an Amazon S3 bucket.

You incur any charges for what the AWS CDK stores in the bucket. Because the AWS CDK does not remove any objects from the bucket, the bucket can accumulate objects as you use the AWS CDK. You can get rid of the bucket by deleting the CDKToolkit stack from your account.

https://docs.aws.amazon.com/cdk/latest/guide/tools.html

 

そこで、スタック CDKToolkit を削除した後、 cdk bootstrap を行ったところ、成功しました。

$ cdk bootstrap
 ⏳  Bootstrapping environment aws://<account>/<region>...
CDKToolkit: creating CloudFormation changeset...
 0/2 | 8:31:37 PM | CREATE_IN_PROGRESS   | AWS::S3::Bucket | StagingBucket 
 0/2 | 8:31:39 PM | CREATE_IN_PROGRESS   | AWS::S3::Bucket | StagingBucket Resource creation Initiated
 1/2 | 8:32:01 PM | CREATE_COMPLETE      | AWS::S3::Bucket | StagingBucket 
 2/2 | 8:32:03 PM | CREATE_COMPLETE      | AWS::CloudFormation::Stack | CDKToolkit 
 ✅  Environment aws://<account>/<region> bootstrapped.

 

次に、 cdk deploy してみたところ、デプロイが成功しました。

$ cdk deploy
<your_stack>: deploying...
<your_stack>: creating CloudFormation changeset...
 0/3 | 8:33:12 PM | CREATE_IN_PROGRESS   | AWS::IAM::User     | xxx (xxx) 
 0/3 | 8:33:12 PM | CREATE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata 
 0/3 | 8:33:13 PM | CREATE_IN_PROGRESS   | AWS::IAM::User     | xxx (xxx) Resource creation Initiated
 0/3 | 8:33:14 PM | CREATE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata Resource creation Initiated
 1/3 | 8:33:14 PM | CREATE_COMPLETE      | AWS::CDK::Metadata | CDKMetadata 

 ✅  <your_stack>

 

これより、AWS CDKを使っている場合は、S3のバケット CDKToolkit を削除してはいけないことが分かりました。

DjangoのListViewで、対象データがない場合は404ページを表示する

DjangoでListViewを使う場合、デフォルトではモデルにデータがない時は HTTP200 で、データがない状態で表示されます。

f:id:thinkAmi:20191029062825p:plain:w400

 
ただ、モデルにデータがない場合に HTTP404 を表示したい時はどうするか、調べたことをメモしておきます。

 

目次

 

環境

 

調査

公式ドキュメントにより、ListViewが継承している MultipleObjectMixin が持つ allow_empty を使えば良さそうでした。

 
stackoverflowにも、同じような回答がありました。
Django: get_object_or_404 for ListView - Stack Overflow  
 
そこで、以下のような

  • urls.py
  • models.py
  • todo_list.html

を用意し、 allow_empty の挙動を試してみます。

urls.py

urlpatterns = [
    path('', NotFoundListView.as_view(), name='todo_list'),
]

models.py

from django.db import models


class Todo(models.Model):
    title = models.CharField(max_length=100)
    content = models.CharField(max_length=255)

todo_list.html

<body>
<main class="container">
    <table>
        <thead>
        <tr>
            <th>ID</th>
            <th>タイトル</th>
            <th>内容</th>
        </tr>
        </thead>
        <tbody>
        {% for todo in todo_list %}
            <tr>
                <td>{{ todo.id }}</td>
                <td>{{ todo.title }}</td>
                <td>{{ todo.content }}</td>
        </tbody>
    </table>
    {% endfor %}
</main>
</body>

 

allow_empty=Trueの場合

使うListViewはこんな感じです。

from django.views.generic import ListView
from listview404.models import Todo


class NotFoundListView(ListView):
    allow_empty = True  # デフォルトのまま
    model = Todo

 

結果はこちら。HTTP200です。

f:id:thinkAmi:20191029062825p:plain:w400

 

allow_empty=Falseの場合

allow_empty のみ変更します。

from django.views.generic import ListView
from listview404.models import Todo


class NotFoundListView(ListView):
    allow_empty = False  # 変更
    model = Todo

 

HTTP404 が表示されました。

f:id:thinkAmi:20191029062859p:plain:w400

 

ソースコード

Githubに上げました。 listview404 が今回のアプリです。
https://github.com/thinkAmi-sandbox/Django22_generic_view-sample

AWS CDKで、cdk deployしたら「Unable to resolve AWS account to use」エラー

AWS CDKを使って cdk deploy したところ

$ cdk deploy
Unable to resolve AWS account to use. It must be either configured when you define your CDK or through the environment

というエラーが発生したため、対応した時のメモ。

 

環境

 

原因と対応

CDK CLIdoctor コマンドを使って確認したところ

$ cdk doctor
ℹ️ CDK Version: 1.9.0 (build 30f158a)
ℹ️ AWS environment variables:
  - AWS_SECRET_ACCESS_KEY = <redacted>
  - AWS_REGION = us-east-1
  - AWS_ACCESS_KEY_ID = AK<redacted>
  - AWS_S3_GATEWAY = http://http://s3.amazonaws.com
ℹ️ No CDK environment variables

と、自分のAWS CLIのconfigとは異なる値(AWS_REGION)や、設定していない値(AWS_S3_GATEWAY)が出てきました。

 
何か環境変数を指定したかなと思い確認したところ、

$ export -p
...
declare -x AWS_ACCESS_KEY_ID="AK***"
declare -x AWS_REGION="us-east-1"
declare -x AWS_S3_GATEWAY="http://http://s3.amazonaws.com"
declare -x AWS_SECRET_ACCESS_KEY="***"

と、別のところで使った時の環境変数を消し忘れているようでした。

 
そのため、 cdk doctor に出てきた環境変数を消しました。

$ unset AWS_S3_GATEWAY
$ unset AWS_REGION
$ unset AWS_SECRET_ACCESS_KEY
$ unset AWS_ACCESS_KEY_ID

 
再度実行したところ、問題なくデプロイできました。

$ cdk deploy
step-functions: deploying...
step-functions: creating CloudFormation changeset...
 0/2 | 8:12:22 PM | UPDATE_IN_PROGRESS   | AWS::CDK::Metadata               | CDKMetadata 

 ✅  step-functions

AWS CDK + Pythonで、ネストした AWS StepFunctions のワークフローを作ってみた

今年の7月にAWS CDK (Cloud Development Kit) がGAとなりました。
AWS クラウド開発キット (CDK) – TypeScript と Python 用がご利用可能に | Amazon Web Services ブログ

APIリファレンスも公開されているため、これでPythonを使ってAWSのリソースを作成することができるようになりました。
API Reference · AWS CDK

 
ただ、ドキュメントではTypeScriptの書き方がメインであり、Pythonでどう書くのかイマイチ分かりませんでした。

そこで、AWS CDK + Pythonで、ネストした AWS StepFunctions のワークフローを作ってみることにしました。

 
なお、記事の末尾にもあるように、ソースコードは公開しています。この記事はそのソースコードの解説です。

 

目次

 

環境

 
今回作成するStepFunctionsのワークフローです。

メインのStateMachineを実行します。サブのStateMachineを3パラレルで起動します。

f:id:thinkAmi:20190929163040p:plain:w300

 
サブの方は、各タスクにエラーハンドリングが付いています。

f:id:thinkAmi:20190929163143p:plain:w250

 
各StateMachineの機能です。

  • メイン
    • LambdaからS3へファイルを保存
    • サブのStateMachineを3パラレルで起動
  • サブ
    • Lambdaを実行

 

環境構築

Getting Startedに従い、環境を構築します。
Getting Started With the AWS CDK - AWS Cloud Development Kit (AWS CDK)

AWS CDKのCLIをインストールします。

$ npm install -g aws-cdk

$ cdk --version
1.9.0 (build 30f158a)

$ mkdir step_functions

$ cd step_functions/

 

cdk initPythonを使ったCDK環境を作成します。

$ cdk init --language python
Applying project template app for python
Executing Creating virtualenv...


# ちなみに、ディレクトリの中に何かファイルがあるとエラー
$ cdk init --language python
`cdk init` cannot be run in a non-empty directory!

 
Pythonの仮想環境が準備されるため、activate後にインストールします。

$ source .env/bin/activate

$ pip install -r requirements.txt

 
次にAWS CDKで使うリソースに対するモジュールを追加でインストールします。

どのモジュールが必要なのかは、APIリファレンスのトップに記載されています。*1

 
まずは、StepFunctionまわりで必要なモジュールをインストールします。

$ pip install aws_cdk.aws_stepfunctions

$ pip install aws_cdk.aws_stepfunctions_tasks

 
次にLambdaまわり。

$ pip install aws_cdk.aws_lambda

 
S3バケットにファイルを保存するため、S3のモジュールも必要です。

$ pip install aws_cdk.aws_s3

 
あとは、S3バケットにファイルを保存するために、IAMロールも必要になります。

$ pip install aws_cdk.aws_iam

 

作成順について

AWS CDKでは、下位のリソースから順に作成します。

今回は

  1. S3
  2. IAM Managed Policy
  3. IAM Role
  4. Lambda
  5. サブのStatemMachine (Step Functions)
  6. メインのStatemMachine (Step Functions)

の順で作成します。

 
Pythonでは、 core.Stack を継承したクラスの __init__() にて、作成したいリソースのオブジェクトを生成します。

class StepFunctionsStack(core.Stack):
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        # S3バケットを生成
        self.bucket = self.create_s3_bucket()
        # 管理ポリシーを生成
        self.managed_policy = self.create_managed_policy()
        ...

 

S3バケットの作成

ドキュメントに従い、S3バケットを新規作成します。
class Bucket (construct) · AWS CDK

 
ドキュメントはTypeScript形式で書かれています。

new Bucket(scope: Construct, id: string, props?: Bucket<wbr>Props)

Pythonに読み替えた時の書き方です。

def create_s3_bucket(self):
    return Bucket(
        self,  # scope
        'S3 Bucket',  # id
        bucket_name=f'sfn-bucket-by-aws-cdk',  # propsのbucketName
    )

Pythonで読み替える時のポイントは以下です。

  • scopeは self で読み替え
  • 引数名は、snake_caseで読み替え
  • props にて指定可能なキーと値は、ドキュメントの Construct Props に記載

 
ちなみに、もし既存のS3バケットを利用したい場合は

  • fromBucketArn()
  • fromBucketAttributes()
  • fromBucketName()

などの静的メソッドを使います。

これにより、既存のS3バケットのオブジェクトが取得できるので、他のリソースでの指定が可能になります。

 

IAM Managed Policy (管理ポリシー)の作成

今回は、S3バケットにアクセスする管理ポリシーを作成します*2
class ManagedPolicy (construct) · AWS CDK

 
Managed Policyを作成するには、

  1. PolicyStatement
  2. ManagedPolicy

の順でオブジェクトを作成します。

 

PolicyStatementの作成

ドキュメントに従い作成します。
class PolicyStatement · AWS CDK

なお、resourcesでは、上記で作成したBucketのARNを参照する必要があります。

ドキュメントにあるように、Bucketオブジェクトのプロパティ bucket_arn でARNを参照します。
https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html#properties

def create_managed_policy(self):
    statement = PolicyStatement(
        effect=Effect.ALLOW,
        actions=[
            "s3:PutObject",
        ],
        resources=[
            f'{self.bucket.bucket_arn}/*',
        ]
    )

 

ManagedPolicyの作成

こちらもドキュメント通りです。
class ManagedPolicy (construct) · AWS CDK

return ManagedPolicy(
    self,
    'Managed Policy',
    managed_policy_name='sfn_lambda_policy',
    statements=[statement],
)

 

IAMロールを作成

次に、LambdaからS3バケットへアクセスするためのIAMロールを作成します。

まずは、 ServicePrincipal オブジェクトでLambdaを指定します。
class ServicePrincipal · AWS CDK

def create_role(self):
    service_principal = ServicePrincipal('lambda.amazonaws.com')

 
次に、 Role オブジェクトを作成します。
class Role (construct) · AWS CDK

ポイントは以下です。

  • assumed_by に、ServicePrincipalオブジェクトを指定
  • managed_policies に、作成したManaged Policyを指定
return Role(
    self,
    'Role',
    assumed_by=service_principal,
    role_name='sfn_lambda_role',
    managed_policies=[self.managed_policy],
)

 

Lambdaの作成

今回は4つのLambdaを作成します。

Lambda名 用途
sfn_first_lambda S3にファイルを保存
sfn_second_lambda サブStateMachineの1番目のLambda
sfn_third_lambda サブStateMachineの2番目のLambda
sfn_error_lambda sfn_second_lambda・sfn_third_lambdaでエラーが起きた時に実行されるLambda

 
なお、CDKで使うLambda本体は、どこかのディレクトリに入れておけばOKです。

CDKのFunctionオブジェクトを生成する際に、そのディレクトリパスを指定することで、CDKがzip化・アップロードまで面倒を見てくれます。

 

S3にファイルを保存するLambdaを作成
Lambda本体

lambda_function/first/lambda_function.py として作成します。

ポイントは以下です。

  • boto3でS3へアップロードする
  • NumPyを使って値を取得する
  • 環境変数 BUCKET_NAME で、保存先のS3バケット名を受け取る
  • Lambdaのパラメータとして message が渡されてくる
    • StateMachineのInputより渡されることを想定
  • 戻り地は、 bodymessage を持つdict
import os
import boto3
from numpy.random import rand


def lambda_handler(event, context):
    body = f'{event["message"]} \n value: {rand()}'
    client = boto3.client('s3')
    client.put_object(
        Bucket=os.environ['BUCKET_NAME'],
        Key='sfn_first.txt',
        Body=body,
    )

    return {
        'body': body,
        'message': event['message'],
    }

 

CDKでLambdaリソース(Function)を作成

まずは、Lambda本体のあるファイルパスを使って、AssetCodeオブジェクトを生成します。
class AssetCode · AWS CDK

function_path = str(self.lambda_path_base.joinpath('first'))
code = AssetCode(function_path)

 
次にNumPyを用意します。

Lambdaでは、NumPyのようによく使われるモジュールは Lambda Layers として用意されていますので、今回もこちらを利用します。
新機能 – AWS Lambda :あらゆるプログラム言語への対応と一般的なコンポーネントの共有 | Amazon Web Services ブログ

 
LayerVersionオブジェクトにて、既存のLambda Layersを扱えます。
class LayerVersion (construct) · AWS CDK

今回はARNを指定してNumPyのLayerを取得するため、静的メソッド from_layer_version_arn() を使います。

scipy_layer = LayerVersion.from_layer_version_arn(
    self, f'sfn_scipy_layer_for_first', AWS_SCIPY_ARN)

なお、Lambda LayersのARNについては、AWSアカウントごとに異なるようです。

そのため、一度、Lambda Consoleにて、該当LayerのARNを確認する必要があります。

 
最後に、FunctionオブジェクトでLambdaリソースを生成します。
class Function (construct) · AWS CDK

return Function(
    self,
    f'id_first',
    # Lambda本体のソースコードがあるディレクトリを指定
    code=code,
    # Lambda本体のハンドラ名を指定
    handler='lambda_function.lambda_handler',
    # ランタイムの指定
    runtime=Runtime.PYTHON_3_7,
    # 環境変数の設定
    environment={'BUCKET_NAME': self.bucket.bucket_name},
    function_name='sfn_first_lambda',
    layers=[scipy_layer],
    memory_size=128,
    role=self.role,
    timeout=core.Duration.seconds(10),
)

 
ちなみに、Layerを自分で作成したい場合は以下のようにします。

Codeオブジェクトのリファレンスは以下です。
class Code · AWS CDK

LayerVersion(
    self,
    'layer_id',
    code=Code.from_asset('your_zip_filepath'),
    compatible_runtimes=[Runtime.PYTHON_3_7],
    layer_version_name='layer_version_name',
)

 

残りのLambda
2番目のLambda本体

エラーハンドリングしたいため、パラレル番号が偶数の場合はエラーとしています。

また、 result_path で結果をわかりやすく表示したいため、戻り値も絞っています。

def lambda_handler(event, context):
    if event['parallel_no'] % 2 == 0:
        raise Exception('偶数です')

    return {
        'message': event['message'],
        'const_value': event['const_value']
    }

 

3番目のLambda本体

こちらもエラーハンドリングしたいため、パラレル番号が1の場合はエラーとしています。

また、最後は文字列を返すようにしました。

def lambda_handler(event, context):
    if event['parallel_no'] == 1:
        raise Exception('強制的にエラーとします')

    return 'only 3rd message.'

 

エラーハンドリングのLambda本体

タスクでエラーが発生した場合は

{
  "resource": "arn:aws:lambda:region:id:function:sfn_error_lambda",
  "input": {
    "Error": "Exception",
    "Cause": "{\"errorMessage\": \"\\u5076\\u6570\\u3067\\u3059\",
               \"errorType\": \"Exception\",
               \"stackTrace\": [\"  File \\\"/var/task/lambda_function.py\\\", line 5,
                  in lambda_handler\\n    raise Exception('\\u5076\\u6570\\u3067\\u3059')
              \\n\"]}"
  },
  "timeoutInSeconds": null
}

という値が渡されてきます。

この中の CauseJSON文字列のため、Lambdaで見えるようにして返します。

def lambda_handler(event, context):
    return {
        # JSONをPythonオブジェクト化することで、文字化けを直す
        'error_message': json.loads(event['Cause']),
    }

 

Functionオブジェクト

ほぼ同じ内容なので一つのメソッドで生成しています。

def create_other_lambda(self, function_name):
    function_path = str(self.lambda_path_base.joinpath(function_name))

    return Function(
        self,
        f'id_{function_name}',
        code=AssetCode(function_path),
        handler='lambda_function.lambda_handler',
        runtime=Runtime.PYTHON_3_7,
        function_name=f'sfn_{function_name}_lambda',
        memory_size=128,
        timeout=core.Duration.seconds(10),
    )
    
# 使う時
self.second_lambda = self.create_other_lambda('second')
self.third_lambda = self.create_other_lambda('third')
self.error_lambda = self.create_other_lambda('error')

 

サブのStateMachine (Step Functions) 作成

概要 (InputPath・OutputPath・ResultPathを試す)

ここまででパラレル実行する、サブのStateMachineのリソースが用意できました。

それらを組み合わせて、サブのStateMachineを作成していきます。

 
StateMachineでは、タスクという単位で処理を定義します。
タスク - AWS Step Functions

サブのStateMachineでは3つのタスクを用意します。

今回、InputPath・OutputPath・ResultPathを試そうと考えました。

 
そこで、こんな感じの設定にしました。

タスク名 Lambda InputPath ResultPath OutputPath
Second Task sfn_second_lambda $['first_result', 'parallel_no', 'message', 'context_name', 'const_value] $.second_result' ['second_result', 'parallel_no']
Third Task sfn_third_lambda $ (Lambdaの戻り値で上書き)
Error Task sfn_error_lambda

 
Second Taskでは、

  • InputPath
    • 前のタスクの結果から 'first_result', 'parallel_no', 'message', 'context_name', 'const_value だけ受け取って処理する
  • OutPath
    • Lambdaの戻り値を、入力値に second_result という項目を追加する
  • ResultPath
    • 次のタスクには 'second_result', 'parallel_no' のみ渡す

とします。

 
また、Third Taskでは、

  • ResultPath
    • 入力値をすべて捨て、Lambdaの戻り値だけを出力する

とします。

 

実際のソースコード

まず、複数タスクで使うため、1つのエラータスクを作成します。

Lambdaを起動するためにはInvokeFunctionクラスを使います。引数としてLambdaオブジェクトを設定します。
class InvokeFunction · AWS CDK

error_task = Task(
    self,
    'Error Task',
    task=InvokeFunction(self.error_lambda),
)

 
次に、Second Taskを生成します。

InputPath・OutputPath・ResultPathに対応する引数があるため、それぞれ指定します。

second_task = Task(
    self,
    'Second Task',
    task=InvokeFunction(self.second_lambda),

    # 渡されてきた項目を絞ってLambdaに渡す
    input_path="$['first_result', 'parallel_no', 'message', 'context_name', 'const_value]",

    # 結果は second_result という項目に入れる
    result_path='$.second_result',

    # 次のタスクに渡す項目は絞る
    output_path="$['second_result', 'parallel_no']"
)

 
Second Taskの中でエラーが発生した場合のハンドリングをするため、 add_catch でエラーが発生した時のタスクを追加します。
エラー処理 - AWS Step Functions

second_task.add_catch(error_task, errors=['States.ALL'])

 
Third Taskを作成します。こちらもエラーハンドリングを追加します。

third_task = Task(
    self,
    'Third Task',
    task=InvokeFunction(self.third_lambda),

    # third_lambdaの結果だけに差し替え
    result_path='$',
)
# こちらもエラーハンドリングを追加
third_task.add_catch(error_task, errors=['States.ALL'])

 
次に、Second Taskの後にThird Taskを実行できるよう、 next() にて設定します。

definition = second_task.next(third_task)

 
最後に、StateMachineを作成します。

return StateMachine(
    self,
    'Sub StateMachine',
    definition=definition,
    state_machine_name='sfn_sub_state_machine',
)

 

メインのStateMachine (Step Functions) の作成

最後のリソースとして、メインのStateMachineを作成します。

 
まずは First Task を作成します。ここは先程と同じです。

first_task = Task(
    self,
    'S3 Lambda Task',
    task=InvokeFunction(self.first_lambda, payload={'message': 'Hello world'}),
    comment='Main StateMachine',
)

 
次のタスクがパラレル実行するサブのStateMachineです。

最初にParallelオブジェクトを生成します。
class Parallel (construct) · AWS CDK

parallel_task = Parallel(
    self,
    'Parallel Task',
)

 
次に、パラレル実行の設定を行います。流れは以下となります。

  1. StartExecution オブジェクトで、使用するStateMachineやInputなどを指定します。
    class StartExecution · AWS CDK

  2. StartExecutionTask に渡します。

  3. TaskParallel.branch() に渡します。

for i in range(1, 4):
    # 1.
    sub_task = StartExecution(
        self.sub_state_machine,
        input={
            'parallel_no': i,
            'first_result.$': '$',

            # first_taskのレスポンスにある、messageをセット
            'message.$': '$.message',

            # コンテキストオブジェクトの名前をセット
            'context_name.$': '$$.State.Name',
            # 固定値を2つ追加(ただ、Taskのinputでignore_valueは無視)
            'const_value': 'ham',
            'ignore_value': 'ignore',
        },
    )

    # 2.
    invoke_sub_task = Task(
        self,
        f'Sub Task {i}',
        task=sub_task,
    )

    # 3.
    parallel_task.branch(invoke_sub_task)

 

以上でStepFunctionsが完成しました。

 

$と$$について

上記例では、

  • 'message.$': '$.message'
  • 'context_name.$': '$$.State.Name'

としている部分がありました。

$$$ については、それぞれ以下の意味となります。

$ について

パスを使用して値を選択するキーと値のペアの場合、キーの名前は .$ で終わる必要があります。 InputPath およびパラメータ - AWS Step Functions

 
$$ について

コンテキストオブジェクトにアクセスするには、パスを使用して状態の入力を選択したときと同様に、.$ を末尾に追加したパラメータ名をまず指定します。次に、入力の代わりにコンテキストオブジェクトデータにアクセスするには、$$. をパスの先頭に追加します。

コンテキストオブジェクト - AWS Step Functions

 

実行結果

ではStepFunctionsのConsoleより実行してみます。

 

メインのStateMachine

f:id:thinkAmi:20190929170656p:plain:w300

S3 Lambda TaskのLambdaFunctionScheduled

{
  "resource": "arn:aws:lambda:region:account_id:function:sfn_first_lambda",
  "input": {
    "message": "Hello world"
  },
  "timeoutInSeconds": null
}

S3 Lambda TaskのTaskStateExited

{
  "name": "S3 Lambda Task",
  "output": {
    "body": "Hello world \n value: 0.035671270119142284",
    "message": "Hello world"
  }
}

 

サブのStateMachine

3パターンあるため、それぞれ記載します。

  • すべて成功
  • Second Taskでエラー
  • Third Taskでエラー
すべて成功

f:id:thinkAmi:20190929171108p:plain:w300

Second TaskのLambdaFunctionScheduledでは、設定した

input_path="$['first_result', 'parallel_no', 'message', 'context_name', 'const_value']",

の通りのinputとなっています。

{
  "resource": "arn:aws:lambda:region:account_id:function:sfn_second_lambda",
  "input": {
    "first_result": {
      "body": "Hello world \n value: 0.035671270119142284",
      "message": "Hello world"
    },
    "parallel_no": 3,
    "message": "Hello world",
    "context_name": "Sub Task 3",
    "const_value": "ham"
  },
  "timeoutInSeconds": null
}

 
Second TaskのTaskStateExitedでも、

result_path='$.second_result',
output_path="$['second_result', 'parallel_no']"

の通りのoutputです。

{
  "name": "Second Task",
  "output": {
    "second_result": {
      "message": "Hello world",
      "const_value": "ham"
    },
    "parallel_no": 3
  }
}

 

Third TaskのLambdaFunctionScheduledはこんな感じ。Secondのoutputを引き継いでいます。

{
  "resource": "arn:aws:lambda:region:account_id:function:sfn_third_lambda",
  "input": {
    "second_result": {
      "message": "Hello world",
      "const_value": "ham"
    },
    "parallel_no": 3
  },
  "timeoutInSeconds": null
}

 

Third TaskのTaskStateExited

result_path='$',通り、outputはLambdaの戻り値の文字列だけになっています。

{
  "name": "Third Task",
  "output": "only 3rd message."
}

 

Second Taskでエラー

f:id:thinkAmi:20190929172018p:plain:w300

Second TaskのLambdaFunctionScheduledは以下の通り(抜粋)。

{
  "input": {
    "parallel_no": 2,
    "context_name": "Sub Task 2",
  },
}

 
Error TaskのTaskStateExitedを確認します。日本語がそのまま表示されています。

{
  "name": "Error Task",
  "output": {
    "error_message": {
      "errorMessage": "偶数です",
      "errorType": "Exception",
      "stackTrace": [
        "  File \"/var/task/lambda_function.py\", line 3, in lambda_handler\n    raise Exception('偶数です')\n"
      ]
    }
  }
}

 

Third Taskでエラー

f:id:thinkAmi:20190929172322p:plain:w300

Third TaskのLambdaFunctionScheduledは以下の通り(抜粋)。

{
    "parallel_no": 1
  },
}

 
Error TaskのTaskStateExitedを確認します。日本語がそのまま表示されています。

{
  "output": {
    "error_message": {
      "errorMessage": "強制的にエラーとします",
      "errorType": "Exception",
      "stackTrace": [
        "  File \"/var/task/lambda_function.py\", line 3, in lambda_handler\n    raise Exception('強制的にエラーとします')\n"
      ]
    }
  }
}

 

削除

cdk destroy にて削除できます。

$ cdk destroy
Are you sure you want to delete: step-functions (y/n)? y
...
 ✅  step-functions: destroyed

 

ただ、上記の方法で作成したS3は、removalpolicyの値により削除されません。
https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html#removalpolicy

そのため、手動で削除するか、作成する時に以下のような設定を行います。
AWS CloudFormationでStackを削除したときにリソースを消さない設定 | DevelopersIO

 

動的並列処理について

先日、StepFunctionsでの動的並列処理がサポートされました。
AWS Step Functions がワークフローでの動的並列処理のサポートを追加

 
CDKでは、以下のissueがcloseとなれば利用できそうです。

 

ソースコード

GitHubに上げました。 step_functions ディレクトリが今回のファイルです。
https://github.com/thinkAmi-sandbox/AWS_CDK-sample

*1:例えばLambdaの場合は、 aws_cdk.aws_lambda が必要です:https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-readme.html

*2:もし、ポリシーを使う場合はこちら:https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-iam.Policy.html

#pyconjp 発表「知ろう!使おう!HDF5ファイル!」の落ち穂拾い

PyCon JP 2019にて発表をした際、いくつか質問をいただきました。

前回の記事にもあるように、当時きちんと回答できたか不安だったため、今回落ち穂拾いとしてまとめてみます。

なお、誤りや過不足などがありましたら、ご指摘いただけるとありがたいです。

   
目次

 

そもそも、何のデータを入れるためにHDFができたのか

The HDF Groupの History of HDF Group ページに記載がありましたので、引用します。
History of HDF Group

In 1987, the Graphics Foundations Task Force (GFTF) at the National Center for Supercomputing Applications (NCSA) at the University of Illinois at Urbana-Champaign, set out to create an architecture-independent software library and file format to address the need to move scientific data among the many different computing platforms in use at NCSA at that time. Additional goals for the format and library included the ability to store and access large objects efficiently, the ability to store many objects of different types together in one container, the ability to grow the format to accommodate new types of objects and object metadata, and the ability to access the stored data with both C and Fortran programs.

 

クロスプラットフォーム間で、Excelファイルの罫線や内容が壊れないか

発表時点でも検証していましたが、もう少し詳しく検証してみました。

用意したExcelファイルは、以下の(a)〜(d)の4種類です。

 
これらのファイルをHDF5ファイルにDatasetとして保存して、Mac/Windowsの双方で読み込んでみます。

 

自作ファイルの作成について

自作ファイルは

  • 罫線
  • 画像
  • 罫線
  • シートのロック

を持つExcelファイルを、 Mac + openpyxlで作成するものとします。

excel_creator.py として用意します。

import datetime
import pathlib

import openpyxl
from openpyxl.comments import Comment
from openpyxl.styles.borders import Border, Side, BORDER_THIN
from openpyxl.styles.colors import BLUE

BASE_DIR = pathlib.Path(__file__).resolve().parents[0]
INPUT_DIR = BASE_DIR.joinpath('input')
OUTPUT_DIR = BASE_DIR.joinpath('output', 'h5py')


wb = openpyxl.Workbook()

# grab the active worksheet
ws = wb.active

# セルにデータを入れる
ws['A1'] = 'Hello, world!'

# 日付データを入れる
ws['A2'] = datetime.datetime.now()

# 罫線を引く
side = Side(style=BORDER_THIN, color=BLUE)
border = Border(top=side, bottom=side, left=side, right=side)
a4 = ws['B3'].border = border

# コメントを入れる
ws['B4'].comment = Comment('ham', 'myauthor')

# 画像ファイル差し込み
img_path = INPUT_DIR.joinpath('shinanogold.png')
img = openpyxl.drawing.image.Image(img_path)
ws.add_image(img, 'C2')

# シートの保護
ws.protection.enable()

# 保存
output = OUTPUT_DIR.joinpath('original.xlsx')
wb.save(output)

 

公開ファイルの利用について

公開ファイルは xls 形式でした。

そのため、Mac上で手動で xlsx へと変換・保存したものを使用します。

 

HDF5ファイルとして保存するプログラム

h5pyを使い、 gleaning.py として作成します。

なお、いずれのExcelファイルも gzip で圧縮するものとします (create_dataset()の引数 compression を利用)。

import hashlib
import pathlib

import h5py

import numpy as np

BASE_DIR = pathlib.Path(__file__).resolve().parents[0]
INPUT_DIR = BASE_DIR.joinpath('input')
OUTPUT_DIR = BASE_DIR.joinpath('output', 'h5py')
TYPE_OF_BINARY = h5py.special_dtype(vlen=np.dtype('uint8'))

ORIGINAL_EXCEL_PATH = OUTPUT_DIR.joinpath('original.xlsx')
GLEANING_HDF5_PATH = OUTPUT_DIR.joinpath('gleaning.h5')

STATISTICS_3MB = OUTPUT_DIR.joinpath('statistics_kenporonbun-toukei201604.xlsx')
STATISTICS_LINE = OUTPUT_DIR.joinpath('statistics_nenpou09.xlsx')
STATISTICS_IMAGE = OUTPUT_DIR.joinpath('statistics_2017_1_02.xlsx')


def calculate_md5(path):
    return hashlib.md5(path.read_bytes()).hexdigest()


def compare_excel(from_path):
    print(f'{from_path.name} --------->')

    # 元ExcelのMD5を見る
    from_md5 = calculate_md5(from_path)
    print(f'from MD5: {from_md5}')

    dataset_name = f'excel_{from_path.stem}'

    # gzip圧縮して、ExcelをHDF5ファイルに入れる
    with h5py.File(GLEANING_HDF5_PATH, mode='a') as file:
        with from_path.open(mode='rb') as excel:
            excel_binary = excel.read()

        excel_data = np.frombuffer(excel_binary, dtype='uint8')
        ds_excel = file.create_dataset(
            dataset_name, excel_data.shape, dtype=TYPE_OF_BINARY, compression='gzip')
        ds_excel[0] = excel_data

    # 取り出して、MD5を見る
    with h5py.File(GLEANING_HDF5_PATH, mode='r') as file:
        dataset = file[dataset_name]
        export_path = from_path.parents[0].joinpath(f'{from_path.stem}_after{from_path.suffix}')
        with export_path.open('wb') as w:
            w.write(dataset[0])

    to_md5 = calculate_md5(export_path)
    print(f'to MD5  : {to_md5}')

    assert from_md5 == to_md5


if __name__ == '__main__':
    # きれいなHDF5ファイルを使うため、事前に削除しておく
    if GLEANING_HDF5_PATH.exists():
        GLEANING_HDF5_PATH.unlink()

    compare_excel(ORIGINAL_EXCEL_PATH)
    compare_excel(STATISTICS_3MB)
    compare_excel(STATISTICS_LINE)
    compare_excel(STATISTICS_IMAGE)

 

HDF5からExcelの入ったDatasetを読み込み、ローカルに保存するプログラム

こちらも h5py を使って excel_reader.py として作成します。

なお、Mac/Windows間で保存したファイルに差異がないか検証するため、MD5ハッシュをprintします。

import hashlib
import pathlib

import h5py

BASE_DIR = pathlib.Path(__file__).resolve().parents[0]
OUTPUT_DIR = BASE_DIR.joinpath('output', 'h5py')
ORIGINAL_EXCEL_PATH = OUTPUT_DIR.joinpath('original.xlsx')
GLEANING_HDF5_PATH = OUTPUT_DIR.joinpath('gleaning.h5')

STATISTICS_3MB = OUTPUT_DIR.joinpath('statistics_kenporonbun-toukei201604.xlsx')
STATISTICS_LINE = OUTPUT_DIR.joinpath('statistics_nenpou09.xlsx')
STATISTICS_IMAGE = OUTPUT_DIR.joinpath('statistics_2017_1_02.xlsx')


def calculate_md5(path):
    return hashlib.md5(path.read_bytes()).hexdigest()


def print_md5(path):

    dataset_name = f'excel_{path.stem}'
    with h5py.File(GLEANING_HDF5_PATH, mode='r') as file:
        dataset = file[dataset_name]
        export_path = path.parents[0].joinpath(f'{path.stem}_reader{path.suffix}')
        with export_path.open('wb') as w:
            w.write(dataset[0])

    to_md5 = calculate_md5(export_path)
    print(f'{path.name} MD5  : {to_md5}')


if __name__ == '__main__':
    print_md5(ORIGINAL_EXCEL_PATH)
    print_md5(STATISTICS_3MB)
    print_md5(STATISTICS_LINE)
    print_md5(STATISTICS_IMAGE)

 

MacでHDF5ファイルからExcelを取得した結果

MD5は以下の通りでした。

$ python excel_reader.py 
original.xlsx MD5  : af366474fc7f09cc5bcfdff764acf01d
statistics_kenporonbun-toukei201604.xlsx MD5  : bb82c493c80e362da47b96b9e8b28385
statistics_nenpou09.xlsx MD5  : 4de608b9452a34caa8a76f1fe8c4b265
statistics_2017_1_02.xlsx MD5  : 838d003d262f05f7e50585feab229f5d

 
また、取得した各ファイルを開いてみましたが、壊れた様子は特にありませんでした。

 

WindowsでHDF5ファイルからExcelを取得した結果

Windowsで実行した結果は以下のスクショの通りです。

f:id:thinkAmi:20190925222701p:plain:w400

 
テキストに落としてみた結果はこちら。MD5自体の差異はなさそうです。

>python excel_reader.py
original.xlsx MD5  : af366474fc7f09cc5bcfdff764acf01d
statistics_kenporonbun-toukei201604.xlsx MD5  : bb82c493c80e362da47b96b9e8b28385
statistics_nenpou09.xlsx MD5  : 4de608b9452a34caa8a76f1fe8c4b265
statistics_2017_1_02.xlsx MD5  : 838d003d262f05f7e50585feab229f5d

 
また、実際にWindowsExcelで開いてみましたが、「ファイルが壊れています」等の表示もなく、見た感じ大丈夫そうでした。

 

圧縮したDatasetを読み込む時は、展開しないとダメか

上記のExcelファイル検証で行いましたが、Datasetを作成する際、圧縮方法としてgzipを指定しました。

一方、読み込む時のソースコードでは、gzipからの展開は特に指定していません。

そのため、h5pyを使う限りは、展開する必要はなさそうです*1。  
 

zipファイルとの違い

今回の発表は、「NumPy方面でなくてもHDF5ファイルは使える」が主題でした。そのため、zipファイルとの機能の違いは確かに気になります。

調べたところでは、

  • HDF5ファイルには、パスワード設定機能がない

がありました。

ただ、プログラムから扱うことを考えると、以下のような機能を利用できるため、HDF5ファイルの方が扱いやすいのかなという印象です。

  • Datasetに紐づくAttributeに対して検索ができる
    • HDF5ファイルを作成する段階で、後から検索しやすいようなAttributeを色々と付けておく
  • (今回の主題とは外れますがが) NumPy方面と相性が良い
    • 上記の History of HDF Group からもうかがえる。
  • 圧縮して格納したDatasetを取り出す場合、展開する必要がない

 
もし、この点について詳しい方がいらっしゃいましたら、ご指摘いただけるとありがたいです。

 

圧縮アルゴリズムについて

What kind of compression methods does HDF5 support? より引用します。

HDF5 supports gzip (deflate), Szip, n-bit, scale-offset, and shuffling (with deflate) compression filters.

 
また、その他にも以下のページに記載がありました。

 

複数人で利用する時のロックはどうなるのか

HDF5 1.10.0以降、 SWMR (Single-Writer/Multiple-Reader) が導入されています。

詳細は以下のリンクとなります。

 
ちなみに、スレッドセーフについては、こちらで触れられています。

 

HDF5ファイルをSQLライクに扱いたい

HDF5ファイルでは、SQLを知らなくてもデータを階層的に保存することができます。

一方で、慣れたSQLライクにHDF5ファイルを保存したいことがあるかもしれません。

その場合は、HDFql というライブラリがあります。
The easy way to manage HDF5 data

 
そのページでは、こんな感じの例が挙げられています。

create file myFile.h5
use file myFile.h5
create dataset myGroup/myDataset as float enable zlib values(12.4)

 
以降は、発表ではふれなかった、スライドの内容です。

   

次のステップについて

HDF5ファイルについて詳しく知りたい場合は、以下の書籍が参考になります。
Python and HDF5 - O'Reilly Media | Andrew Collette著

 
また、h5pyやPyTablesの使い方については、以下の記事や発表を見るのが良いと思います。

あるいは、SciPy 2017のチュートリアルもあります。
tomkooij/scipy2017: SciPy 2017 tutorial: HDF5 take 2: h5py and PyTables

 

公式情報

The HDF Groupでは、いろいろな公式情報があります。

HDF Forumでは質疑応答が色々とありましたので、困ったら覗いてみるのもよいかもしれないです。

 

h5servを使うための準備

h5servを使うためには、少々手間がかかりましたので、メモ代わりに記載します。

自分の端末で使う場合、hostsファイルに設定

hostsファイルのIPアドレスに対する値は、 <ファイル名>.<適当なドメイン> という形で設定します。

例えば、h5servが動作している example.com にアクセスすると、 test.h5 ファイルを操作できるようにするには、以下のような設定となります。

127.0.0.1 test.example.com

 
hostsファイルの設定が終わったら、domain オプションでドメインを指定した上で、h5servを起動します。

$ python h5serv --port=5000 --toc_name=mytoc.h5 --domain=example.com

 

操作対象のファイルを作成

中身は空で問題ないですので、作成します。

作成先は、h5servのデータディレクトリである data の中になります。

また、ファイル名は test.h5 です。

p = Path(__file__).resolve().parents[0].joinpath('h5serv', 'data', 'test.h5')
with h5py.File(p, mode='w') as f:
    pass

 

この時点での data ディレクトリ構成
$ tree
.
├── public/
├── readme.txt
└── test.h5

 

動作確認

では、実際にHDF5ファイルを扱ってみます。今回は型定義を作成するところまでです。

# 型定義を作成
str_type = {'charSet': 'H5T_CSET_UTF8',
            'class':   'H5T_STRING',
            'strPad':  'H5T_STR_NULLTERM',
            'length':  'H5T_VARIABLE'}
payload = {'type': str_type, 'shape': 1}

# h5servにPOST
res1 = requests.post(
    URL_BASE + 'datasets',
    json=payload
).json()

 
結果を確認します。

新しく test.h5 ファイルが作成されました。

├── mytoc.h5
├── public/
├── readme.txt
└── test.h5

 
また、Datasetを見ると、型定義がされていることが分かります。

f:id:thinkAmi:20190925230419p:plain:w300

 
 

HDF5ファイルのバージョンについて、もう少し

HDF5 Library Release Version Numbers より、もう少し詳しく見てみます。

HDF5ファイルのバージョンが HDF5 x.y.z となっているとすると、x, y, zのそれぞれは以下を示します。

  • x: major version number
    • ライブラリやファイルフォーマットの大きな変更あり
  • y: minor version number
    • 新しい機能によるファイルフォーマットの変更
    • 偶数はstable、奇数はdevelop
  • z: release number
    • バグ修正等ライブラリの改善、フォーマットの変更なし

 

ソースコード

この記事で使ったソースコードについては、リポジトリに追加してあります。
https://github.com/thinkAmi/PyCon_JP_2019_talk/commit/df88400b033bdd64beecb8453b1f2542d7bc33a1

*1:他の言語、他のライブラリでは異なるかもしれませんが

#pyconjp PyCon JP 2019に参加しました & 発表しました

9/16(月・祝)・17(火)に大田区産業プラザ PiOで開催された「PyCon JP 2019」に参加 & 発表しました。
トップページ - PyCon JP 2019

 
去年同様、今年も無事に参加できてよかったです。
#pyconjp PyCon JP 2018に参加しました - メモ的な思考的な

 
動画もすでに公開されています。ありがとうございます。
PyConJP - YouTube

 
ここでは自分の発表、および全体を通してのメモを残します。誤りがありましたらご指摘ください。

 
目次

 

自分の発表「知ろう!使おう!HDF5ファイル!」

タイムテーブル上の詳細はこちら
知ろう!使おう!HDF5ファイル!

 
スライドです。

 
発表で使用したソースコードはこちらです。
https://github.com/thinkAmi/PyCon_JP_2019_talk

 
動画はこちら。7分くらいから始まるようです。
02-603_知ろう!使おう!HDF5ファイル!(thinkAmi) - YouTube

 
発表ですが、時間配分を誤り、持ち時間を発表で使い切ってしまったことから、時間内に質疑応答ができませんでした。本当に申し訳ないです。

本当は「この発表の次のステップ」についてふれたかったのですが、時間の都合上カットしました。資料だとp77〜あたりです。

 
ただ、ありがたいことに、発表終了後いくつか質問をいただきました。

覚えているのは

  • そもそも、何のデータを入れるためにHDFができたのか
  • クロスプラットフォーム間で、Excelファイルの罫線や内容が壊れないか (特に大きなサイズのExcelファイル)
  • zipファイルとの違い

あたりです。

ただ発表後も緊張が解けず、質問と回答の記憶があやふやです。そのため、もしかしたら質問の内容不足や誤りがあるかもしれません。その場合はお知らせください。

また、ちょっと検証したいこともあるため、検証後にBlog化したいです。

 
というところで、以降は今回のPyCon JP 2019のメモです。

 

参加したセッション

基調講演:Why Python is Eating the World

Cory Althoff 氏

 
書籍「独学プログラマー」の著者による基調講演でした。

Think PythonPythonを学びはじめてからの行動を聞き、やはり何か気になることがあれば行動することが大事だなと感じました。

また、後半のStart somethingのところを聞き、今の自分にできることとして、このBlogを引き続き書いていこうと感じました。

 

ExcelPython による社会インフラシステムの設定ファイルの自動生成

武山 文信 氏

 
設定ファイル という単語につられて参加しました。

お話の中で、設定ファイル生成ツールに求められる条件が整理されていましたので、もし同じような状況になったら参考にしたいです。

互換性を保ちながら、できるところだけでも自動化していく姿も印象に残りました。

 

Djangoで実践ドメイン駆動設計による実装

大島 和輝 氏

 
Djangoでのドメイン駆動設計が気になって参加しました。

セッションの中でもふれられていましたが、Djangoでのドメイン駆動設計例が見当たらないため、今回のソースコードを参考にして取り組んでみたいです。

Python製のDIコンテナ python-inject にも言及しており、後で見てみようと思いました。

 

SupportingPython3 in Large Scale Project

Kir Chou 氏

 

Python2からPython3に切り替えるにあたり、考えるべきことがいくつもあげられていました。

また、技術的な資料へのリンクも豊富にあるのが助かりました。

今のところ手元には移行するものはありませんが、今後出会う機会があれば、参考にしたいと感じました。

 

Python ウェブアプリケーションのためのプロファイラの実装

Yusuke Miyazaki 氏

 
昔、WSGIミドルウェアについて調べていた時に、 wsgi-lineprof を試したことがあったため、内部構造などを知りたくて参加しました。

wsgi-lineprofのアーキテクチャの図が分かりやすかったです。

また、プロファイラはどのような技術で構成されているかの紹介もあり、こんな便利な機能がいろいろとあるんだと勉強になりました。

 

Doujin-activity with Ren'Py

Daisuke Saito 氏

 
来週の技術書典7に参加予定なこともあり、 Doujin というタイトルに惹かれて参加しました。

Ren'Pyの概要と、簡単にゲームができあがるということが分かりました。

 

基調講演:Pythonで切り開く新しい農業

小池 誠 氏

 
きゅうりで機械学習 という話を読んだこともあり、楽しみな基調講演でした。

初めて見た時とは判別機の扱いが変わっていて、判別機が人間のサポートをするようにしたことで、熟練者の技術継承がしやすくなっているように見えました。

講演の中で特に印象に残ったのは、終わりの方の すべては学ぶためにとすれば、失敗ではなくなる。やってみなければわからない という言葉です。

それを聞き、次に活かせるような形で失敗するよう心がけようと思いました。

 

Pythonで始めてみよう関数型プログラミング

寺嶋 哲 氏

 
関数型プログラミングについて、その考え方とPythonで実現するためのライブラリ紹介がありました。

自分の今のコードに取り入れることができそうな関数型の考え方がいくつもあり、参考になりました。

あとで資料を見返して、理解を深めたいと感じました。

 

Pythonでライブをしよう - FoxDotを使った新時代のPython活用法 -

田中 慎太郎 氏

 
同僚です。

社内のPyCon JP 予行演習会にてライブコーディングを聴いた時から「当日は、この次のコマで自分が発表するから、この音楽セッションでリラックスしよう」と考えていました。

デモが進むに従って曲になっていったので、「あの変更でこんなふうに音が変化するんだ」と、とても不思議な感覚でした。音楽に詳しくないせいかもしれませんが、新鮮でした。

Youtube上だとFoxDotの音があまり残っていないようで残念ですが、ライブだと思えば仕方ないですね。。。

 

機械学習ライブラリのPython API作成方法

山入端 孝浩 氏

 

外部ライブラリのラッパーを作る時の大切なこと(既存との互換性やメモリ使用量など)についてふれられていました。

普通に使う時とpytestの時でメモリ使用量が異なるの、あまり着目したことがなかったので、参考になりました。

 

Ansibleを通じて「べき等性」を理解してみよう

Kazuya Takei 氏

 
Ansibleではどんなふうに冪等性を担保しているのか気になっていたこともあり、参加しました。

ソースコードの解説から、Ansibleががんばって標準出力を解析してべき等性を担保していると分かり、意外と泥臭いことをやってると分かりました。

あとは、Ansibleで管理できるルーターがほしいなーと思いました。

 

その他

同僚のスピーカーなど

上記にもありますが、私を含めて、会社から3名がスピーカーとして登壇しました。もう一人はこちら。
Pythonと便利ガジェット、サービス、ツールを使ってセンシング〜見る化してみよう

 
また、今年もぷろぷろ(@pro_proletariat)氏の呼びかけで、社内でPyCon JP 予行演習会が何回か開催されました。ありがとうございました。

 
あと、社内のPython使いの絵師さんが描いたマスコットキャラクター(ぎーらぼちゃん)も、無事デビューできてよかったです。

 

ブース

2日目の登壇ということもあり、精神的余裕があまりなく、ブース巡りはあまりできなかったのが残念でした。

1日目のはじめに、Python EDのガラガラでマグカップをいただきました。ありがとうございました。

 
また、書籍「独学プログラマー」のサイン会がありました。めったにない機会なので書籍を購入しサインを頂きました。ありがとうございました。

 

パーティ

ご挨拶したかった方々といろいろとお話できてよかったです。

Welcomeパーティの時もそうでしたが、年々パーティの雰囲気に慣れてきているのか、今年は自分から話しかけられたので成長した感がありました。

 
最後になりましたが、PyCon JP 2019を運営してくださったみなさま、参加者のみなさま、ありがとうございました。

来年も同じ場所で開催されるようですので、楽しみにして一年を過ごしたいと思います!