Lewati ke isi

Jenis

Jika memungkinkan, Pydantic menggunakan tipe perpustakaan standar untuk menentukan bidang, sehingga memperlancar kurva pembelajaran. Namun, untuk banyak aplikasi yang berguna, tidak ada tipe perpustakaan standar, sehingga Pydantic mengimplementasikan banyak tipe yang umum digunakan.

Ada juga tipe yang lebih kompleks yang dapat ditemukan di paket Pydantic Extra Types .

Jika tidak ada tipe yang sesuai dengan tujuan Anda, Anda juga dapat mengimplementasikan tipe Anda sendiri yang kompatibel dengan Pydantic dengan properti khusus dan validasi.

Bagian berikut menjelaskan tipe yang didukung oleh Pydantic.

Ketik konversi

Selama validasi, Pydantic dapat memaksa data ke dalam tipe yang diharapkan.

Ada dua cara pemaksaan: ketat dan longgar. Lihat Tabel Konversi untuk detail selengkapnya tentang cara Pydantic mengonversi data dalam mode ketat dan longgar.

Lihat Mode Ketat dan Tipe Ketat untuk detail tentang cara mengaktifkan pemaksaan ketat.

Tipe Ketat

Pydantic menyediakan tipe ketat berikut:

Tipe ini hanya akan lolos validasi ketika nilai yang divalidasi adalah tipe yang bersangkutan atau merupakan subtipe dari tipe tersebut.

Tipe terbatas

Perilaku ini juga diekspos melalui bidang strict dari tipe yang dibatasi dan dapat dikombinasikan dengan banyak aturan validasi yang kompleks. Lihat tanda tangan tipe individual untuk argumen yang didukung.

Peringatan berikut ini berlaku:

  • StrictBytes (dan opsi strict conbytes() ) akan menerima tipe bytes , dan bytearray .
  • StrictInt (dan opsi strict dari conint() ) tidak akan menerima tipe bool , meskipun bool adalah subkelas dari int dengan Python. Subkelas lain akan berfungsi.
  • StrictFloat (dan opsi strict confloat() ) tidak akan menerima int .

Selain yang di atas, Anda juga dapat memiliki tipe FiniteFloat yang hanya akan menerima nilai terbatas (yaitu bukan inf , -inf atau nan ).

Jenis Khusus

Anda juga dapat menentukan tipe data kustom Anda sendiri. Ada beberapa cara untuk mencapainya.

PEP 593 memperkenalkan Annotated sebagai cara untuk melampirkan metadata waktu proses ke tipe tanpa mengubah cara pemeriksa tipe menafsirkannya. Pydantic memanfaatkan ini untuk memungkinkan Anda membuat tipe yang identik dengan tipe asli sejauh menyangkut pemeriksa tipe, tetapi menambahkan validasi, membuat serialisasi secara berbeda, dll.

Misalnya, untuk membuat tipe yang mewakili int positif:

# or `from typing import Annotated` for Python 3.9+
from typing_extensions import Annotated

from pydantic import Field, TypeAdapter, ValidationError

PositiveInt = Annotated[int, Field(gt=0)]

ta = TypeAdapter(PositiveInt)

print(ta.validate_python(1))
#> 1

try:
    ta.validate_python(-1)
except ValidationError as exc:
    print(exc)
    """
    1 validation error for constrained-int
      Input should be greater than 0 [type=greater_than, input_value=-1, input_type=int]
    """

Perhatikan bahwa Anda juga dapat menggunakan batasan dari tipe beranotasi untuk membuat agnostik Pydantic ini:

from annotated_types import Gt
from typing_extensions import Annotated

from pydantic import TypeAdapter, ValidationError

PositiveInt = Annotated[int, Gt(0)]

ta = TypeAdapter(PositiveInt)

print(ta.validate_python(1))
#> 1

try:
    ta.validate_python(-1)
except ValidationError as exc:
    print(exc)
    """
    1 validation error for constrained-int
      Input should be greater than 0 [type=greater_than, input_value=-1, input_type=int]
    """

Menambahkan validasi dan serialisasi

Anda dapat menambahkan atau mengganti validasi, serialisasi, dan skema JSON ke tipe arbitrer menggunakan penanda yang diekspor Pydantic:

