概要
AWS Lambda Powertools Python 1 を使うとAPI Gatewayのリソースマッピングが直感的に記述できます。
また、dictなりlistなりをreturnしておけば良い感じのJSONテキストにしてくれる等、とても便利です。
このレスポンスをJSONに変換してくれる部分で少し困ったので、対応した内容を記録します。
準備
AWS Lambda Powertools をインストールします
$ pip install aws-lambda-powertools
サンプルハンドラーコード
リクエスト GET /info
に対して、name
と created
要素を返すシンプルな内容です。
問題点に特化したサンプルですが、リクエストと処理が一目でわかる仕組みになっています。
from datetime import datetime
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
app = APIGatewayRestResolver()
def lambda_handler(event, context):
return app.resolve(event, context)
@app.get('/info')
def get_info():
return {
'name': 'myname',
'created': datetime.now()
}
テスト用のコード
Contextパラメータを作る部分がちょっと面倒に見えますが、API Gatewayのリクエスト最小eventパラメータで上記のサンプルコードを実行し、レスポンスを print する内容です。
デプロイなしで手元で確認できます。
from handler import lambda_handler
from dataclasses import dataclass
@dataclass
class Context:
function_name: str = 'handler.main'
memory_limit_in_mb: int = 128
invoked_function_arn: str = 'arn:aws:lambda:ap-northeast-1:123456789012:function:myFunction'
aws_request_id: str = '11111111-2222-3333-eeee-ffffffffffff'
event = {
'path': '/info',
'httpMethod': 'GET'
}
print(lambda_handler(event, Context()))
実行結果
datetimeはJSONシリアライズできないので例外が発生します。
TypeError: Object of type datetime is not JSON serializable
$ python test.py
Traceback (most recent call last):
File "/home/shinsaka/ws/awspowertools/test.py", line 16, in <module>
print(lambda_handler(event, Context()))
File "/home/shinsaka/ws/awspowertools/handler.py", line 7, in lambda_handler
return app.resolve(event, context)
File "/home/shinsaka/ws/awspowertools/.venv/lib/python3.9/site-packages/aws_lambda_powertools/event_handler/api_gateway.py", line 509, in resolve
return self._resolve().build(self.current_event, self._cors)
File "/home/shinsaka/ws/awspowertools/.venv/lib/python3.9/site-packages/aws_lambda_powertools/event_handler/api_gateway.py", line 571, in _resolve
return self._call_route(route, match_results.groupdict()) # pass fn args
File "/home/shinsaka/ws/awspowertools/.venv/lib/python3.9/site-packages/aws_lambda_powertools/event_handler/api_gateway.py", line 625, in _call_route
return ResponseBuilder(self._to_response(route.func(**args)), route)
File "/home/shinsaka/ws/awspowertools/.venv/lib/python3.9/site-packages/aws_lambda_powertools/event_handler/api_gateway.py", line 701, in _to_response
body=self._json_dump(result),
File "/home/shinsaka/ws/awspowertools/.venv/lib/python3.9/site-packages/aws_lambda_powertools/event_handler/api_gateway.py", line 705, in _json_dump
return self._serializer(obj)
File "/home/shinsaka/.pyenv/versions/3.9.12/lib/python3.9/json/__init__.py", line 234, in dumps
return cls(
File "/home/shinsaka/.pyenv/versions/3.9.12/lib/python3.9/json/encoder.py", line 199, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/home/shinsaka/.pyenv/versions/3.9.12/lib/python3.9/json/encoder.py", line 257, in iterencode
return _iterencode(o, 0)
File "/home/shinsaka/ws/awspowertools/.venv/lib/python3.9/site-packages/aws_lambda_powertools/shared/json_encoder.py", line 16, in default
return super().default(obj)
File "/home/shinsaka/.pyenv/versions/3.9.12/lib/python3.9/json/encoder.py", line 179, in default
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type datetime is not JSON serializable
修正
カスタムシリアライザーを実装
date
または datetime
については isoformat()
メソッドの結果を返すように実装しました。
(参考: json.JSONEncoder 2)
import json
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
from datetime import date, datetime
if isinstance(obj, (date, datetime)):
return obj.isoformat()
return json.JSONEncoder.default(self, obj)
def custom_serializer(obj) -> str:
return json.dumps(obj, cls=CustomEncoder)
ハンドラーコードを修正
カスタムシリアライザーをimportして APIGatewayRestResolver
のserializerパラメータへ指定します。
from datetime import datetime
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from utils import custom_serializer # 追記
app = APIGatewayRestResolver(serializer=custom_serializer) # serializerを指定
def lambda_handler(event, context):
return app.resolve(event, context)
@app.get('/info')
def get_info():
return {
'name': 'myname',
'created': datetime.now()
}
修正後の実行結果
問題のdatetimeオブジェクトは isoformat() 結果の文字列となり、正常にレスポンスできました。
$ python test.py
{'statusCode': 200, 'headers': {'Content-Type': 'application/json'}, 'body': '{"name": "myname", "created": "2022-09-15T13:24:40.348289"}', 'isBase64Encoded': False}