kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Python の mimetypes.guess_type() で .geojson を変換できるようにする

Python 標準ライブラリ mimetypes を使って GeoJSON ファイル名 (.geojson) から MIME タイプに変換しようとしたら (None, None) になってしまった😇

>>> import mimetypes
>>> mimetypes.guess_type('example.geojson')
(None, None)

docs.python.org

前提条件

  • Python 3.12

mimetypes.types_map を確認する

MIME タイプのマッピングは環境によって異なる場合があって,今回は macOS (Sonoma) と AWS Lambda 関数 (Python 3.12) で mimetypes.types_map の値から JSON 関連のマッピングを確認してみた.やはり .geojson のマッピングはなかった🥲

macOS (Sonoma)

{'.json': 'application/json', '.jsonml': 'application/jsonml+json'}

AWS Lambda 関数 (Python 3.12)

{'.json': 'application/json'}

mimetypes.add_type() で追加する

mimetypes.add_type() でマッピングを追加すれば OK👌

>>> mimetypes.add_type('application/geo+json', '.geojson')
>>> mimetypes.guess_type('example.geojson')
('application/geo+json', None)

knownfiles を確認する

MIME タイプのマッピング (knownfiles) は mimetypes.knownfiles で確認できる.

>>> mimetypes.knownfiles
['/etc/mime.types', '/etc/httpd/mime.types', '/etc/httpd/conf/mime.types', '/etc/apache/mime.types', '/etc/apache2/mime.types', '/usr/local/etc/httpd/conf/mime.types', '/usr/local/lib/netscape/mime.types', '/usr/local/etc/httpd/conf/mime.types', '/usr/local/etc/mime.types']

github.com

macOS だと /etc/apache2/mime.types に knownfiles があって,JSON 関連のマッピングを確認してみた.

mimetypes.types_map の値と同じで application/jsonapplication/jsonml+json はサポートされていて,application/geo+json はコメントアウトになっていた💡

$ grep -i json /etc/apache2/mime.types
# application/alto-costmap+json
# application/alto-costmapfilter+json
# application/alto-directory+json
# application/alto-endpointcost+json
# application/alto-endpointcostparams+json
# application/alto-endpointprop+json
# application/alto-endpointpropparams+json
# application/alto-error+json
# application/alto-networkmap+json
# application/alto-networkmapfilter+json
# application/calendar+json
# application/coap-group+json
# application/csvm+json
# application/geo+json
# application/jose+json
# application/jrd+json
application/json                json
# application/json-patch+json
# application/json-seq
application/jsonml+json             jsonml
# application/jwk+json
# application/jwk-set+json
# application/ld+json
# application/merge-patch+json
# application/ppsp-tracker+json
# application/problem+json
# application/rdap+json
# application/reputon+json
# application/scim+json
# application/vcard+json
# application/vnd.apache.thrift.json
# application/vnd.api+json
# application/vnd.bekitzur-stech+json
# application/vnd.collection+json
# application/vnd.collection.doc+json
# application/vnd.collection.next+json
# application/vnd.coreos.ignition+json
# application/vnd.document+json
# application/vnd.drive+json
# application/vnd.geo+json
# application/vnd.hal+json
# application/vnd.heroku+json
# application/vnd.hyperdrive+json
# application/vnd.ims.lis.v2.result+json
# application/vnd.ims.lti.v2.toolconsumerprofile+json
# application/vnd.ims.lti.v2.toolproxy+json
# application/vnd.ims.lti.v2.toolproxy.id+json
# application/vnd.ims.lti.v2.toolsettings+json
# application/vnd.ims.lti.v2.toolsettings.simple+json
# application/vnd.mason+json
# application/vnd.micro+json
# application/vnd.miele+json
# application/vnd.oftn.l10n+json
# application/vnd.oma.lwm2m+json
# application/vnd.oracle.resource+json
# application/vnd.pagerduty+json
# application/vnd.siren+json
# application/vnd.vel+json
# application/vnd.xacml+json
# model/gltf+json

Python 3.13 だと

ちなみに Python 3.13 のドキュメントを読むと mimetypes.guess_type()soft deprecated(警告は出ずに引き続き使える非推奨)になっていて,今後は mimetypes.guess_file_type() を使うことになりそう📝 覚えておこう〜