from typing_extensions import Annotated

from pydantic import (
    AfterValidator,
    PlainSerializer,
    TypeAdapter,
    WithJsonSchema,
)

TruncatedFloat = Annotated[
    float,
    AfterValidator(lambda x: round(x, 1)),
    PlainSerializer(lambda x: f'{x:.1e}', return_type=str),
    WithJsonSchema({'type': 'string'}, mode='serialization'),
]


ta = TypeAdapter(TruncatedFloat)

input = 1.02345
assert input != 1.0

assert ta.validate_python(input) == 1.0

assert ta.dump_json(input) == b'"1.0e+00"'

assert ta.json_schema(mode='validation') == {'type': 'number'}
assert ta.json_schema(mode='serialization') == {'type': 'string'}

Generik

Anda dapat menggunakan variabel tipe dalam Annotated untuk membuat modifikasi tipe yang dapat digunakan kembali:

from typing import Any, List, Sequence, TypeVar

from annotated_types import Gt, Len
from typing_extensions import Annotated

from pydantic import ValidationError
from pydantic.type_adapter import TypeAdapter

SequenceType = TypeVar('SequenceType', bound=Sequence[Any])


ShortSequence = Annotated[SequenceType, Len(max_length=10)]


ta = TypeAdapter(ShortSequence[List[int]])

v = ta.validate_python([1, 2, 3, 4, 5])
assert v == [1, 2, 3, 4, 5]

try:
    ta.validate_python([1] * 100)
except ValidationError as exc:
    print(exc)
    """
    1 validation error for list[int]
      List should have at most 10 items after validation, not 100 [type=too_long, input_value=[1, 1, 1, 1, 1, 1, 1, 1, ... 1, 1, 1, 1, 1, 1, 1, 1], input_type=list]
    """


T = TypeVar('T')  # or a bound=SupportGt

PositiveList = List[Annotated[T, Gt(0)]]

ta = TypeAdapter(PositiveList[float])

v = ta.validate_python([1])
assert type(v[0]) is float


try:
    ta.validate_python([-1])
except ValidationError as exc:
    print(exc)
    """
    1 validation error for list[constrained-float]
    0
      Input should be greater than 0 [type=greater_than, input_value=-1, input_type=int]
    """

Alias tipe bernama

Contoh di atas menggunakan alias tipe implisit. Artinya, mereka tidak akan dapat memiliki title dalam skema JSON dan skemanya akan disalin antar kolom. Anda dapat menggunakan TypeAliasType PEP 695 melalui backport ekstensi pengetikannya untuk membuat alias bernama, memungkinkan Anda menentukan tipe baru tanpa membuat subkelas. Tipe baru ini bisa sesederhana sebuah nama atau memiliki logika validasi kompleks yang melekat padanya:

from typing import List

from annotated_types import Gt
from typing_extensions import Annotated, TypeAliasType

from pydantic import BaseModel

ImplicitAliasPositiveIntList = List[Annotated[int, Gt(0)]]


class Model1(BaseModel):
    x: ImplicitAliasPositiveIntList
    y: ImplicitAliasPositiveIntList


print(Model1.model_json_schema())
"""
{
    'properties': {
        'x': {
            'items': {'exclusiveMinimum': 0, 'type': 'integer'},
            'title': 'X',
            'type': 'array',
        },
        'y': {
            'items': {'exclusiveMinimum': 0, 'type': 'integer'},
            'title': 'Y',
            'type': 'array',
        },
    },
    'required': ['x', 'y'],
    'title': 'Model1',
    'type': 'object',
}
"""

PositiveIntList = TypeAliasType('PositiveIntList', List[Annotated[int, Gt(0)]])


class Model2(BaseModel):
    x: PositiveIntList
    y: PositiveIntList


print(Model2.model_json_schema())
"""
{
    '$defs': {
        'PositiveIntList': {
            'items': {'exclusiveMinimum': 0, 'type': 'integer'},
            'type': 'array',
        }
    },
    'properties': {
        'x': {'$ref': '#/$defs/PositiveIntList'},
        'y': {'$ref': '#/$defs/PositiveIntList'},
    },
    'required': ['x', 'y'],
    'title': 'Model2',
    'type': 'object',
}
"""

