Models
API Documentation
Pydanticでスキーマを定義する主な方法の1つは、モデルを使用することです。モデルは、pydantic.BaseModel
から継承し、フィールドを注釈付き属性として定義する単純なクラスです。
モデルは、Cなどの言語の構造体に似ていると考えることも、APIの単一エンドポイントの要件と考えることもできます。
モデルはPythonのデータクラスと多くの類似点を共有していますが、検証、シリアライゼーション、JSONスキーマ生成に関連する特定のワークフローを合理化するために、いくつかの微妙ではありますが重要な違いを持って設計されています。これについての詳細は、ドキュメントのDataclassesセクションを参照してください。
信頼できないデータをモデルに渡すことができますが、パースと検証の後、Pydanticは、結果のモデルインスタンスのフィールドがモデルで定義されたフィールドタイプに準拠することを保証します。
Validation — a deliberate misnomer
TL;DR¶
"バリデーション"という用語は、指定されたタイプと制約に従うモデル(または他のタイプ)をインスタンス化するプロセスを指すために使用します。Pydanticでよく知られているこのタスクは、口語では"バリデーション"として最も広く認識されていますが、他の文脈では"バリデーション"という用語はより限定的な可能性があります。
The long version¶
詳しい説明¶
"バリデーション"という用語をめぐる潜在的な混乱は、厳密に言えば、Pydanticの主な焦点が辞書の"バリデーション"の定義と正確に一致していないという事実からきています。
バリデーション¶
名詞 何かの有効性または正確さをチェックまたは証明する行為。
Pydanticでは、"バリデーション'という用語は、指定された型と制約に従うモデル(または他の型)をインスタンス化するプロセスを指します。Pydanticは、入力データではなく、出力の型と制約を保証します。
この区別は、データがモデルインスタンスに正常にパースできない場合にPydanticのValidationError
が発生することを考えると明らかになります。
この区別は最初は微妙に見えるかもしれないが、実際には重要です。 場合によっては、"バリデーション"は単なるモデルの作成にとどまらず、データのコピーや強制的な型変換を含むこともあります。 これには、元の入力データを変更せずに新しい型への強制的な型変換を実行するために、コンストラクタに渡された引数をコピーすることが含まれます。使用方法への影響の詳細については、以下のData ConversionおよびAttribute Copiesのセクションを参照してください。
本質的に、Pydanticの主な目標は、結果として得られる構造の後処理("バリデーション"と呼ばれる)が、適用された型ヒントに正確に適合することを保証することです。このプロセスの口語として"バリデーション"が広く採用されていることを考慮して、私たちのドキュメントでは一貫して"バリデーション"を使用します。
"パース"と"バリデーション"という用語は以前は互換的に使用されていましたが、今後は"バリデーション"のみを使用します。 "パース"は特にJSON parsingに関連する議論のために予約されています。
Basic model usage¶
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = 'Jane Doe'
この例では、User
は2つのフィールドを持つモデルです。
id
, which is an integer and is required-
name
, which is a string and is not required (it has a default value). -
id
は整数で、必須です。 name
は文字列であり、必須ではありません(デフォルト値があります)。
user = User(id='123')
この例では、user
はUser
のインスタンスです。
オブジェクトを初期化すると、すべてのパースと検証が実行されます。
ValidationError
が発生しない場合は、結果のモデルインスタンスが有効であることがわかります。
assert user.id == 123
assert isinstance(user.id, int)
# Note that '123' was coerced to an int and its value is 123
pydanticの強制型変換ロジックの詳細については、Data Conversionを参照してください。
モデルのフィールドは、"user"オブジェクトの通常の属性としてアクセスできます。
文字列'123'
はフィールド型に従ってintに変換されました。
assert user.name == 'Jane Doe'
user
の初期化時にname
が設定されていなかったため、デフォルト値が設定されています。
assert user.model_fields_set == {'id'}
このフィールドは、ユーザーが初期化されたときに指定されたフィールドです。
assert user.model_dump() == {'id': 123, 'name': 'Jane Doe'}
.model_dump()
またはdict(user)
のどちらかがフィールドのdictを提供しますが、.model_dump()
は他の多くのものを取ることができます。(dict(user)
はネストされたモデルをディクテーションに再帰的に変換しませんが、.model_dump()
は変換します。)
user.id = 321
assert user.id == 321
デフォルトでは、モデルは可変であり、フィールド値は属性の割り当てによって変更できます。
Model methods and properties¶
上の例は、モデルができることの氷山の一角を示しているにすぎません。 モデルには、次のメソッドと属性があります。
model_computed_fields
: このモデルインスタンスの計算済みフィールドの辞書です。
model_construct()
: 検証を実行せずにモデルを作成するためのクラスメソッドです。Creating models without validationを参照してください。
model_copy()
: モデルのコピー(デフォルトではシャローコピー)を返します。Serializationを参照してください。
model_dump_json()
:model_dump()
のJSON文字列表現を返します。Serializationを参照してください。
model_extra
: 検証中に追加フィールドセットを取得します。
model_fields_set
: モデルインスタンスが初期化されたときに設定されたフィールドのセットです。
model_json_schema()
: JSONスキーマとしてモデルを表すJSON化可能な辞書を返します。JSON Schemaを参照してください。
model_parameterized_name()
: ジェネリッククラスのパラメータ化のクラス名を計算します。
model_post_init()
:モデルが初期化された後に追加の初期化を実行します。
model_rebuild()
: 再帰的なジェネリックモデルの構築もサポートするモデルスキーマを再構築します。Rebuild model schemaを参照してください。
model_validate()
: 任意のオブジェクトをモデルにロードするためのユーティリティです。Helper functionsを参照してください。
model_validate_json()
: 指定されたJSONデータをPydanticモデルに対して検証するためのユーティリティです。Helper functionsを参照してください。
Note
メソッドと属性の完全なリストを含むクラス定義については、BaseModel
を参照してください。
Tip
Pydantic V1からの変更の詳細については、Migration GuideのChanges to pydantic.BaseModel
を参照してください。
Nested models¶
より複雑な階層データ構造は、モデル自体をアノテーションの型として使用して定義することができます。
from typing import List, Optional
from pydantic import BaseModel
class Foo(BaseModel):
count: int
size: Optional[float] = None
class Bar(BaseModel):
apple: str = 'x'
banana: str = 'y'
class Spam(BaseModel):
foo: Foo
bars: List[Bar]
m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
print(m)
"""
foo=Foo(count=4, size=None) bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y')]
"""
print(m.model_dump())
"""
{
'foo': {'count': 4, 'size': None},
'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y'}],
}
"""
自己参照モデルについては、postponed annotationsを参照してください。
Note
モデルを定義するときは、フィールド名とそのタイプ、以前に定義されたモデル、または読み込まれたライブラリとの間の名前の衝突に注意してください。
たとえば、次の例では検証エラーが発生します。
from typing import Optional
from pydantic import BaseModel
class Boo(BaseModel):
int: Optional[int] = None
m = Boo(int=123) # errors
int
フィールドはデフォルト値None
に設定されており、そのタイプとまったく同じ名前を持っているため、両方ともNone
と解釈され、エラーが発生します。
Rebuild model schema¶
モデルスキーマはmodel_rebuild()
を使用して再構築できます。これは再帰的なジェネリックモデルを構築するのに便利です。
from pydantic import BaseModel, PydanticUserError
class Foo(BaseModel):
x: 'Bar'
try:
Foo.model_json_schema()
except PydanticUserError as e:
print(e)
"""
`Foo` is not fully defined; you should define `Bar`, then call `Foo.model_rebuild()`.
For further information visit https://errors.pydantic.dev/2/u/class-not-fully-defined
"""
class Bar(BaseModel):
pass
Foo.model_rebuild()
print(Foo.model_json_schema())
"""
{
'$defs': {'Bar': {'properties': {}, 'title': 'Bar', 'type': 'object'}},
'properties': {'x': {'$ref': '#/$defs/Bar'}},
'required': ['x'],
'title': 'Foo',
'type': 'object',
}
"""
Pydanticは、いつこれが必要なのかを自動的に判断しようとし、それが行われなかった場合はエラーになりますが、再帰的なモデルやジェネリックスを扱う場合は、事前にmodel_rebuild()
を呼び出した方がよいでしょう。
V2では、V1のupdate_forward_refs()
がmodel_rebuild()
に置き換えられました。新しい動作には若干の違いがあります。
最大の変更点は、最も外側のモデルでmodel_rebuild()
を呼び出すと、モデル全体(ネストされたモデルとすべて)の検証に使用されるコアスキーマが構築されるため、model_rebuild()
が呼び出される前に、すべてのレベルのすべての型が準備されている必要があることです。
Arbitrary class instances¶
(以前は"ORM Mode"/from_orm
と呼ばれていました。)
Pydanticモデルは、モデルフィールド名に対応するインスタンス属性を読み取ることによって、任意のクラスインスタンスから作成することもできます。この機能の一般的なアプリケーションの1つは、オブジェクトリレーショナルマッピング(ORM)との統合にあります。
これを行うには、config属性model_config['from_attributes']=True
を設定します。
詳細については、Model ConfigおよびConfigDictを参照してください。
ここでの例ではSQLAlchemyを使用していますが、どのORMに対しても同じアプローチが有効です。
from typing import List
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.orm import declarative_base
from typing_extensions import Annotated
from pydantic import BaseModel, ConfigDict, StringConstraints
Base = declarative_base()
class CompanyOrm(Base):
__tablename__ = 'companies'
id = Column(Integer, primary_key=True, nullable=False)
public_key = Column(String(20), index=True, nullable=False, unique=True)
name = Column(String(63), unique=True)
domains = Column(ARRAY(String(255)))
class CompanyModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
public_key: Annotated[str, StringConstraints(max_length=20)]
name: Annotated[str, StringConstraints(max_length=63)]
domains: List[Annotated[str, StringConstraints(max_length=255)]]
co_orm = CompanyOrm(
id=123,
public_key='foobar',
name='Testing',
domains=['example.com', 'foobar.com'],
)
print(co_orm)
#> <__main__.CompanyOrm object at 0x0123456789ab>
co_model = CompanyModel.model_validate(co_orm)
print(co_model)
"""
id=123 public_key='foobar' name='Testing' domains=['example.com', 'foobar.com']
"""
Reserved names¶
予約されたSQLAlchemyフィールドの後にColumn
という名前を付けたい場合があります。その場合はField
という別名が便利です。
import typing
import sqlalchemy as sa
from sqlalchemy.orm import declarative_base
from pydantic import BaseModel, ConfigDict, Field
class MyModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
metadata: typing.Dict[str, str] = Field(alias='metadata_')
Base = declarative_base()
class SQLModel(Base):
__tablename__ = 'my_table'
id = sa.Column('id', sa.Integer, primary_key=True)
# 'metadata' is reserved by SQLAlchemy, hence the '_'
metadata_ = sa.Column('metadata', sa.JSON)
sql_model = SQLModel(metadata_={'key': 'val'}, id=1)
pydantic_model = MyModel.model_validate(sql_model)
print(pydantic_model.model_dump())
#> {'metadata': {'key': 'val'}}
print(pydantic_model.model_dump(by_alias=True))
#> {'metadata_': {'key': 'val'}}
Note
上記の例が機能するのは、エイリアスがフィールド入力のフィールド名よりも優先されるためです。SQLModel
のmetadata
属性にアクセスするとValidationError
が発生します。
Nested attributes¶
属性を使用してモデルをパースする場合、モデルインスタンスは、必要に応じて最上位の属性と下位にネストされた属性の両方から作成されます。
この原理を示す例を次に示します。
from typing import List
from pydantic import BaseModel, ConfigDict
class PetCls:
def __init__(self, *, name: str, species: str):
self.name = name
self.species = species
class PersonCls:
def __init__(self, *, name: str, age: float = None, pets: List[PetCls]):
self.name = name
self.age = age
self.pets = pets
class Pet(BaseModel):
model_config = ConfigDict(from_attributes=True)
name: str
species: str
class Person(BaseModel):
model_config = ConfigDict(from_attributes=True)
name: str
age: float = None
pets: List[Pet]
bones = PetCls(name='Bones', species='dog')
orion = PetCls(name='Orion', species='cat')
anna = PersonCls(name='Anna', age=20, pets=[bones, orion])
anna_model = Person.model_validate(anna)
print(anna_model)
"""
name='Anna' age=20.0 pets=[Pet(name='Bones', species='dog'), Pet(name='Orion', species='cat')]
"""
Error handling¶
Pydanticは、検証中のデータにエラーを検出するたびにValidationError
を発生させます。
検出されたエラーの数にかかわらず、タイプValidationError
の単一の例外が発生し、ValidationError
にはすべてのエラーとその発生方法に関する情報が含まれます。
標準エラーとカスタムエラーの詳細については、Error Handlingを参照してください。
デモンストレーション:
from typing import List
from pydantic import BaseModel, ValidationError
class Model(BaseModel):
list_of_ints: List[int]
a_float: float
data = dict(
list_of_ints=['1', 2, 'bad'],
a_float='not a float',
)
try:
Model(**data)
except ValidationError as e:
print(e)
"""
2 validation errors for Model
list_of_ints.2
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='bad', input_type=str]
a_float
Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='not a float', input_type=str]
"""
Helper functions¶
Pydanticは、データをパースするための3つのclassmethod
ヘルパー関数をモデルに提供します。
model_validate()
: これはモデルの__init__
メソッドと非常によく似ていますが、キーワード引数ではなく辞書またはオブジェクトを取る点が異なります。渡されたオブジェクトが検証できない場合、または問題のモデルの辞書またはインスタンスでない場合、ValidationError
が発生します。
model_validate_json()
: strまたはbytesを受け取ってjsonとしてパースし、結果をmodel_validate()
に渡します。
model_validate_strings()
: 文字列のキーと値を持つdict(ネスト可能)を取得し、jsonモードでデータを検証して、その文字列を強制的に正しい型に変換できるようにします。
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, ValidationError
class User(BaseModel):
id: int
name: str = 'John Doe'
signup_ts: Optional[datetime] = None
m = User.model_validate({'id': 123, 'name': 'James'})
print(m)
#> id=123 name='James' signup_ts=None
try:
User.model_validate(['not', 'a', 'dict'])
except ValidationError as e:
print(e)
"""
1 validation error for User
Input should be a valid dictionary or instance of User [type=model_type, input_value=['not', 'a', 'dict'], input_type=list]
"""
m = User.model_validate_json('{"id": 123, "name": "James"}')
print(m)
#> id=123 name='James' signup_ts=None
try:
m = User.model_validate_json('{"id": 123, "name": 123}')
except ValidationError as e:
print(e)
"""
1 validation error for User
name
Input should be a valid string [type=string_type, input_value=123, input_type=int]
"""
try:
m = User.model_validate_json('invalid JSON')
except ValidationError as e:
print(e)
"""
1 validation error for User
Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='invalid JSON', input_type=str]
"""
m = User.model_validate_strings({'id': '123', 'name': 'James'})
print(m)
#> id=123 name='James' signup_ts=None
m = User.model_validate_strings(
{'id': '123', 'name': 'James', 'signup_ts': '2024-04-01T12:00:00'}
)
print(m)
#> id=123 name='James' signup_ts=datetime.datetime(2024, 4, 1, 12, 0)
try:
m = User.model_validate_strings(
{'id': '123', 'name': 'James', 'signup_ts': '2024-04-01'}, strict=True
)
except ValidationError as e:
print(e)
"""
1 validation error for User
signup_ts
Input should be a valid datetime, invalid datetime separator, expected `T`, `t`, `_` or space [type=datetime_parsing, input_value='2024-04-01', input_type=str]
"""
JSON以外のフォーマットでシリアライズされたデータを検証したい場合は、データを自分でdictにロードしてから、model_validate
に渡してください。
Note
関連する型とモデルの設定によって、model_validate
とmodel_validate_json
の検証動作が異なる場合があります。
JSON以外のソースからのデータがあり、model_validate_json
と同じ検証動作とエラーが必要な場合は、今のところ、model_validate_json(json.dumps(data))
を使用するか、データが文字列のキーと値を持つ(ネストされている可能性のある)dictの形式をとる場合はmodel_validate_strings
を使用することをお勧めします。
Note
JSONパースの詳細については、ドキュメントのJSONセクションを参照してください。
Note
モデルのインスタンスをmodel_validate
に渡す場合は、モデルの構成にrevalidate_instances
を設定することを検討してください。
この値を設定しない場合、モデルインスタンスの検証はスキップされます。次の例を参照してください。
from pydantic import BaseModel
class Model(BaseModel):
a: int
m = Model(a=0)
# note: the `model_config` setting validate_assignment=True` can prevent this kind of misbehavior
m.a = 'not an int'
# doesn't raise a validation error even though m is invalid
m2 = Model.model_validate(m)
from pydantic import BaseModel, ConfigDict, ValidationError
class Model(BaseModel):
a: int
model_config = ConfigDict(revalidate_instances='always')
m = Model(a=0)
# note: the `model_config` setting validate_assignment=True` can prevent this kind of misbehavior
m.a = 'not an int'
try:
m2 = Model.model_validate(m)
except ValidationError as e:
print(e)
"""
1 validation error for Model
a
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='not an int', input_type=str]
"""
Creating models without validation¶
Pydanticには、model_construct()
メソッドも用意されています。このメソッドを使用すると、検証なしでモデルを作成できます。これは、少なくとも次のような場合に便利です。
- すでに有効であることがわかっている複雑なデータを処理する場合(パフォーマンス上の理由から)
- 1つ以上の検証関数がべき等でない場合、または
- 1つ以上のバリデータ関数に、トリガされたくない副作用がある場合。
Note
Pydantic V2では、BaseModel.__init__
とBaseModel.model_construct
の間のパフォーマンスの差が大幅に縮小されました。単純なモデルの場合、BaseModel.__init__
を呼び出す方が高速になる可能性があります。
パフォーマンス上の理由からmodel_construct()
を使用している場合は、model_construct()
の方が高速であると想定する前に、ユースケースをプロファイルすることをお勧めします。
Warning
model_construct()
は検証を行いません。つまり、無効なモデルを作成する可能性があります。
model_construct()
メソッドは、すでに検証済みのデータ、または確実に信頼できるデータに対してのみ使用してください。
from pydantic import BaseModel
class User(BaseModel):
id: int
age: int
name: str = 'John Doe'
original_user = User(id=123, age=32)
user_data = original_user.model_dump()
print(user_data)
#> {'id': 123, 'age': 32, 'name': 'John Doe'}
fields_set = original_user.model_fields_set
print(fields_set)
#> {'age', 'id'}
# ...
# pass user_data and fields_set to RPC or save to the database etc.
# ...
# you can then create a new instance of User without
# re-running validation which would be unnecessary at this point:
new_user = User.model_construct(_fields_set=fields_set, **user_data)
print(repr(new_user))
#> User(id=123, age=32, name='John Doe')
print(new_user.model_fields_set)
#> {'age', 'id'}
# construct can be dangerous, only use it with validated data!:
bad_user = User.model_construct(id='dog')
print(repr(bad_user))
#> User(id='dog', name='John Doe')
model_construct()
の_fields_set
キーワード引数はオプションですが、どのフィールドが最初に設定され、どのフィールドが設定されなかったかをより正確に知ることができます。これを省略すると、model_fields_set
は単に提供されたデータのキーになります。
例えば、上の例では、_fields_set
が与えられなかった場合、new_user.model_fields_set
は{'id','age','name'}
になります。
RootModel
サブクラスでは、キーワード引数を使用する代わりに、ルートの値をmodel_construct()
に位置的に渡すことができることに注意してください。
model_construct()
の動作に関する追加の注意事項を以下に示します。
- "検証は実行されません"となる場合、これにはディクテーションをモデルインスタンスに変換することも含まれます。したがって、
Model
型のフィールドがある場合は、model_construct()
に渡す前に、内部辞書を自分でモデルに変換する必要があります。
- 特に、
model_construct()
メソッドは、ディクテーションからモデルを再帰的に構築することをサポートしていません。
- デフォルト値を持つフィールドにキーワード引数を渡さない場合でも、デフォルト値が使用されます。
- private属性を持つモデルの場合、
__pydantic_private__
dictは__init__
を呼び出したときと同じように初期化されます。
model_construct()
を使用してインスタンスを構築する場合、カスタムの__init__
メソッドが定義されていても、モデルまたはその親クラスから__init__
メソッドは呼び出されません。
model_construct
のextra
の動作について
* model_config['extra']=='allow'
を持つモデルの場合、フィールドに対応しないデータは__pydantic_extra__
dictに正しく保存され、モデルの__dict__
に保存されます。
* model_config['extra']=='ignore'
を持つモデルの場合、フィールドに対応しないデータは無視されます。つまり、インスタンスの__pydantic_extra__
や__dict__
には保存されません。
* __init__
の呼び出しとは異なり、model_config['extra']=='forbid'
を指定したmodel_construct
の呼び出しでは、フィールドに対応しないデータが存在してもエラーになりません。むしろ、その入力データは単に無視されます。
Generic models¶
Pydanticは、一般的なモデル構造の再利用を容易にするために、ジェネリックモデルの作成をサポートしています。
ジェネリックモデルを宣言するには、以下の手順を実行します。
- モデルのパラメータ化に使用する1つ以上の
typing.TypeVar
インスタンスを宣言します。
pydantic.BaseModel
とtyping.Generic
を継承するpydanticモデルを宣言し、TypeVar
インスタンスをパラメータとしてtyping.Generic
に渡します。
TypeVar
インスタンスをアノテーションとして使用し、他の型やpydanticモデルに置き換えます。
以下は、BaseModel
ジェネリックサブクラスを使用して、簡単に再利用できるHTTPレスポンスペイロードラッパーを作成する例です。
from typing import Generic, List, Optional, TypeVar
from pydantic import BaseModel, ValidationError
DataT = TypeVar('DataT')
class DataModel(BaseModel):
numbers: List[int]
people: List[str]
class Response(BaseModel, Generic[DataT]):
data: Optional[DataT] = None
print(Response[int](data=1))
#> data=1
print(Response[str](data='value'))
#> data='value'
print(Response[str](data='value').model_dump())
#> {'data': 'value'}
data = DataModel(numbers=[1, 2, 3], people=[])
print(Response[DataModel](data=data).model_dump())
#> {'data': {'numbers': [1, 2, 3], 'people': []}}
try:
Response[int](data='value')
except ValidationError as e:
print(e)
"""
1 validation error for Response[int]
data
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='value', input_type=str]
"""
ジェネリックモデル定義でmodel_config
を設定したり、@field_validator
やその他のPydanticデコレータを使用したりすると、それらはBaseModel
サブクラスから継承する場合と同じ方法で、パラメータ化されたサブクラスに適用されます。ジェネリッククラスで定義されたメソッドも継承されます。
Pydanticのジェネリックスは型チェッカーとも適切に統合されているため、パラメーター化ごとに異なる型を宣言した場合に期待されるすべての型チェックを行うことができます。
Note
内部的には、Pydanticはジェネリックモデルがパラメータ化されると、実行時にBaseModel
のサブクラスを作成します。
これらのクラスはキャッシュされるため、ジェネリックス・モデルを使用することで生じるオーバーヘッドは最小限に抑えられます。
ジェネリックモデルから継承し、ジェネリックであるという事実を保持するには、サブクラスもtyping.Generic
から継承する必要があります。
from typing import Generic, TypeVar
from pydantic import BaseModel
TypeX = TypeVar('TypeX')
class BaseClass(BaseModel, Generic[TypeX]):
X: TypeX
class ChildClass(BaseClass[TypeX], Generic[TypeX]):
# Inherit from Generic[TypeX]
pass
# Replace TypeX by int
print(ChildClass[int](X=1))
#> X=1
スーパークラスの型パラメータを部分的または完全に置き換えるBaseModel
のジェネリックサブクラスを作成することもできます。
from typing import Generic, TypeVar
from pydantic import BaseModel
TypeX = TypeVar('TypeX')
TypeY = TypeVar('TypeY')
TypeZ = TypeVar('TypeZ')
class BaseClass(BaseModel, Generic[TypeX, TypeY]):
x: TypeX
y: TypeY
class ChildClass(BaseClass[int, TypeY], Generic[TypeY, TypeZ]):
z: TypeZ
# Replace TypeY by str
print(ChildClass[str, int](x='1', y='y', z='3'))
#> x=1 y='y' z=3
具象サブクラスの名前が重要な場合は、デフォルトの名前生成をオーバーライドすることもできます。
from typing import Any, Generic, Tuple, Type, TypeVar
from pydantic import BaseModel
DataT = TypeVar('DataT')
class Response(BaseModel, Generic[DataT]):
data: DataT
@classmethod
def model_parametrized_name(cls, params: Tuple[Type[Any], ...]) -> str:
return f'{params[0].__name__.title()}Response'
print(repr(Response[int](data=1)))
#> IntResponse(data=1)
print(repr(Response[str](data='a')))
#> StrResponse(data='a')
パラメータ化されたジェネリックモデルを他のモデルのタイプとして使用できます。
from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar('T')
class ResponseModel(BaseModel, Generic[T]):
content: T
class Product(BaseModel):
name: str
price: float
class Order(BaseModel):
id: int
product: ResponseModel[Product]
product = Product(name='Apple', price=0.5)
response = ResponseModel[Product](content=product)
order = Order(id=1, product=response)
print(repr(order))
"""
Order(id=1, product=ResponseModel[Product](content=Product(name='Apple', price=0.5)))
"""
Tip
パラメータ化されたジェネリックモデルを別のモデルの型として使用する場合(product:ResponseModel[Product]
など)、モデルインスタンスを初期化するときには必ずそのジェネリックモデルをパラメータ化してください(response=ResponseModel[Product](content=product)
など)。
そうしないと、Pydanticは渡されたデータに基づいてジェネリックモデルの型を推測しないので、ValidationError
が発生します。
ネストされたモデルで同じTypeVar
を使用すると、モデル内の異なるポイントで型付け関係を強制することができます。
from typing import Generic, TypeVar
from pydantic import BaseModel, ValidationError
T = TypeVar('T')
class InnerT(BaseModel, Generic[T]):
inner: T
class OuterT(BaseModel, Generic[T]):
outer: T
nested: InnerT[T]
nested = InnerT[int](inner=1)
print(OuterT[int](outer=1, nested=nested))
#> outer=1 nested=InnerT[int](inner=1)
try:
nested = InnerT[str](inner='a')
print(OuterT[int](outer='a', nested=nested))
except ValidationError as e:
print(e)
"""
2 validation errors for OuterT[int]
outer
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
nested
Input should be a valid dictionary or instance of InnerT[int] [type=model_type, input_value=InnerT[str](inner='a'), input_type=InnerT[str]]
"""
境界された型パラメータを使用する場合、および型パラメータを指定しないままにしておく場合、PydanticはジェネリックモデルをList
やDict
のような組み込みジェネリック型と同じように扱います。
- ジェネリックモデルをインスタンス化する前にパラメータを指定しない場合、それらは
TypeVar
の境界として検証されます。
*関係するTypeVar
に境界がない場合、それらはAny
として扱われます。
また、List
やDict
のように、TypeVar
を使用して指定されたパラメータは、後で具体的な型に置き換えることができます。
from typing import Generic, TypeVar
from pydantic import BaseModel, ValidationError
AT = TypeVar('AT')
BT = TypeVar('BT')
class Model(BaseModel, Generic[AT, BT]):
a: AT
b: BT
print(Model(a='a', b='a'))
#> a='a' b='a'
IntT = TypeVar('IntT', bound=int)
typevar_model = Model[int, IntT]
print(typevar_model(a=1, b=1))
#> a=1 b=1
try:
typevar_model(a='a', b='a')
except ValidationError as exc:
print(exc)
"""
2 validation errors for Model[int, TypeVar]
a
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
b
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
"""
concrete_model = typevar_model[int]
print(concrete_model(a=1, b=1))
#> a=1 b=1
Warning
エラーになることはありませんが、isinstanceチェックでパラメーター化されたジェネリックスを使用しないことを強くお勧めします。
例えば、isinstance(my_model, MyGenericModel[int])
を実行すべきではありませんが、isinstance(my_model, MyGenericModel)
を実行しても問題ありません(標準のジェネリックスの場合、パラメータ化されたジェネリックスでサブクラスのチェックを行うとエラーが発生することに注意してください)。
パラメータ化されたジェネリックに対してisinstanceチェックを実行する必要がある場合は、パラメータ化されたジェネリッククラスをサブクラス化することで実行できます。これはclass MyIntModel(MyGenericModel[int]):...
およびisinstance(my_model, MyIntModel)
のようになります。
PydanticモデルがTypeVar
境界で使用され、ジェネリック型がパラメータ化されていない場合、Pydanticは検証に境界を使用しますが、シリアライゼーションの観点からは値をAny
として扱います。
from typing import Generic, Optional, TypeVar
from pydantic import BaseModel
class ErrorDetails(BaseModel):
foo: str
ErrorDataT = TypeVar('ErrorDataT', bound=ErrorDetails)
class Error(BaseModel, Generic[ErrorDataT]):
message: str
details: Optional[ErrorDataT]
class MyErrorDetails(ErrorDetails):
bar: str
# serialized as Any
error = Error(
message='We just had an error',
details=MyErrorDetails(foo='var', bar='var2'),
)
assert error.model_dump() == {
'message': 'We just had an error',
'details': {
'foo': 'var',
'bar': 'var2',
},
}
# serialized using the concrete parametrization
# note that `'bar': 'var2'` is missing
error = Error[ErrorDetails](
message='We just had an error',
details=ErrorDetails(foo='var'),
)
assert error.model_dump() == {
'message': 'We just had an error',
'details': {
'foo': 'var',
},
}
上記の動作の別の例として、境界仕様とジェネリック型のパラメータ化に関するすべての順列を列挙します:
from typing import Generic
from typing_extensions import TypeVar
from pydantic import BaseModel
TBound = TypeVar('TBound', bound=BaseModel)
TNoBound = TypeVar('TNoBound')
class IntValue(BaseModel):
value: int
class ItemBound(BaseModel, Generic[TBound]):
item: TBound
class ItemNoBound(BaseModel, Generic[TNoBound]):
item: TNoBound
item_bound_inferred = ItemBound(item=IntValue(value=3))
item_bound_explicit = ItemBound[IntValue](item=IntValue(value=3))
item_no_bound_inferred = ItemNoBound(item=IntValue(value=3))
item_no_bound_explicit = ItemNoBound[IntValue](item=IntValue(value=3))
# calling `print(x.model_dump())` on any of the above instances results in the following:
#> {'item': {'value': 3}}
default=...
(Python>=3.13またはtyping-extensions
で使用可能)または制約(TypeVar('T',str, int)
;この形式のTypeVar
を使用することはめったにないことに注意してください)を使用する場合、型変数がパラメータ化されていなければ、検証とシリアライゼーションの両方にデフォルト値または制約が使用されます。
この動作はpydantic.SerializeAsAny
を使って上書きできます:
from typing import Generic, Optional
from typing_extensions import TypeVar
from pydantic import BaseModel, SerializeAsAny
class ErrorDetails(BaseModel):
foo: str
ErrorDataT = TypeVar('ErrorDataT', default=ErrorDetails)
class Error(BaseModel, Generic[ErrorDataT]):
message: str
details: Optional[ErrorDataT]
class MyErrorDetails(ErrorDetails):
bar: str
# serialized using the default's serializer
error = Error(
message='We just had an error',
details=MyErrorDetails(foo='var', bar='var2'),
)
assert error.model_dump() == {
'message': 'We just had an error',
'details': {
'foo': 'var',
},
}
class SerializeAsAnyError(BaseModel, Generic[ErrorDataT]):
message: str
details: Optional[SerializeAsAny[ErrorDataT]]
# serialized as Any
error = SerializeAsAnyError(
message='We just had an error',
details=MyErrorDetails(foo='var', bar='baz'),
)
assert error.model_dump() == {
'message': 'We just had an error',
'details': {
'foo': 'var',
'bar': 'baz',
},
}
Note
注意:ジェネリックの境界に対して検証する場合、ジェネリックをパラメータ化しないと、ちょっとしたトラブルに巻き込まれる可能性があります。 データが失われる可能性があります。次の例を参照してください。
from typing import Generic
from typing_extensions import TypeVar
from pydantic import BaseModel
TItem = TypeVar('TItem', bound='ItemBase')
class ItemBase(BaseModel): ...
class IntItem(ItemBase):
value: int
class ItemHolder(BaseModel, Generic[TItem]):
item: TItem
loaded_data = {'item': {'value': 1}}
print(ItemHolder(**loaded_data).model_dump()) # (1)!
#> {'item': {}}
print(ItemHolder[IntItem](**loaded_data).model_dump()) # (2)!
#> {'item': {'value': 1}}
-
ジェネリックがパラメータ化されていない場合、入力データはジェネリック境界に対して検証されます。
ItemBase
にフィールドがない場合、item
フィールドの情報は失われます。 -
この場合、実行時の型情報は一般的なパラメータ化によって明示的に提供されるため、入力データは
IntItem
クラスに対して検証され、シリアライゼーションの出力は期待されたものと一致します。
Dynamic model creation¶
API Documentation
実行時情報を使用してモデルを作成し、フィールドを指定することが望ましい場合があります。
このために、Pydanticはcreate_model
関数を提供して、モデルをオンザフライで作成できるようにします。
from pydantic import BaseModel, create_model
DynamicFoobarModel = create_model(
'DynamicFoobarModel', foo=(str, ...), bar=(int, 123)
)
class StaticFoobarModel(BaseModel):
foo: str
bar: int = 123
ここで、StaticFoobarModel
とDynamicFoobarModel
は同一です。
フィールドは、次のタプル形式のいずれかで定義されます。
(<type>, <default value>)
(<type>, Field(...))
typing.Annotated[<type>, Field(...)]
タプルの2番目の引数(デフォルト値)としてField(...)
呼び出しを使用すると、より高度なフィールド設定が可能になります。したがって、次のように類似しています。
from pydantic import BaseModel, Field, create_model
DynamicModel = create_model(
'DynamicModel',
foo=(str, Field(..., description='foo description', alias='FOO')),
)
class StaticModel(BaseModel):
foo: str = Field(..., description='foo description', alias='FOO')
特別なキーワード引数__config__
と__base__
を使って新しいモデルをカスタマイズすることができます。
これには、フィールドを追加して基本モデルを拡張することも含まれます。
from pydantic import BaseModel, create_model
class FooModel(BaseModel):
foo: str
bar: int = 123
BarModel = create_model(
'BarModel',
apple=(str, 'russet'),
banana=(str, 'yellow'),
__base__=FooModel,
)
print(BarModel)
#> <class '__main__.BarModel'>
print(BarModel.model_fields.keys())
#> dict_keys(['foo', 'bar', 'apple', 'banana'])
__validators__
引数にdictを渡すことでバリデータを追加することもできます。
from pydantic import ValidationError, create_model, field_validator
def username_alphanumeric(cls, v):
assert v.isalnum(), 'must be alphanumeric'
return v
validators = {
'username_validator': field_validator('username')(username_alphanumeric)
}
UserModel = create_model(
'UserModel', username=(str, ...), __validators__=validators
)
user = UserModel(username='scolvin')
print(user)
#> username='scolvin'
try:
UserModel(username='scolvi%n')
except ValidationError as e:
print(e)
"""
1 validation error for UserModel
username
Assertion failed, must be alphanumeric [type=assertion_error, input_value='scolvi%n', input_type=str]
"""
Note
動的に作成されたモデルをピクル化するには、次の手順を行います。
- モデルはグローバルに定義する必要があります。
- __module__
を提供する必要があります
RootModel
and custom root types¶
API Documentation
Pydanticモデルは、pydantic.RootModel
をサブクラス化することで、"カスタムルートタイプ"で定義できます。
ルート型はPydanticでサポートされている任意の型で、RootModel
の汎用パラメータで指定されます。
ルート値は、最初で唯一の引数を介してモデル__init__
またはmodel_validate
に渡すことができます。
これがどのように機能するかの例を次に示します。
from typing import Dict, List
from pydantic import RootModel
Pets = RootModel[List[str]]
PetsByName = RootModel[Dict[str, str]]
print(Pets(['dog', 'cat']))
#> root=['dog', 'cat']
print(Pets(['dog', 'cat']).model_dump_json())
#> ["dog","cat"]
print(Pets.model_validate(['dog', 'cat']))
#> root=['dog', 'cat']
print(Pets.model_json_schema())
"""
{'items': {'type': 'string'}, 'title': 'RootModel[List[str]]', 'type': 'array'}
"""
print(PetsByName({'Otis': 'dog', 'Milo': 'cat'}))
#> root={'Otis': 'dog', 'Milo': 'cat'}
print(PetsByName({'Otis': 'dog', 'Milo': 'cat'}).model_dump_json())
#> {"Otis":"dog","Milo":"cat"}
print(PetsByName.model_validate({'Otis': 'dog', 'Milo': 'cat'}))
#> root={'Otis': 'dog', 'Milo': 'cat'}
root
フィールド内の項目に直接アクセスしたり、項目を繰り返し処理したりしたい場合は、次の例に示すように、カスタムの__iter__
関数と__getitem__
関数を実装することができます。
from typing import List
from pydantic import RootModel
class Pets(RootModel):
root: List[str]
def __iter__(self):
return iter(self.root)
def __getitem__(self, item):
return self.root[item]
pets = Pets.model_validate(['dog', 'cat'])
print(pets[0])
#> dog
print([pet for pet in pets])
#> ['dog', 'cat']
パラメータ化されたルートモデルのサブクラスを直接作成することもできます。
from typing import List
from pydantic import RootModel
class Pets(RootModel[List[str]]):
def describe(self) -> str:
return f'Pets: {", ".join(self.root)}'
my_pets = Pets.model_validate(['dog', 'cat'])
print(my_pets.describe())
#> Pets: dog, cat
Faux immutability¶
モデルはmodel_config['frozen']=True
で不変に設定できます。これが設定されている場合、インスタンス属性の値を変更しようとするとエラーが発生します。詳細については、API referenceを参照してください。
Note
この動作は、Pydantic V1でallow_mutation=False
という設定によって実現されました。
このconfigフラグはPydantic V2では廃止され、frozen
に置き換えられました。
Warning
Pythonでは、不変性は強制されません。開発者は、従来"不変"と見なされていたオブジェクトを、変更することを選択した場合に変更することができます。
from pydantic import BaseModel, ConfigDict, ValidationError
class FooBarModel(BaseModel):
model_config = ConfigDict(frozen=True)
a: str
b: dict
foobar = FooBarModel(a='hello', b={'apple': 'pear'})
try:
foobar.a = 'different'
except ValidationError as e:
print(e)
"""
1 validation error for FooBarModel
a
Instance is frozen [type=frozen_instance, input_value='different', input_type=str]
"""
print(foobar.a)
#> hello
print(foobar.b)
#> {'apple': 'pear'}
foobar.b['apple'] = 'grape'
print(foobar.b)
#> {'apple': 'grape'}
a
を変更しようとするとエラーが発生し、a
は変更されません。しかし、辞書b
は変更可能であり、foobar
の不変性はb
の変更を止めるものではありません。
Abstract base classes¶
Pydanticモデルは、PythonのAbstract Base Classes(ABC)と一緒に使用できます。
import abc
from pydantic import BaseModel
class FooBarModel(BaseModel, abc.ABC):
a: str
b: int
@abc.abstractmethod
def my_abstract_method(self):
pass
Field ordering¶
フィールドの順序は、モデルに次のような影響を与えます。
- フィールドの順序はモデルschemaで保持されます。
- validation errorsでフィールドの順序が保持されます。
- フィールドの順序は、
.model_dump()
および.model_dump_json()
などによって保持されます。
from pydantic import BaseModel, ValidationError
class Model(BaseModel):
a: int
b: int = 2
c: int = 1
d: int = 0
e: float
print(Model.model_fields.keys())
#> dict_keys(['a', 'b', 'c', 'd', 'e'])
m = Model(e=2, a=1)
print(m.model_dump())
#> {'a': 1, 'b': 2, 'c': 1, 'd': 0, 'e': 2.0}
try:
Model(a='x', b='x', c='x', d='x', e='x')
except ValidationError as err:
error_locations = [e['loc'] for e in err.errors()]
print(error_locations)
#> [('a',), ('b',), ('c',), ('d',), ('e',)]
Required fields¶
必要に応じてフィールドを宣言するには、注釈を使用するか、注釈とField
仕様を組み合わせて宣言します。
特にField
コンストラクタを使用する場合は、Ellipsis
/...
を使用して、フィールドが必要であることを強調することもできます。
Field
関数は、主に属性のalias
やdescription
のような設定を行うために使われます。
コンストラクタはEllipsis
/...
を唯一の位置引数としてサポートします。
これは、そのフィールドが必須であることを示す方法として使用されますが、この要件を強制するのは型ヒントです。
from pydantic import BaseModel, Field
class Model(BaseModel):
a: int
b: int = ...
c: int = Field(..., alias='C')
ここでa
、b
、c
はすべて必須です。しかし、このb:int=...
の使用はmypyでは適切に動作せず、v1.0ではほとんどの場合避けるべきです。
Note
Pydantic V1では、Optional
またはAny
で注釈されたフィールドには、デフォルトが明示的に指定されていない場合でも、暗黙的なデフォルトNone
が与えられていました。この動作はPydantic V2で変更され、暗黙的なデフォルト値を持つフィールドを生成する型注釈はもはや存在しません。
必須フィールドとnull許容フィールドの変更の詳細については、the migration guideを参照してください。
Fields with non-hashable default values¶
Pythonの一般的なバグの原因は、関数またはメソッドの引数のデフォルト値として可変オブジェクトを使用することです。これは、各呼び出しで同じインスタンスが再利用されるためです。
この場合、dataclasses
モジュールは実際にはエラーを発生し、dataclasses.field
にdefault_factory
引数を使用する必要があることを示します。
Pydanticは、ハッシュ不可能なデフォルト値に対してdefault_factory
の使用もサポートしていますが、必須ではありません。デフォルト値がハッシュ可能でない場合、Pydanticはモデルの各インスタンスを作成するときにデフォルト値をディープコピーします。
from typing import Dict, List
from pydantic import BaseModel
class Model(BaseModel):
item_counts: List[Dict[str, int]] = [{}]
m1 = Model()
m1.item_counts[0]['a'] = 1
print(m1.item_counts)
#> [{'a': 1}]
m2 = Model()
print(m2.item_counts)
#> [{}]
Fields with dynamic default values¶
デフォルト値を使用してフィールドを宣言する場合は、そのフィールドを動的(つまり、モデルごとに異なる)にすることができます。
これを行うには、default_factory
を使用します。
次に例を示します。
from datetime import datetime, timezone
from uuid import UUID, uuid4
from pydantic import BaseModel, Field
def datetime_now() -> datetime:
return datetime.now(timezone.utc)
class Model(BaseModel):
uid: UUID = Field(default_factory=uuid4)
updated: datetime = Field(default_factory=datetime_now)
m1 = Model()
m2 = Model()
assert m1.uid != m2.uid
詳細については、Field
functionのドキュメントを参照してください。
Automatically excluded attributes¶
Class vars¶
typing.ClassVar
で注釈が付けられた属性は、Pydanticによってクラス変数として適切に扱われ、モデルインスタンスのフィールドにはなりません。
from typing import ClassVar
from pydantic import BaseModel
class Model(BaseModel):
x: int = 2
y: ClassVar[int] = 1
m = Model()
print(m)
#> x=2
print(Model.y)
#> 1
Private model attributes¶
API Documentation
名前の先頭にアンダースコアが付いている属性は、Pydanticではフィールドとして扱われず、モデルスキーマにも含まれません。代わりに、これらは"プライベート属性"に変換されます。この属性は検証されず、__init__
、model_validate
などの呼び出し時にも設定されません。
Note
Pydantic v2.1.0では、private属性でField
関数を使用しようとすると、NameErrorを受け取ります。
プライベート属性はフィールドとして扱われないため、Field()関数は適用できません。
次に使用例を示します。
from datetime import datetime
from random import randint
from pydantic import BaseModel, PrivateAttr
class TimeAwareModel(BaseModel):
_processed_at: datetime = PrivateAttr(default_factory=datetime.now)
_secret_value: str
def __init__(self, **data):
super().__init__(**data)
# this could also be done with default_factory
self._secret_value = randint(1, 5)
m = TimeAwareModel()
print(m._processed_at)
#> 2032-01-02 03:04:05.000006
print(m._secret_value)
#> 3
モデルフィールドとの競合を避けるため、プライベート属性名はアンダースコアで始める必要があります。ただし、名前(__attr__
など)はサポートされていません。
Data conversion¶
Pydanticは入力データをキャストして、モデルのフィールド型に強制的に準拠させることがあり、場合によっては情報が失われることがあります。 次に例を示します。
from pydantic import BaseModel
class Model(BaseModel):
a: int
b: float
c: str
print(Model(a=3.000, b='2.72', c=b'binary data').model_dump())
#> {'a': 3, 'b': 2.72, 'c': 'binary data'}
これはPydanticの意図的な決定であり、多くの場合、最も有用なアプローチです。このテーマに関するより長い議論については、ここを参照してください。
ただし、strict type checkingもサポートされています。
Model signature¶
すべてのPydanticモデルは、そのフィールドに基づいて生成されたシグネチャを持ちます。
import inspect
from pydantic import BaseModel, Field
class FooModel(BaseModel):
id: int
name: str = None
description: str = 'Foo'
apple: int = Field(alias='pear')
print(inspect.signature(FooModel))
#> (*, id: int, name: str = None, description: str = 'Foo', pear: int) -> None
正確な署名は、イントロスペクションの目的や、FastAPI
やhypothesis
のようなライブラリに役立ちます。
生成された署名は、カスタムの__init__
関数も考慮します。
import inspect
from pydantic import BaseModel
class MyModel(BaseModel):
id: int
info: str = 'Foo'
def __init__(self, id: int = 1, *, bar: str, **data) -> None:
"""My custom init!"""
super().__init__(id=id, bar=bar, **data)
print(inspect.signature(MyModel))
#> (id: int = 1, *, bar: str, info: str = 'Foo') -> None
シグネチャに含めるフィールドのエイリアスまたは名前は、有効なPython識別子である必要があります。 Pydanticは、シグネチャを生成するときにフィールドの名前よりもエイリアスを優先しますが、エイリアスが有効なPython識別子でない場合はフィールド名を使用することができます。
フィールドの別名と名前の両方が有効な識別子でない場合(これはcreate_model
の特殊な使い方によって可能になるかもしれません)、**data
引数が追加されます。さらに、model_config['extra']=='allow'
の場合、**data
引数は常にシグネチャに存在します。
Structural pattern matching¶
Pydanticは、Python 3.10のPEP 636で導入されたモデルの構造パターンマッチングをサポートしています。
from pydantic import BaseModel
class Pet(BaseModel):
name: str
species: str
a = Pet(name='Bones', species='dog')
match a:
# match `species` to 'dog', declare and initialize `dog_name`
case Pet(species='dog', name=dog_name):
print(f'{dog_name} is a dog')
#> Bones is a dog
# default case
case _:
print('No dog matched')
Note
match-case文は、新しいモデルを作成するように見えますが、騙されてはいけません。 単にアトリビュートを取得して比較したり、宣言して初期化したりするための糖衣構文です。
Attribute copies¶
In many cases, arguments passed to the constructor will be copied in order to perform validation and, where necessary, coercion. 多くの場合、コンストラクタに渡された引数は、検証と、必要に応じて強制型変換を実行するためにコピーされます。
この例では、検証中にコピーされたため、クラスの構築後にリストのIDが変更されることに注意してください。
from typing import List
from pydantic import BaseModel
class C1:
arr = []
def __init__(self, in_arr):
self.arr = in_arr
class C2(BaseModel):
arr: List[int]
arr_orig = [1, 9, 10, 3]
c1 = C1(arr_orig)
c2 = C2(arr=arr_orig)
print('id(c1.arr) == id(c2.arr):', id(c1.arr) == id(c2.arr))
#> id(c1.arr) == id(c2.arr): False
Note
モデルを渡すときなど、Pydanticが属性をコピーしない場合があります。モデルをそのまま使用します。この動作は、model_config['revalidate_instances'] = 'always'
を設定することで上書きできます。
Extra fields¶
デフォルトでは、Pydanticモデルは、認識されないフィールドのデータを提供してもエラーにならず、単に無視されるだけです。
from pydantic import BaseModel
class Model(BaseModel):
x: int
m = Model(x=1, y='a')
assert m.model_dump() == {'x': 1}
これによってエラーを発生させたい場合は、model_config
を使用してこれを実行できます。
from pydantic import BaseModel, ConfigDict, ValidationError
class Model(BaseModel):
x: int
model_config = ConfigDict(extra='forbid')
try:
Model(x=1, y='a')
except ValidationError as exc:
print(exc)
"""
1 validation error for Model
y
Extra inputs are not permitted [type=extra_forbidden, input_value='a', input_type=str]
"""
提供された追加データを保持するには、extra='allow'
と設定します。
追加されたフィールドはBaseModel.__pydantic_extra__
に保存されます。
from pydantic import BaseModel, ConfigDict
class Model(BaseModel):
x: int
model_config = ConfigDict(extra='allow')
m = Model(x=1, y='a')
assert m.__pydantic_extra__ == {'y': 'a'}
デフォルトでは、これらの追加項目に検証は適用されませんが、__pydantic_extra__
の型注釈をオーバーライドすることで、値の型を設定できます。
from typing import Dict
from pydantic import BaseModel, ConfigDict, Field, ValidationError
class Model(BaseModel):
__pydantic_extra__: Dict[str, int] = Field(init=False) # (1)!
x: int
model_config = ConfigDict(extra='allow')
try:
Model(x=1, y='a')
except ValidationError as exc:
print(exc)
"""
1 validation error for Model
y
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
"""
m = Model(x=1, y='2')
assert m.x == 1
assert m.y == 2
assert m.model_dump() == {'x': 1, 'y': 2}
assert m.__pydantic_extra__ == {'y': 2}
=Field(init=False)
は実行時には何の効果もありませんが、__pydantic_extra__
フィールドが型チェッカーによってモデルの__init__
メソッドの引数として扱われるのを防ぎます。
同じ設定がTypedDict
とdataclass
'にも適用されますが、クラスの__pydantic_config__
属性を有効なConfigDict
に設定することで設定が制御される点が異なります。