Deprecated since version 3.13: Passing a file path instead of URL is soft deprecated. Use guess_file_type() for this.

>>> import mimetypes
>>> mimetypes.guess_file_type('example.json')
('application/json', None)

docs.python.org

参考サイト

www.iana.org

AWS Step Functions でエラーハンドリングを実装する構成例

AWS Step Functions でタスクがエラーになったときに統一的なエラーハンドリング(エラー処理・リカバリ処理・通知処理など)が必要になることがある💡エラーハンドリングを実現する構成例をいくつか考えてみた👍

もちろん最終的には要件次第ではあって絶対にコレ❗️という答えはないと思う \( 'ω')/

案1: タスクごとに Catch する

まず最初に思い付くのはタスクごとに Catch する案だと思う.例えば以下のように AWS Lambda 関数(特に意味はなく Yay! という名前にした✌)を順番に3回呼び出す場合にタスクごとに Catch を実装して,エラーハンドリング用の処理 CustomErrorHandler を実行するイメージ👌

案1: ワークフロー例

実行すると期待通りになる❗️タスクごとに異なるエラーハンドリングを実装できて自由度は高いけど,統一的なエラーハンドリングを前提にすると,すべてのタスクに Catch を実装する必要があって AWS Step Functions ワークフローの複雑さは課題になると思う.

案1: 実行結果

案2: Parallel を使ってタスクをグループ化する

AWS Step Functions には複数のタスクをまとめてグループ化する直接的な機能はないけど,タスクを並列に実行するときに使う Parallel は,実はブランチ(レーン)は1以上で使えるという仕様になっている👌

docs.aws.amazon.com

よって Parallel で片側にタスクをまとめると「グループ化」のように実装できるという Tips がある💡

案2: ワークフロー例

実行すると期待通りになる❗️グループ化のために Parallel を使うのはちょっと裏技感があるけど,統一的なエラーハンドリングで十分な場合は AWS Step Functions ワークフローがシンプルになって良いと思う.

案2: 実行結果

案3: AWS Step Functions ワークフローをネストする

AWS Step Functions の「サービス統合」を使うと AWS Step Functions ワークフローをネストして,別の AWS Step Functions ワークフローを呼び出せる.

docs.aws.amazon.com

複数のタスクを AWS Step Functions ワークフロー(子ワークフロー)にまとめて「グループ化」のようにして,子ワークフローを呼び出す AWS Step Functions ワークフロー(親ワークフロー)ではエラーハンドリングを実装する👌

案3: 子ワークフロー例

案3: 親ワークフロー例

実行すると期待通りになる❗️エラーハンドリングのために管理する AWS Step Functions ワークフローが増えてしまうのはデメリットだけど,AWS Step Functions ワークフロー全体がさらに複雑化する予定があったりするなら拡張しやすいと思う.

案3: 実行結果

案4: その他

エラーハンドリングを AWS Step Functions ワークフローから独立させて実現する案を2つ考えてみた📝

Amazon EventBridge を使う

Amazon EventBridge で Step Functions Execution Status ChangeABORTED / TIMED_OUT / FAILED をトリガーして AWS Lambda 関数などのエラーハンドリングを実行できる💡

{
    "source": [
        "aws.states"
    ],
    "detail-type": [
        "Step Functions Execution Status Change"
    ],
    "detail": {
        "status": [
            "ABORTED",
            "TIMED_OUT",
            "FAILED"
        ],
        "stateMachineArn": [
            "arn:aws:states:ap-northeast-1:000000000000:stateMachine:xxxxx"
        ]
    }
}

docs.aws.amazon.com

Amazon CloudWatch Alarm を使う

AWS Step Functions ワークフローのメトリクス ExecutionsAborted / ExecutionsTimedOut / ExecutionsFailed を Amazon CloudWatch Alarm でトリガーして Amazon SNS トピック経由で AWS Lambda 関数などのエラーハンドリングを実行できる💡

docs.aws.amazon.com

ちなみに最近では AWS Lambda 関数を直接実行することもできる👌

aws.amazon.com

まとめ

適材適所に考えよう〜 \( 'ω')/

LocalStack で AWS サポートへの問い合わせを API で操作できる「AWS Support API」を試そう