Alias tipe bernama ini juga bisa bersifat generik:

from typing import Generic, List, TypeVar

from annotated_types import Gt
from typing_extensions import Annotated, TypeAliasType

from pydantic import BaseModel, ValidationError

T = TypeVar('T')  # or a `bound=SupportGt`

PositiveList = TypeAliasType(
    'PositiveList', List[Annotated[T, Gt(0)]], type_params=(T,)
)


class Model(BaseModel, Generic[T]):
    x: PositiveList[T]


assert Model[int].model_validate_json('{"x": ["1"]}').x == [1]

try:
    Model[int](x=[-1])
except ValidationError as exc:
    print(exc)
    """
    1 validation error for Model[int]
    x.0
      Input should be greater than 0 [type=greater_than, input_value=-1, input_type=int]
    """

Dinamakan tipe rekursif

Anda juga dapat menggunakan TypeAliasType untuk membuat tipe rekursif:

from typing import Any, Dict, List, Union

from pydantic_core import PydanticCustomError
from typing_extensions import Annotated, TypeAliasType

from pydantic import (
    TypeAdapter,
    ValidationError,
    ValidationInfo,
    ValidatorFunctionWrapHandler,
    WrapValidator,
)


def json_custom_error_validator(
    value: Any, handler: ValidatorFunctionWrapHandler, _info: ValidationInfo
) -> Any:
    """Simplify the error message to avoid a gross error stemming
    from exhaustive checking of all union options.
    """
    try:
        return handler(value)
    except ValidationError:
        raise PydanticCustomError(
            'invalid_json',
            'Input is not valid json',
        )


Json = TypeAliasType(
    'Json',
    Annotated[
        Union[Dict[str, 'Json'], List['Json'], str, int, float, bool, None],
        WrapValidator(json_custom_error_validator),
    ],
)


ta = TypeAdapter(Json)

v = ta.validate_python({'x': [1], 'y': {'z': True}})
assert v == {'x': [1], 'y': {'z': True}}

try:
    ta.validate_python({'x': object()})
except ValidationError as exc:
    print(exc)
    """
    1 validation error for function-wrap[json_custom_error_validator()]
      Input is not valid json [type=invalid_json, input_value={'x': <object object at 0x0123456789ab>}, input_type=dict]
    """

Menyesuaikan validasi dengan __get_pydantic_core_schema__

Untuk melakukan penyesuaian yang lebih luas tentang cara Pydantic menangani kelas khusus, dan khususnya ketika Anda memiliki akses ke kelas tersebut atau dapat membuat subkelasnya, Anda dapat menerapkan __get_pydantic_core_schema__ khusus untuk memberi tahu Pydantic cara menghasilkan skema pydantic-core .

Meskipun pydantic menggunakan pydantic-core secara internal untuk menangani validasi dan serialisasi, ini adalah API baru untuk Pydantic V2, sehingga ini adalah salah satu area yang paling mungkin untuk diubah di masa mendatang dan Anda harus mencoba untuk tetap menggunakan konstruksi bawaan seperti yang disediakan oleh annotated-types , pydantic.Field , atau BeforeValidator dan seterusnya.

Anda dapat menerapkan __get_pydantic_core_schema__ pada tipe khusus dan metadata yang dimaksudkan untuk dimasukkan ke dalam Annotated . Dalam kedua kasus tersebut, API-nya mirip middleware dan mirip dengan validator "wrap": Anda mendapatkan source_type (yang belum tentu sama dengan kelasnya, khususnya untuk obat generik) dan sebuah handler yang dapat Anda panggil dengan sebuah tipe untuk memanggil metadata berikutnya di Annotated atau memanggil pembuatan skema internal Pydantic.

Implementasi no-op yang paling sederhana memanggil handler dengan tipe yang diberikan kepada Anda, lalu mengembalikannya sebagai hasilnya. Anda juga dapat memilih untuk mengubah tipe sebelum memanggil penangan, mengubah skema inti yang dikembalikan oleh penangan, atau tidak memanggil penangan sama sekali.

Sebagai metode pada tipe khusus

