LoginSignup
1
0

More than 1 year has passed since last update.

[AWS Lambda Powertools Python]API GatewayレスポンスのJSONシリアライズエラーに対応する

Last updated at Posted at 2022-09-15

概要

AWS Lambda Powertools Python 1 を使うとAPI Gatewayのリソースマッピングが直感的に記述できます。
また、dictなりlistなりをreturnしておけば良い感じのJSONテキストにしてくれる等、とても便利です。

このレスポンスをJSONに変換してくれる部分で少し困ったので、対応した内容を記録します。

準備

AWS Lambda Powertools をインストールします

$ pip install aws-lambda-powertools

サンプルハンドラーコード

リクエスト GET /info に対して、namecreated 要素を返すシンプルな内容です。
問題点に特化したサンプルですが、リクエストと処理が一目でわかる仕組みになっています。

handler.py
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 する内容です。
デプロイなしで手元で確認できます。

test.py
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

utils.py
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パラメータへ指定します。

handler.py
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}
  1. AWS Lambda Powertools Python

  2. json.JSONEncoder

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0