AWS サポートへの問い合わせを API で操作できる「AWS Support API」は気になるけどビジネスプラン以上じゃないと使えなくて気軽に試せないじゃーん😇と悩んでいる人がいたら LocalStack をおすすめしたい❗️実は LocalStack(無料版)で一部の AWS Support API がサポートされている💡

(あくまで練習として)問い合わせし放題だぁぁぁ〜 \( 'ω')/

docs.aws.amazon.com

現在「5種類」の API がサポートされている📝

docs.localstack.cloud

LocalStack で AWS Support API を試す💪

今回は LocalStack AWS CLI(awslocal コマンド)で試す.ちなみに東京リージョンで AWS Support API を実行する場合はバージニア北部リージョン (us-east-1) を指定する必要がある点は注意すること🚨

docs.aws.amazon.com

👾 support describe-cases

awscli.amazonaws.com

最初は問い合わせ0件になっている👌

$ awslocal support describe-cases --region us-east-1 | jq .
{
  "cases": []
}

👾 support create-case

awscli.amazonaws.com

問い合わせできた👌

$ awslocal support create-case --region us-east-1 \
    --subject '[TEST CASE-Please ignore] LocalStack Support API テスト1' \
    --communication-body '東京リージョンの EC2(i-***)について質問です。' \
    --language ja | jq .
{
  "caseId": "case-12345678910-2020-4hJ2i1J38FbKAeh2"
}

$ awslocal support create-case --region us-east-1 \
    --subject '[TEST CASE-Please ignore] LocalStack Support API テスト2' \
    --communication-body '東京リージョンの EC2(i-***)について質問です。' \
    --language ja | jq .
{
  "caseId": "case-12345678910-2020-KMmj62hbE43e578a"
}

$ awslocal support describe-cases --region us-east-1 | jq .
{
  "cases": [
    {
      "caseId": "case-12345678910-2020-4hJ2i1J38FbKAeh2",
      "displayId": "foo_display_id",
      "subject": "[TEST CASE-Please ignore] LocalStack Support API テスト1",
      "status": "pending-customer-action",
      "submittedBy": "moto@moto.com",
      "timeCreated": "2024-09-27T05:25:30.764608",
      "recentCommunications": {
        "communications": [
          {
            "caseId": "case-12345678910-2020-4hJ2i1J38FbKAeh2",
            "body": "東京リージョンの EC2(i-***)について質問です。",
            "submittedBy": "moto@moto.com",
            "timeCreated": "2024-09-27T05:25:30.764612",
            "attachmentSet": [
              {
                "fileName": "support_file.txt"
              }
            ]
          }
        ],
        "nextToken": "foo_next_token"
      },
      "language": "ja"
    },
    {
      "caseId": "case-12345678910-2020-KMmj62hbE43e578a",
      "displayId": "foo_display_id",
      "subject": "[TEST CASE-Please ignore] LocalStack Support API テスト2",
      "status": "pending-customer-action",
      "submittedBy": "moto@moto.com",
      "timeCreated": "2024-09-27T05:25:52.411090",
      "recentCommunications": {
        "communications": [
          {
            "caseId": "case-12345678910-2020-KMmj62hbE43e578a",
            "body": "東京リージョンの EC2(i-***)について質問です。",
            "submittedBy": "moto@moto.com",
            "timeCreated": "2024-09-27T05:25:52.411096",
            "attachmentSet": [
              {
                "fileName": "support_file.txt"
              }
            ]
          }
        ],
        "nextToken": "foo_next_token"
      },
      "language": "ja"
    }
  ]
}

ちなみにドキュメントに以下のように書いてあって,今回は subject[TEST CASE-Please ignore] と付けてある📝

If you call the CreateCase operation to create test support cases, then we recommend that you include a subject line, such as TEST CASE-Please ignore. After you're done with your test support case, call the ResolveCase operation to resolve it.

👾 support resolve-case

awscli.amazonaws.com

問い合わせを解決できた👌

$ awslocal support resolve-case --region us-east-1 \
    --case-id case-12345678910-2020-4hJ2i1J38FbKAeh2 | jq .
{
  "initialCaseStatus": "reopened",
  "finalCaseStatus": "resolved"
}