Berikut ini adalah contoh tipe yang menggunakan __get_pydantic_core_schema__ untuk menyesuaikan cara validasinya. Ini setara dengan penerapan __get_validators__ di Pydantic V1.

from typing import Any

from pydantic_core import CoreSchema, core_schema

from pydantic import GetCoreSchemaHandler, TypeAdapter


class Username(str):
    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> CoreSchema:
        return core_schema.no_info_after_validator_function(cls, handler(str))


ta = TypeAdapter(Username)
res = ta.validate_python('abc')
assert isinstance(res, Username)
assert res == 'abc'

Lihat Skema JSON untuk detail selengkapnya tentang cara menyesuaikan skema JSON untuk tipe kustom.

Sebagai anotasi

Seringkali Anda ingin membuat parameter tipe kustom Anda dengan lebih dari sekadar parameter tipe generik (yang dapat Anda lakukan melalui sistem tipe dan akan dibahas nanti). Atau Anda mungkin tidak benar-benar peduli (atau ingin) membuat instance dari subkelas Anda; Anda sebenarnya menginginkan tipe aslinya, hanya dengan beberapa validasi tambahan yang dilakukan.

Misalnya, jika Anda mengimplementasikan pydantic.AfterValidator (lihat Menambahkan validasi dan serialisasi ) sendiri, Anda akan melakukan hal serupa seperti berikut:

from dataclasses import dataclass
from typing import Any, Callable

from pydantic_core import CoreSchema, core_schema
from typing_extensions import Annotated

from pydantic import BaseModel, GetCoreSchemaHandler


@dataclass(frozen=True)  # (1)!
class MyAfterValidator:
    func: Callable[[Any], Any]

    def __get_pydantic_core_schema__(
        self, source_type: Any, handler: GetCoreSchemaHandler
    ) -> CoreSchema:
        return core_schema.no_info_after_validator_function(
            self.func, handler(source_type)
        )


Username = Annotated[str, MyAfterValidator(str.lower)]


class Model(BaseModel):
    name: Username


assert Model(name='ABC').name == 'abc'  # (2)!
  1. Spesifikasi frozen=True membuat MyAfterValidator dapat di-hash. Tanpa ini, gabungan seperti Username | None yang akan menimbulkan kesalahan.
  2. Perhatikan bahwa pemeriksa tipe tidak akan mengeluh tentang menugaskan 'ABC' ke Username seperti yang mereka lakukan pada contoh sebelumnya karena mereka tidak menganggap Username sebagai tipe yang berbeda dari str .

Menangani tipe pihak ketiga

Kasus penggunaan lain untuk pola di bagian sebelumnya adalah untuk menangani tipe pihak ketiga.

from typing import Any

from pydantic_core import core_schema
from typing_extensions import Annotated

from pydantic import (
    BaseModel,
    GetCoreSchemaHandler,
    GetJsonSchemaHandler,
    ValidationError,
)
from pydantic.json_schema import JsonSchemaValue


class ThirdPartyType:
    """
    This is meant to represent a type from a third-party library that wasn't designed with Pydantic
    integration in mind, and so doesn't have a `pydantic_core.CoreSchema` or anything.
    """

    x: int

    def __init__(self):
        self.x = 0


class _ThirdPartyTypePydanticAnnotation:
    @classmethod
    def __get_pydantic_core_schema__(
        cls,
        _source_type: Any,
        _handler: GetCoreSchemaHandler,
    ) -> core_schema.CoreSchema:
        """
        We return a pydantic_core.CoreSchema that behaves in the following ways:

        * ints will be parsed as `ThirdPartyType` instances with the int as the x attribute
        * `ThirdPartyType` instances will be parsed as `ThirdPartyType` instances without any changes
        * Nothing else will pass validation
        * Serialization will always return just an int
        """

        def validate_from_int(value: int) -> ThirdPartyType:
            result = ThirdPartyType()
            result.x = value
            return result

        from_int_schema = core_schema.chain_schema(
            [
                core_schema.int_schema(),
                core_schema.no_info_plain_validator_function(validate_from_int),
            ]
        )

        return core_schema.json_or_python_schema(
            json_schema=from_int_schema,
            python_schema=core_schema.union_schema(
                [
                    # check if it's an instance first before doing any further work
                    core_schema.is_instance_schema(ThirdPartyType),
                    from_int_schema,
                ]
            ),
            serialization=core_schema.plain_serializer_function_ser_schema(
                lambda instance: instance.x
            ),
        )

    @classmethod
    def __get_pydantic_json_schema__(
        cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
    ) -> JsonSchemaValue:
        # Use the same schema that would be used for `int`
        return handler(core_schema.int_schema())


