0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

デジタルキューブグループAdvent Calendar 2024

Day 5

lambda製のapiにおけるpydanticでのvalidation

Last updated at Posted at 2024-12-19

この記事はデジタルキューブグループ エンジニアチームアドベントカレンダー2024 12月5日の記事です。

目的

apigateway + lambdaでバックエンドの開発をする際に、API リクエストのバリデーションにpowertools for aws lambdaを使っている

この構成でバリデーションが正しく動いているか確認する必要が出てきたため、本記事のような検証を行った

環境

  • python(3.11.x)
  • pydantic(v2.x)
    • powertools for aws lambdaの一部機能に pydantic が使われているが、pydanticが部分的にしか使えないので大元のパッケージも利用する
  • powertools for aws lambda(v3.x)

バリデーションを行う際のコード

lambda本体側は以下の感じ

from aws_lambda_powertools.utilities.parser import parse, ValidationError
from models import Hoge # 各自pydanticなクラスを定義したものをimportする

def handler(event, context):
    try:
        request_parameter = parse(event["body"], Hoge)
    except ValidationError as e:
        # エラー時にlambdaが返すレスポンスやエラーログ出力などの処理

    # なにか後続の処理

任意のキーと必須のキー混合

from typing import Any
from pydantic import RootModel, model_validator

class Hoge(RootModel[dict[str, Any]]):
   # 基本的には全てのキー/バリューを許可、一部必須フィールドをチェックするためのvalidatorを定義
   @model_validator(mode="before")
   def required_field_validation(self: dict[str, Any]):
       keys = self.keys()
       
       # 必須キーのキー名と型を定義
       required = {
           "key1": [float],
           "key2": [str, int],
       }
       
       error_messages = []
       
       for key_name, types in required.items():
           if key_name not in keys:
               error_messages.append(f"{key_name} is required")
           elif type(self[key_name]) not in types:
               error_messages.append(f"{key_name} must be {' or '.join([str(t.__name__) for t in types])}")

       if error_messages:
           raise ValueError("\n".join(error_messages))

       return self

任意のキーすべてを受け入れる

apiのpostで以下のようなbodyを受け入れる場合

{
    "hoge": "hoge",
    "fuga": 1
}

以下のように定義

from typing import Any
from pydantic import RootModel

class Hoge(RootModel[dict[str, Any]]):
    pass

配列を受け入れる

apiのpostで以下のようなbodyを受け入れる場合

[
    {
        "hoge": "hoge",
        "fuga": 1
    },
    {
        "hoge": "hoge",
        "fuga": 2
    },
    {
        "hoge": "hoge",
        "fuga": 3
    },
]

以下のように定義

from typing import Any
from pydantic import RootModel

class Hoge(RootModel[list[dict[str, Any]]]):
    pass

任意のdictでないならたとえば以下でいいはず

from typing import Any
from pydantic import RootModel, BaseModel

class HogeDict(BaseModel):
    hoge: str
    fuga: int

class Hoge(RootModel[list[HogeDict]]):
    pass

その他

こういうのもよく使うケースなのでメモ的に残しておく
動け たぶん動く)

from pydantic import RootModel, BaseModel

class HogeProps(BaseModel):
    hoge: str
    fuga: int

class Hoge(RootModel[HogeProps]):
    props: HogeProps
    name: str
    age: int

想定されるリクエストボディ

{
    "name": "name",
    "age": 111,
    "props": {
        "hoge": "hoge",
        "fuga": 1
    }
}
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?