$ awslocal support describe-cases --region us-east-1 | jq .
{
  "cases": [
    {
      "caseId": "case-12345678910-2020-KMmj62hbE43e578a",
      "displayId": "foo_display_id",
      "subject": "[TEST CASE-Please ignore] LocalStack Support API テスト2",
      "status": "reopened",
      "submittedBy": "moto@moto.com",
      "timeCreated": "2024-09-27T05:25:52.411090",
      "recentCommunications": {
        "communications": [
          {
            "caseId": "case-12345678910-2020-KMmj62hbE43e578a",
            "body": "東京リージョンの EC2(i-***)について質問です。",
            "submittedBy": "moto@moto.com",
            "timeCreated": "2024-09-27T05:25:52.411096",
            "attachmentSet": [
              {
                "fileName": "support_file.txt"
              }
            ]
          }
        ],
        "nextToken": "foo_next_token"
      },
      "language": "ja"
    }
  ]
}

$ awslocal support describe-cases --region us-east-1 \
    --include-resolved-cases | jq .
{
  "cases": [
    {
      "caseId": "case-12345678910-2020-4hJ2i1J38FbKAeh2",
      "displayId": "foo_display_id",
      "subject": "[TEST CASE-Please ignore] LocalStack Support API テスト1",
      "status": "unassigned",
      "submittedBy": "moto@moto.com",
      "timeCreated": "2024-09-27T05:25:30.764608",
      "recentCommunications": {
        "communications": [
          {
            "caseId": "case-12345678910-2020-4hJ2i1J38FbKAeh2",
            "body": "東京リージョンの EC2(i-***)について質問です。",
            "submittedBy": "moto@moto.com",
            "timeCreated": "2024-09-27T05:25:30.764612",
            "attachmentSet": [
              {
                "fileName": "support_file.txt"
              }
            ]
          }
        ],
        "nextToken": "foo_next_token"
      },
      "language": "ja"
    },
    {
      "caseId": "case-12345678910-2020-KMmj62hbE43e578a",
      "displayId": "foo_display_id",
      "subject": "[TEST CASE-Please ignore] LocalStack Support API テスト2",
      "status": "resolved",
      "submittedBy": "moto@moto.com",
      "timeCreated": "2024-09-27T05:25:52.411090",
      "recentCommunications": {
        "communications": [
          {
            "caseId": "case-12345678910-2020-KMmj62hbE43e578a",
            "body": "東京リージョンの EC2(i-***)について質問です。",
            "submittedBy": "moto@moto.com",
            "timeCreated": "2024-09-27T05:25:52.411096",
            "attachmentSet": [
              {
                "fileName": "support_file.txt"
              }
            ]
          }
        ],
        "nextToken": "foo_next_token"
      },
      "language": "ja"
    }
  ]
}

👾 support describe-services

未実装🙅‍♂️

$ awslocal support describe-services --region us-east-1
An error occurred (InternalFailure) when calling the DescribeServices operation: The describe_services action has not been implemented

👾 describe-severity-levels

未実装🙅‍♂️

$ awslocal support describe-severity-levels --region us-east-1
An error occurred (InternalFailure) when calling the DescribeSeverityLevels operation: The describe_severity_levels action has not been implemented

まとめ

LocalStack(無料版)で AWS Support API を試せるよ❗️という紹介でした \( 'ω')/

実際に問い合わせるときはガイドラインも確認しましょう〜📝

aws.amazon.com

Lambda@Edge と CloudFront Functions に入門できるワークショップ「Handling Rewrites and Redirects using Edge Functions」

Lambda@Edge と CloudFront Functions を使って HTTP リクエストのリダイレクトとリライトを体験するワークショップ「Handling Rewrites and Redirects using Edge Functions」を実施してみた❗️

Lambda@Edge と CloudFront Functions に入門したいな〜という人に特におすすめ💡アーキテクチャ図も多く載っていてイメージしやすく構成されているのも良かった〜 \( 'ω')/

catalog.us-east-1.prod.workshops.aws

目次

大きく2部構成になっていて,Module 1 では Lambda@Edge を使ってキャッシュ可能なリダイレクトとリライトを体験して,Module 2 では CloudFront Functions を使って動的なリダイレクトとリライトを体験する📝

  • Getting Started
  • Overview
  • Module 1 - Cacheable Use Cases
    • URI based Redirects
    • Geo Location Redirects
    • Device Redirects
    • URI based Rewrites
  • Module 2 - Dynamic Use Cases
    • Dynamic Geo Location Redirects
    • Cookie Based Redirect
    • Bot Signatures Based Rewrite
  • Conclusion