# We now create an `Annotated` wrapper that we'll use as the annotation for fields on `BaseModel`s, etc.
PydanticThirdPartyType = Annotated[
    ThirdPartyType, _ThirdPartyTypePydanticAnnotation
]


# Create a model class that uses this annotation as a field
class Model(BaseModel):
    third_party_type: PydanticThirdPartyType


# Demonstrate that this field is handled correctly, that ints are parsed into `ThirdPartyType`, and that
# these instances are also "dumped" directly into ints as expected.
m_int = Model(third_party_type=1)
assert isinstance(m_int.third_party_type, ThirdPartyType)
assert m_int.third_party_type.x == 1
assert m_int.model_dump() == {'third_party_type': 1}

# Do the same thing where an instance of ThirdPartyType is passed in
instance = ThirdPartyType()
assert instance.x == 0
instance.x = 10

m_instance = Model(third_party_type=instance)
assert isinstance(m_instance.third_party_type, ThirdPartyType)
assert m_instance.third_party_type.x == 10
assert m_instance.model_dump() == {'third_party_type': 10}

# Demonstrate that validation errors are raised as expected for invalid inputs
try:
    Model(third_party_type='a')
except ValidationError as e:
    print(e)
    """
    2 validation errors for Model
    third_party_type.is-instance[ThirdPartyType]
      Input should be an instance of ThirdPartyType [type=is_instance_of, input_value='a', input_type=str]
    third_party_type.chain[int,function-plain[validate_from_int()]]
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    """


assert Model.model_json_schema() == {
    'properties': {
        'third_party_type': {'title': 'Third Party Type', 'type': 'integer'}
    },
    'required': ['third_party_type'],
    'title': 'Model',
    'type': 'object',
}

Anda dapat menggunakan pendekatan ini misalnya untuk menentukan perilaku untuk tipe Pandas atau Numpy.

Menggunakan GetPydanticSchema untuk mengurangi boilerplate

??? api "Dokumentasi API" pydantic.types.GetPydanticSchema

Anda mungkin memperhatikan bahwa contoh di atas saat kita membuat kelas marker memerlukan jumlah boilerplate yang cukup. Untuk banyak kasus sederhana, Anda dapat meminimalkannya dengan menggunakan pydantic.GetPydanticSchema :

from pydantic_core import core_schema
from typing_extensions import Annotated

from pydantic import BaseModel, GetPydanticSchema


class Model(BaseModel):
    y: Annotated[
        str,
        GetPydanticSchema(
            lambda tp, handler: core_schema.no_info_after_validator_function(
                lambda x: x * 2, handler(tp)
            )
        ),
    ]


assert Model(y='ab').y == 'abab'

Ringkasan

Mari kita rekap:

  1. Pydantic menyediakan kait tingkat tinggi untuk menyesuaikan tipe melalui Annotated seperti AfterValidator dan Field . Gunakan ini jika memungkinkan.
  2. Di bawah tenda ini menggunakan pydantic-core untuk menyesuaikan validasi, dan Anda dapat menghubungkannya secara langsung menggunakan GetPydanticSchema atau kelas penanda dengan __get_pydantic_core_schema__ .
  3. Jika Anda benar-benar menginginkan tipe khusus, Anda dapat menerapkan __get_pydantic_core_schema__ pada tipe itu sendiri.

Menangani kelas generik khusus

!!! peringatan Ini adalah teknik lanjutan yang mungkin tidak Anda perlukan pada awalnya. Dalam sebagian besar kasus, Anda mungkin akan baik-baik saja dengan model Pydantic standar.

Anda dapat menggunakan Kelas Generik sebagai tipe bidang dan melakukan validasi khusus berdasarkan "parameter tipe" (atau subtipe) dengan __get_pydantic_core_schema__ .

