概要
FastAPIではPydantic
というライブラリがあり、データのバリデーションを簡単に行うことができます。今回は、そのPydantic
を利用している実装中にnone is not an allowed value
というエラーに遭遇したのでその解決方法を紹介します。
レスポンスが成功するサンプルコード
from typing import Any, Optional, Union
from pydantic import BaseModel
class ResultResponse(BaseModel):
request_id: Optional[str] = None
status: int = ''
msg: str = ''
info: Optional[Union[int, str]]
flag: Optional[Any] = None
print(f"ResultResponse: {ResultResponse}, type: {type(ResultResponse)}")
# ResultResponse: <class '__main__.ResultResponse'>, type: <class 'pydantic.main.ModelMetaclass'>
response_request_id = '001'
response_status = 1234
response_message = 'これはメッセージです'
response_info = None
response_flag = False
response_data=ResultResponse( \
request_id=response_request_id, \
status=response_status, \
msg=response_message, \
info=response_info, \
flag=response_flag
)
print(f"response_data: {response_data}, type: {type(response_data)}")
# response_data: request_id='001' status=1234 msg='これはメッセージです' info=None flag=False, type: <class '__main__.ResultResponse'>
補足:pydantic
モデルとBaseModel
クラス
FastAPIでは、APIのリクエストデータを自動的にPydantic
モデルとして解釈し、バリデーションを行います。また、レスポンスデータもPydantic
モデルとして定義することができ、FastAPIは自動的にデータをシリアライズしてJSON形式で返します。
BaseModel
クラスはpydantic
が提供する特別なクラスであり、データモデルを定義する際に使用します。BaseModel
を使用することで、データが指定したルールに適合しているかどうかを簡単にチェックできます。不適切なデータが与えられた場合、自動的にValidationError
が発生します。
ResultResponse
のtype
をprintしていますが、BaseModel
クラスを継承したクラスを定義する際にそのクラスが pydantic.main.ModelMetaclass
を持つようになります。このメタクラスは、クラス定義時にフィールドの型ヒントやバリデーションルールを解析し、データモデルとしての振る舞いを付与します。
補足:データモデルでフィールド定義
上記のコードでは、pydantic
ライブラリを使用して ResultResponse
というデータモデルを定義していますが、フィールド定義ではint
やstr
などの型を指定しています。これにより、データの整合性を保証し、バリデーションエラーを事前に検知することができるわけです。
サンプルコードでは、ResultResponse
データモデルで以下のフィールド定義をしています。
request_id
フィールド: 文字列型の値を持ち、デフォルト値としてNone
を指定。
status
フィールド: 整数型の値を持ち、デフォルト値として空の文字列''
を指定。
msg
フィールド: 文字列型の値を持ち、デフォルト値として空の文字列''
を指定。
Union
とは?
info
フィールドでは、Optional[Union[int, str]]
と定義しています。この場合、数型の値、文字列型の値、またはNone
のいずれかを受け入れることができます(デフォルト値は指定していません)。
Any
とは?
flag
フィールドでは、Optional[Any]
型を定義しています。Any
型は任意の型を表しますので、任意の型の値もしくは None
を受け入れることができます。デフォルト値としてNone
が指定しています。
この状態であれば、response_data
インスタンスの中身は正常に出力されます。
ただし、以下の場合だと失敗しました。
バリデーションエラーになるサンプルコード
from typing import Any, Optional, Union
from pydantic import BaseModel
class ResultResponse(BaseModel):
request_id: str = '' # デフォルト値を`''`に変更。
status: int = ''
msg: str = ''
info: Optional[Union[int, str]]
flag: Optional[Any] = None
print(f"ResultResponse: {ResultResponse}, type: {type(ResultResponse)}")
# ResultResponse: <class '__main__.ResultResponse'>, type: <class 'pydantic.main.ModelMetaclass'>
response_request_id = None # Noneに変更
response_status = 1234
response_message = 'これはメッセージです'
response_info = None
response_flag = False
response_data=ResultResponse( \
request_id=response_request_id, \
status=response_status, \
msg=response_message, \
info=response_info, \
flag=response_flag
)
print(f"response_data: {response_data}, type: {type(response_data)}")
# none is not an allowed value (type=type_error.none.not_allowed)
エラー内容全文は以下の通りです。
Traceback (most recent call last):
File "/Users/xxxx/Projects/TEST/test-pydantic.py", line 19, in <module>
response_data=ResultResponse( \
File "pydantic/main.py", line 342, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for ResultResponse
request_id
none is not an allowed value (type=type_error.none.not_allowed)
変更した点は、データモデルResultResponse
でrequest_id
のデフォルト値をrequest_id: str = ''
に変更し、response_request_id = None
というようにNone
を渡すようにした2点。
str = ''
とOptional[str] = None
の違い
なぜこれでnone is not an allowed value
というエラーになったかというと、request_id: str = ''
の場合、None
を許容しないからです。ResultResponse
クラスの request_id
フィールドがデフォルトで''
(空の文字列)で初期化されているため、None
が代入された場合でも、空の文字列がデフォルト値として設定されます。変数宣言でデフォルト値として空の文字列''
指定すると、その変数は常に空の文字列を持つことになるからです。
Optional
は、Pythonのtyping
モジュールに含まれる型ヒントで、None
を許容します(ということを知らなかったので少し苦戦した)。
request_id: str = ''
の場合、デフォルト値として空の文字列''
が指定されているため、初期化時に明示的に値を指定しなかった場合、request_id
は空の文字列となってしまいます。これがnone is not an allowed value
というエラーが出た理由だったんですね。