所要時間はワークショップの冒頭に以下のように書いてあって妥当だと思う👌僕は計3ポモドーロ (90 minutes) で最後のクリーンアップまで完了できた.

This workshop takes about 90-120 minutes to complete all the labs covered.

Lambda@Edge と CloudFront Functions の比較

Lambda@Edge と CloudFront Functions の比較はワークショップ (Overview) で出てくるけど,ワークショップ環境を AWS CloudFormation で構築する待ち時間などに以下のドキュメントを軽く読んでおくと理解しやすくなって良いと思う💪

docs.aws.amazon.com

Module 1

Module 1 では Lambda@Edge を使って4種類のリダイレクトとリライトを体験する.Lambda@Edge のコード (Python 3.9) はコピペする.シンプルな実装で理解しやすいし,AWS Lambda 関数の実装に慣れているから特に詰まるところもなかった💡

Lambda@Edge に Amazon CloudFront のトリガーを設定して,AWS CloudShell から curl コマンドを実行しながらリダイレクト・リライトの挙動確認や X-Cache ヘッダーの結果を確認していく.

また2番目の Geo Location Redirects では Amazon CloudFront で CloudFront-Viewer-Country ヘッダーを追加したりする.勉強になる👌

docs.aws.amazon.com

そして4番目に試す URI based Rewrites もプロダクション運用でニーズがありそうで実践的だな〜と思った💡

Module 1 の実施ログ

最終的に4種類の Lambda@Edge を追加した.

Module 1 で追加した Lambda@Edge

Module 2

Module 2 では CloudFront Functions を使って3種類のリダイレクトとリライトを体験する.CloudFront Functions の言語は JavaScript なので TypeScript で実装してデプロイできたら良さそうだな〜なんて考えながら進めていた👀

3番目に試す Bot Signatures Based Rewrite は AWS WAF と組み合わせてボット対応を体験する.awswaf:managed:aws:bot-control:bot:category:social_media ラベルを使ったりして,実践的で良かった🛡️

Module 2 で設定した AWS WAF Web ACLs

最終的に3種類の CloudFront Functions を追加した.

Module 2 で追加した CloudFront Functions

まとめ

Lambda@Edge と CloudFront Functions に入門できるワークショップ「Handling Rewrites and Redirects using Edge Functions」を実施してみた❗️面白かった〜 \( 'ω')/

ちなみに朝活でワークショップを実施した🌅朝活だと頭もスッキリしてて集中して取り組めるからおすすめ.

ワークショップは AWS Workshops で探せるよ〜

awsworkshop.io

Powertools for AWS Lambda (Python) で Cognito User Pools の トークン生成前トリガーを実装しよう

Amazon Cognito User Pools には AWS Lambda 関数を使って認証フローをカスタマイズできる「Lambda トリガー」という機能がある💡トークン生成前トリガー (Pre token generation Lambda trigger) を使うと,認証時に発行されるトークン (ID Token / Access Token) をカスタマイズできる👌

docs.aws.amazon.com

docs.aws.amazon.com

ちなみにトークン (Access Token) は2023年12月のリリースでカスタマイズできるようになっていたりする💡

aws.amazon.com

Powertools for AWS Lambda (Python)

Powertools for AWS Lambda (Python) の Event Source Data Classes を使うと,シンプルに Lambda トリガーを実装できるようになる.今回は PreTokenGenerationTriggerEvent を使ってトークン (ID Token) のペイロードにクレームを追加/削除するトークン生成前トリガーを実装する❗️

docs.powertools.aws.dev

準備

まずは Amazon Cognito User Pools を作っておく❗️今回はサクッと認証を試すためにアプリケーションクライアントの認証フローとして ALLOW_USER_PASSWORD_AUTH も有効化する.あとカスタム属性として X アカウント名を表す custom:x を追加しておく.その他はデフォルト設定で良いかなと思う👌

トークンを取得する(トークン生成前トリガーなし)

まずは「トークン生成前トリガーなし」でトークン (ID Token) を取得する.AWS CLI で aws cognito-idp initiate-auth コマンドを実行する💡

awscli.amazonaws.com

$ CLIENT_ID=xxx
$ EMAIL=xxx
$ PASSWORD=xxx