Jika kelas Generik yang Anda gunakan sebagai subtipe memiliki metode kelas __get_pydantic_core_schema__ , Anda tidak perlu menggunakan arbitrary_types_allowed agar dapat berfungsi.

Karena parameter source_type tidak sama dengan parameter cls , Anda dapat menggunakan typing.get_args (atau typing_extensions.get_args ) untuk mengekstrak parameter generik. Kemudian Anda dapat menggunakan handler untuk membuat skema dengan memanggil handler.generate_schema . Perhatikan bahwa kami tidak melakukan hal seperti itu handler(get_args(source_type)[0]) karena kami ingin membuat skema yang tidak terkait untuk parameter generik tersebut, bukan skema yang dipengaruhi oleh konteks metadata Annotated saat ini dan semacamnya. Hal ini kurang penting untuk tipe kustom, namun penting untuk metadata beranotasi yang mengubah pembuatan skema.

from dataclasses import dataclass
from typing import Any, Generic, TypeVar

from pydantic_core import CoreSchema, core_schema
from typing_extensions import get_args, get_origin

from pydantic import (
    BaseModel,
    GetCoreSchemaHandler,
    ValidationError,
    ValidatorFunctionWrapHandler,
)

ItemType = TypeVar('ItemType')


# This is not a pydantic model, it's an arbitrary generic class
@dataclass
class Owner(Generic[ItemType]):
    name: str
    item: ItemType

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> CoreSchema:
        origin = get_origin(source_type)
        if origin is None:  # used as `x: Owner` without params
            origin = source_type
            item_tp = Any
        else:
            item_tp = get_args(source_type)[0]
        # both calling handler(...) and handler.generate_schema(...)
        # would work, but prefer the latter for conceptual and consistency reasons
        item_schema = handler.generate_schema(item_tp)

        def val_item(
            v: Owner[Any], handler: ValidatorFunctionWrapHandler
        ) -> Owner[Any]:
            v.item = handler(v.item)
            return v

        python_schema = core_schema.chain_schema(
            # `chain_schema` means do the following steps in order:
            [
                # Ensure the value is an instance of Owner
                core_schema.is_instance_schema(cls),
                # Use the item_schema to validate `items`
                core_schema.no_info_wrap_validator_function(
                    val_item, item_schema
                ),
            ]
        )

        return core_schema.json_or_python_schema(
            # for JSON accept an object with name and item keys
            json_schema=core_schema.chain_schema(
                [
                    core_schema.typed_dict_schema(
                        {
                            'name': core_schema.typed_dict_field(
                                core_schema.str_schema()
                            ),
                            'item': core_schema.typed_dict_field(item_schema),
                        }
                    ),
                    # after validating the json data convert it to python
                    core_schema.no_info_before_validator_function(
                        lambda data: Owner(
                            name=data['name'], item=data['item']
                        ),
                        # note that we re-use the same schema here as below
                        python_schema,
                    ),
                ]
            ),
            python_schema=python_schema,
        )


class Car(BaseModel):
    color: str


class House(BaseModel):
    rooms: int


class Model(BaseModel):
    car_owner: Owner[Car]
    home_owner: Owner[House]


model = Model(
    car_owner=Owner(name='John', item=Car(color='black')),
    home_owner=Owner(name='James', item=House(rooms=3)),
)
print(model)
"""
car_owner=Owner(name='John', item=Car(color='black')) home_owner=Owner(name='James', item=House(rooms=3))
"""

try:
    # If the values of the sub-types are invalid, we get an error
    Model(
        car_owner=Owner(name='John', item=House(rooms=3)),
        home_owner=Owner(name='James', item=Car(color='black')),
    )
except ValidationError as e:
    print(e)
    """
    2 validation errors for Model
    wine
      Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='Kinda good', input_type=str]
    cheese
      Input should be a valid boolean, unable to interpret input [type=bool_parsing, input_value='yeah', input_type=str]
    """

# Similarly with JSON
model = Model.model_validate_json(
    '{"car_owner":{"name":"John","item":{"color":"black"}},"home_owner":{"name":"James","item":{"rooms":3}}}'
)
print(model)
"""
car_owner=Owner(name='John', item=Car(color='black')) home_owner=Owner(name='James', item=House(rooms=3))
"""

