コンテンツにスキップ

Models

API Documentation

pydantic.main.BaseModel

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')

この例では、userUserのインスタンスです。 オブジェクトを初期化すると、すべてのパースと検証が実行されます。 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_copy(): モデルのコピー(デフォルトではシャローコピー)を返します。Serializationを参照してください。
  • model_extra: 検証中に追加フィールドセットを取得します。
  • model_fields_set: モデルインスタンスが初期化されたときに設定されたフィールドのセットです。
  • model_post_init():モデルが初期化された後に追加の初期化を実行します。
  • model_rebuild(): 再帰的なジェネリックモデルの構築もサポートするモデルスキーマを再構築します。Rebuild model schemaを参照してください。
  • model_validate(): 任意のオブジェクトをモデルにロードするためのユーティリティです。Helper functionsを参照してください。

Note

メソッドと属性の完全なリストを含むクラス定義については、BaseModelを参照してください。

Tip

Pydantic V1からの変更の詳細については、Migration GuideChanges 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

上記の例が機能するのは、エイリアスがフィールド入力のフィールド名よりも優先されるためです。SQLModelmetadata属性にアクセスすると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_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_validatemodel_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_constructextraの動作について

* 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. モデルのパラメータ化に使用する1つ以上のtyping.TypeVarインスタンスを宣言します。
  1. pydantic.BaseModeltyping.Genericを継承するpydanticモデルを宣言し、TypeVarインスタンスをパラメータとしてtyping.Genericに渡します。
  1. 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はジェネリックモデルをListDictのような組み込みジェネリック型と同じように扱います。

  • ジェネリックモデルをインスタンス化する前にパラメータを指定しない場合、それらはTypeVarの境界として検証されます。

*関係するTypeVarに境界がない場合、それらはAnyとして扱われます。

また、ListDictのように、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}}
  1. ジェネリックがパラメータ化されていない場合、入力データはジェネリック境界に対して検証されます。ItemBaseにフィールドがない場合、itemフィールドの情報は失われます。

  2. この場合、実行時の型情報は一般的なパラメータ化によって明示的に提供されるため、入力データはIntItemクラスに対して検証され、シリアライゼーションの出力は期待されたものと一致します。

Dynamic model creation

API Documentation

pydantic.main.create_model

実行時情報を使用してモデルを作成し、フィールドを指定することが望ましい場合があります。 このために、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

ここで、StaticFoobarModelDynamicFoobarModelは同一です。

フィールドは、次のタプル形式のいずれかで定義されます。

  • (<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.root_model.RootModel

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

フィールドの順序は、モデルに次のような影響を与えます。

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関数は、主に属性のaliasdescriptionのような設定を行うために使われます。 コンストラクタはEllipsis/...を唯一の位置引数としてサポートします。 これは、そのフィールドが必須であることを示す方法として使用されますが、この要件を強制するのは型ヒントです。

from pydantic import BaseModel, Field


class Model(BaseModel):
    a: int
    b: int = ...
    c: int = Field(..., alias='C')

ここでabcはすべて必須です。しかし、この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.fielddefault_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.fields.PrivateAttr

名前の先頭にアンダースコアが付いている属性は、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

正確な署名は、イントロスペクションの目的や、FastAPIhypothesisのようなライブラリに役立ちます。

生成された署名は、カスタムの__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}
  1. =Field(init=False)は実行時には何の効果もありませんが、__pydantic_extra__フィールドが型チェッカーによってモデルの__init__メソッドの引数として扱われるのを防ぎます。

同じ設定がTypedDictdataclass'にも適用されますが、クラスの__pydantic_config__属性を有効なConfigDictに設定することで設定が制御される点が異なります。