コンテンツにスキップ

??? API「APIドキュメント」 pydantic.dataclasses.dataclass

Pydantic のBaseModel使用したくない場合は、代わりに標準データクラス(Python 3.7 で導入) で同じデータ検証を行うことができます。

from datetime import datetime

from pydantic.dataclasses import dataclass


@dataclass
class User:
    id: int
    name: str = 'John Doe'
    signup_ts: datetime = None


user = User(id='42', signup_ts='2032-06-21T12:00')
print(user)
"""
User(id=42, name='John Doe', signup_ts=datetime.datetime(2032, 6, 21, 12, 0))
"""

!!!注意点に留意してください pydantic.dataclasses.dataclass pydantic.BaseModelの代替ではありませんpydantic.dataclasses.dataclass dataclasses.dataclassと同様の機能を提供しますが、Pydantic 検証が追加されています。 pydantic.BaseModelをサブクラス化することがより良い選択となる場合があります。

For more information and discussion see
[pydantic/pydantic#710](https://github.com/pydantic/pydantic/issues/710).

Pydantic データクラスとBaseModelには次のような違いがあります。

すべての標準的な Pydantic フィールド タイプを使用できます。ただし、コンストラクターに渡される引数は、検証と必要に応じて強制を実行するためにコピーされることに注意してください。

Pydantic データクラスで検証を実行したり、JSON スキーマを生成するには、データクラスを TypeAdapter でラップし、そのメソッドを使用する必要があります。

default_factoryを必要とするフィールドは、 pydantic.Fieldまたはdataclasses.fieldのいずれかで指定できます。

import dataclasses
from typing import List, Optional

from pydantic import Field, TypeAdapter
from pydantic.dataclasses import dataclass


@dataclass
class User:
    id: int
    name: str = 'John Doe'
    friends: List[int] = dataclasses.field(default_factory=lambda: [0])
    age: Optional[int] = dataclasses.field(
        default=None,
        metadata=dict(title='The age of the user', description='do not lie!'),
    )
    height: Optional[int] = Field(None, title='The height in cm', ge=50, le=300)


user = User(id='42')
print(TypeAdapter(User).json_schema())
"""
{
    'properties': {
        'id': {'title': 'Id', 'type': 'integer'},
        'name': {'default': 'John Doe', 'title': 'Name', 'type': 'string'},
        'friends': {
            'items': {'type': 'integer'},
            'title': 'Friends',
            'type': 'array',
        },
        'age': {
            'anyOf': [{'type': 'integer'}, {'type': 'null'}],
            'default': None,
            'description': 'do not lie!',
            'title': 'The age of the user',
        },
        'height': {
            'anyOf': [
                {'maximum': 300, 'minimum': 50, 'type': 'integer'},
                {'type': 'null'},
            ],
            'default': None,
            'title': 'The height in cm',
        },
    },
    'required': ['id'],
    'title': 'User',
    'type': 'object',
}
"""

pydantic.dataclasses.dataclass の引数は、model_config と同じ意味を持つ追加のキーワード引数config 1 つ除いて、標準デコレータと同じです。

!!!警告 v1.2 以降、 _pydantic_データクラスを型チェックするにはMypy プラグインをインストールする必要があります。

バリデーターとデータクラスの組み合わせの詳細については、 「データクラス バリデーター」を参照してください。

データクラス構成

BaseModelの場合と同様にconfig変更したい場合は、2 つのオプションがあります。

  • データクラスデコレーターに構成を辞書として適用します
  • ConfigDict構成として使用します

    from pydantic import ConfigDict from pydantic.dataclasses import dataclass

    Option 1 - use directly a dict

    Note: mypy will still raise typo error

    @dataclass(config=dict(validate_assignment=True)) # (1)! class MyDataclass1: a: int

    Option 2 - use ConfigDict

    (same as before at runtime since it's a TypedDict but with intellisense)

    @dataclass(config=ConfigDict(validate_assignment=True)) class MyDataclass2: a: int

  • validate_assignmentの詳細については、API リファレンス を参照してください。

!!! note Pydantic データクラスは、イニシャライザに渡される追加のフィールドをignoreforbid 、またはallow extra 構成をサポートしています。ただし、stdlib データクラスの一部のデフォルト動作が優先される場合があります。たとえば、 extra='allow'を使用して Pydantic データクラスに存在する追加のフィールドは、データクラスがprintれるときに省略されます。

ネストされたデータクラス

ネストされたデータクラスは、データクラスと通常のモデルの両方でサポートされます。

from pydantic import AnyUrl
from pydantic.dataclasses import dataclass


@dataclass
class NavbarButton:
    href: AnyUrl


@dataclass
class Navbar:
    button: NavbarButton


navbar = Navbar(button={'href': 'https://example.com'})
print(navbar)
#> Navbar(button=NavbarButton(href=Url('https://example.com/')))

フィールドとして使用する場合、データクラス (Pydantic または vanilla) は検証入力として dict を使用する必要があります。

汎用データクラス

Pydantic は、型変数を含む汎用データクラスをサポートします。

from typing import Generic, TypeVar

from pydantic import TypeAdapter
from pydantic.dataclasses import dataclass

T = TypeVar('T')


@dataclass
class GenericDataclass(Generic[T]):
    x: T


validator = TypeAdapter(GenericDataclass)

assert validator.validate_python({'x': None}).x is None
assert validator.validate_python({'x': 1}).x == 1
assert validator.validate_python({'x': 'a'}).x == 'a'

データクラスをBaseModelのフィールドとして使用する場合、または FastAPI 経由で使用する場合は、 TypeAdapter必要ないことに注意してください。

Stdlib データクラスと Pydantic データクラス

stdlib データクラスから継承

Stdlib データクラス (ネストされているかどうか) も継承でき、Pydantic は継承されたすべてのフィールドを自動的に検証します。

import dataclasses

import pydantic


@dataclasses.dataclass
class Z:
    z: int


@dataclasses.dataclass
class Y(Z):
    y: int = 0


@pydantic.dataclasses.dataclass
class X(Y):
    x: int = 0


foo = X(x=b'1', y='2', z='3')
print(foo)
#> X(z=3, y=2, x=1)

try:
    X(z='pika')
except pydantic.ValidationError as e:
    print(e)
    """
    1 validation error for X
    z
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='pika', input_type=str]
    """

BaseModelでの stdlib データクラスの使用

stdlib データクラス (ネストされているかどうかに関係なく) は、 BaseModelと混合すると自動的に Pydantic データクラスに変換されることに注意してください。さらに、生成された Pydantic データクラスは、元のデータクラスとまったく同じ構成( orderfrozenなど) を持ちます。

import dataclasses
from datetime import datetime
from typing import Optional

from pydantic import BaseModel, ConfigDict, ValidationError


@dataclasses.dataclass(frozen=True)
class User:
    name: str


@dataclasses.dataclass
class File:
    filename: str
    last_modification_time: Optional[datetime] = None


class Foo(BaseModel):
    # Required so that pydantic revalidates the model attributes
    model_config = ConfigDict(revalidate_instances='always')

    file: File
    user: Optional[User] = None


file = File(
    filename=['not', 'a', 'string'],
    last_modification_time='2020-01-01T00:00',
)  # nothing is validated as expected
print(file)
"""
File(filename=['not', 'a', 'string'], last_modification_time='2020-01-01T00:00')
"""

try:
    Foo(file=file)
except ValidationError as e:
    print(e)
    """
    1 validation error for Foo
    file.filename
      Input should be a valid string [type=string_type, input_value=['not', 'a', 'string'], input_type=list]
    """

foo = Foo(file=File(filename='myfile'), user=User(name='pika'))
try:
    foo.user.name = 'bulbi'
except dataclasses.FrozenInstanceError as e:
    print(e)
    #> cannot assign to field 'name'

カスタムタイプを使用する

stdlib データクラスは検証を追加するために自動的に変換されるため、カスタム型を使用すると予期しない動作が発生する可能性があります。この場合、設定にarbitrary_types_allowedを追加するだけです。

import dataclasses

from pydantic import BaseModel, ConfigDict
from pydantic.errors import PydanticSchemaGenerationError


class ArbitraryType:
    def __init__(self, value):
        self.value = value

    def __repr__(self):
        return f'ArbitraryType(value={self.value!r})'


@dataclasses.dataclass
class DC:
    a: ArbitraryType
    b: str


# valid as it is a builtin dataclass without validation
my_dc = DC(a=ArbitraryType(value=3), b='qwe')

try:

    class Model(BaseModel):
        dc: DC
        other: str

    # invalid as it is now a pydantic dataclass
    Model(dc=my_dc, other='other')
except PydanticSchemaGenerationError as e:
    print(e.message)
    """
    Unable to generate pydantic-core schema for <class '__main__.ArbitraryType'>. Set `arbitrary_types_allowed=True` in the model_config to ignore this error or implement `__get_pydantic_core_schema__` on your type to fully support it.

    If you got this error by calling handler(<some type>) within `__get_pydantic_core_schema__` then you likely need to call `handler.generate_schema(<some type>)` since we do not call `__get_pydantic_core_schema__` on `<some type>` otherwise to avoid infinite recursion.
    """


class Model(BaseModel):
    model_config = ConfigDict(arbitrary_types_allowed=True)

    dc: DC
    other: str


m = Model(dc=my_dc, other='other')
print(repr(m))
#> Model(dc=DC(a=ArbitraryType(value=3), b='qwe'), other='other')

データクラスがpydanticデータクラスかどうかを確認する

Pydantic データクラスは依然としてデータクラスとみなされているため、 dataclasses.is_dataclassを使用するとTrueが返されます。型が具体的に pydantic データクラスであるかどうかを確認するには、次のコマンドを使用できます。 pydantic.dataclasses.is_pydantic_dataclass

import dataclasses

import pydantic


@dataclasses.dataclass
class StdLibDataclass:
    id: int


PydanticDataclass = pydantic.dataclasses.dataclass(StdLibDataclass)

print(dataclasses.is_dataclass(StdLibDataclass))
#> True
print(pydantic.dataclasses.is_pydantic_dataclass(StdLibDataclass))
#> False

print(dataclasses.is_dataclass(PydanticDataclass))
#> True
print(pydantic.dataclasses.is_pydantic_dataclass(PydanticDataclass))
#> True

初期化フック

データクラスを初期化するとき、 @model_validatorデコレータmodeパラメータを使用して、検証の_前後__に_コードを実行できます。

from typing import Any, Dict

from typing_extensions import Self

from pydantic import model_validator
from pydantic.dataclasses import dataclass


@dataclass
class Birth:
    year: int
    month: int
    day: int


@dataclass
class User:
    birth: Birth

    @model_validator(mode='before')
    @classmethod
    def pre_root(cls, values: Dict[str, Any]) -> Dict[str, Any]:
        print(f'First: {values}')
        """
        First: ArgsKwargs((), {'birth': {'year': 1995, 'month': 3, 'day': 2}})
        """
        return values

    @model_validator(mode='after')
    def post_root(self) -> Self:
        print(f'Third: {self}')
        #> Third: User(birth=Birth(year=1995, month=3, day=2))
        return self

    def __post_init__(self):
        print(f'Second: {self.birth}')
        #> Second: Birth(year=1995, month=3, day=2)


user = User(**{'birth': {'year': 1995, 'month': 3, 'day': 2}})

Pydantic データクラスの__post_init__バリデーターの_途中_で呼び出されます。順序は次のとおりです。

  • model_validator(mode='before')
  • field_validator(mode='before')
  • field_validator(mode='after')
  • 内部バリデータ。たとえば、 intstrなどの型の検証など。
  • __post_init__
  • model_validator(mode='after')

    from dataclasses import InitVar from pathlib import Path from typing import Optional

    from pydantic.dataclasses import dataclass

    @dataclass class PathData: path: Path base_path: InitVar[Optional[Path]]

    def __post_init__(self, base_path):
        print(f'Received path={self.path!r}, base_path={base_path!r}')
        #> Received path=PosixPath('world'), base_path=PosixPath('/hello')
        if base_path is not None:
            self.path = base_path / self.path
    

    path_data = PathData('world', base_path='/hello')

    Received path='world', base_path='/hello'

    assert path_data.path == Path('/hello/world')

stdlib データクラスとの違い

Python stdlib のdataclasses.dataclassは検証ステップを実行しないため、 __post_init__メソッドのみを実装することに注意してください。

JSONダンプ

Pydantic データクラスには.model_dump_json()関数がありません。それらを JSON としてダンプするには、次のようにRootModelを使用する必要があります。

import dataclasses
from typing import List

from pydantic import RootModel
from pydantic.dataclasses import dataclass


@dataclass
class User:
    id: int
    name: str = 'John Doe'
    friends: List[int] = dataclasses.field(default_factory=lambda: [0])


user = User(id='42')
print(RootModel[User](User(id='42')).model_dump_json(indent=4))
"""
{
    "id": 42,
    "name": "John Doe",
    "friends": [
        0
    ]
}
"""

本文总阅读量