try:
    Model.model_validate_json(
        '{"car_owner":{"name":"John","item":{"rooms":3}},"home_owner":{"name":"James","item":{"color":"black"}}}'
    )
except ValidationError as e:
    print(e)
    """
    2 validation errors for Model
    car_owner.item.color
      Field required [type=missing, input_value={'rooms': 3}, input_type=dict]
    home_owner.item.rooms
      Field required [type=missing, input_value={'color': 'black'}, input_type=dict]
    """

Wadah umum

Ide yang sama dapat diterapkan untuk membuat tipe kontainer generik, seperti tipe Sequence kustom:

from typing import Any, Sequence, TypeVar

from pydantic_core import ValidationError, core_schema
from typing_extensions import get_args

from pydantic import BaseModel, GetCoreSchemaHandler

T = TypeVar('T')


class MySequence(Sequence[T]):
    def __init__(self, v: Sequence[T]):
        self.v = v

    def __getitem__(self, i):
        return self.v[i]

    def __len__(self):
        return len(self.v)

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source: Any, handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        instance_schema = core_schema.is_instance_schema(cls)

        args = get_args(source)
        if args:
            # replace the type and rely on Pydantic to generate the right schema
            # for `Sequence`
            sequence_t_schema = handler.generate_schema(Sequence[args[0]])
        else:
            sequence_t_schema = handler.generate_schema(Sequence)

        non_instance_schema = core_schema.no_info_after_validator_function(
            MySequence, sequence_t_schema
        )
        return core_schema.union_schema([instance_schema, non_instance_schema])


class M(BaseModel):
    model_config = dict(validate_default=True)

    s1: MySequence = [3]


m = M()
print(m)
#> s1=<__main__.MySequence object at 0x0123456789ab>
print(m.s1.v)
#> [3]


class M(BaseModel):
    s1: MySequence[int]


M(s1=[1])
try:
    M(s1=['a'])
except ValidationError as exc:
    print(exc)
    """
    2 validation errors for M
    s1.is-instance[MySequence]
      Input should be an instance of MySequence [type=is_instance_of, input_value=['a'], input_type=list]
    s1.function-after[MySequence(), json-or-python[json=list[int],python=chain[is-instance[Sequence],function-wrap[sequence_validator()]]]].0
      Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
    """

Akses ke nama bidang

!!!catatan Ini tidak mungkin dilakukan dengan Pydantic V2 ke V2.3, ini ditambahkan kembali di Pydantic V2.4.

Pada Pydantic V2.4, Anda dapat mengakses nama bidang melalui handler.field_name dalam __get_pydantic_core_schema__ dan dengan demikian mengatur nama bidang yang akan tersedia dari info.field_name .

from typing import Any

from pydantic_core import core_schema

from pydantic import BaseModel, GetCoreSchemaHandler, ValidationInfo


class CustomType:
    """Custom type that stores the field it was used in."""

    def __init__(self, value: int, field_name: str):
        self.value = value
        self.field_name = field_name

    def __repr__(self):
        return f'CustomType<{self.value} {self.field_name!r}>'

    @classmethod
    def validate(cls, value: int, info: ValidationInfo):
        return cls(value, info.field_name)

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        return core_schema.with_info_after_validator_function(
            cls.validate, handler(int), field_name=handler.field_name
        )


class MyModel(BaseModel):
    my_field: CustomType


m = MyModel(my_field=1)
print(m.my_field)
#> CustomType<1 'my_field'>

Anda juga dapat mengakses field_name dari penanda yang digunakan dengan Annotated , seperti [AfterValidator][pydantic.function_validators.AfterValidator].

from typing_extensions import Annotated

from pydantic import AfterValidator, BaseModel, ValidationInfo


def my_validators(value: int, info: ValidationInfo):
    return f'<{value} {info.field_name!r}>'


class MyModel(BaseModel):
    my_field: Annotated[int, AfterValidator(my_validators)]


m = MyModel(my_field=1)
print(m.my_field)
#> <1 'my_field'>

本文总阅读量