$ aws cognito-idp initiate-auth \
  --client-id ${CLIENT_ID} \
  --auth-flow USER_PASSWORD_AUTH \
  --auth-parameters USERNAME=${EMAIL},PASSWORD=${PASSWORD} | jq .
{
  "ChallengeParameters": {},
  "AuthenticationResult": {
    "AccessToken": "xxx",
    "ExpiresIn": 3600,
    "TokenType": "Bearer",
    "RefreshToken": "xxx",
    "IdToken": "xxx"
  }
}

取得した IdToken の値から JWT Payload を抜き出すと以下のようになっていた❗️追加したカスタム属性 custom:x にも @kakakakakku という値が入っている👌

{
  "sub": "f7a4da98-a041-70e4-7954-8bbc5d3ca764",
  "email_verified": true,
  "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_xxxxxxxxx",
  "cognito:username": "f7a4da98-a041-70e4-7954-8bbc5d3ca764",
  "custom:x": "@kakakakakku",
  "origin_jti": "85ed5e84-1346-41fa-8269-1e5aa0a35bab",
  "aud": "xxx",
  "event_id": "525e5170-54f5-450c-a053-258fde4ea50f",
  "token_use": "id",
  "auth_time": 1726876453,
  "exp": 1726880053,
  "iat": 1726876453,
  "jti": "db5441ca-8710-4fe7-b0cc-4b31f6fc77e0",
  "email": "y.yoshida22@gmail.com"
}

トークン (ID Token) の詳細は以下のドキュメントに載っている📝

docs.aws.amazon.com

トークン生成前トリガー

次に AWS Lambda 関数を実装してトークン生成前トリガーを設定する.

AWS Lambda 関数の実装は Powertools for AWS Lambda (Python) の Event Source Data Classes を使って,my_key クレームの追加と custom:x クレームの削除をする.実装自体は簡単にできる👌

👾 app.py(サンプル1)

シンプルに実装する場合

from aws_lambda_powertools.utilities.data_classes.cognito_user_pool_event import PreTokenGenerationTriggerEvent


def lambda_handler(event, context):
    event: PreTokenGenerationTriggerEvent = PreTokenGenerationTriggerEvent(event)
    event.response.claims_override_details.claims_to_add_or_override = {'my_key': 'my_value'}
    event.response.claims_override_details.claims_to_suppress = ['custom:x']

    return event.raw_event

👾 app.py(サンプル2)

ハンドラとロジックを分離してテストをしやすく実装する場合

import json

from aws_lambda_powertools.utilities.data_classes.cognito_user_pool_event import PreTokenGenerationTriggerEvent


def main(event):
    event: PreTokenGenerationTriggerEvent = PreTokenGenerationTriggerEvent(event)
    event.response.claims_override_details.claims_to_add_or_override = {'my_key': 'my_value'}
    event.response.claims_override_details.claims_to_suppress = ['custom:x']

    return event.raw_event


def lambda_handler(event, context):
    return main(event)


if __name__ == '__main__':
    with open('./events/event.json', 'r') as f:
        event = json.load(f)
    print(main(event))

トークンを取得する(トークン生成前トリガーあり)

もう一度 AWS CLI で aws cognito-idp initiate-auth コマンドを実行して,IdToken の値から JWT Payload を抜き出すと以下のようになっていた❗️期待通りに my_key が追加されていて,カスタム属性 custom:x は削除されていた👌

{
  "sub": "f7a4da98-a041-70e4-7954-8bbc5d3ca764",
  "email_verified": true,
  "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_xxxxxxxxx",
  "cognito:username": "f7a4da98-a041-70e4-7954-8bbc5d3ca764",
  "origin_jti": "888ee4b8-4c3b-4a78-a34c-7e5f04c3788c",
  "aud": "xxx",
  "event_id": "bf25e9fe-beb5-4ad2-b1c7-188eda16ea75",
  "token_use": "id",
  "auth_time": 1726876789,
  "my_key": "my_value",
  "exp": 1726880389,
  "iat": 1726876789,
  "jti": "f75d7eaa-c826-4c40-9951-6dbe8bcc100a",
  "email": "y.yoshida22@gmail.com"
}

まとめ

Powertools for AWS Lambda (Python) の Event Source Data Classes を使って Amazon Cognito User Pools のトークン生成前トリガーをシンプルに実装するサンプルの紹介でした〜 \( 'ω')/