Ga naar inhoud

Typen

Waar mogelijk gebruikt Pydantic standaardbibliotheektypen om velden te definiëren, waardoor de leercurve wordt versoepeld. Voor veel nuttige toepassingen bestaat er echter geen standaard bibliotheektype, dus implementeert Pydantic veel veelgebruikte typen.

Er zijn ook complexere typen die te vinden zijn in het Pydantic Extra Types -pakket.

Als geen enkel bestaand type geschikt is voor uw doel, kunt u ook uw eigen Pydantic-compatibele typen implementeren met aangepaste eigenschappen en validatie.

In de volgende secties worden de typen beschreven die door Pydantic worden ondersteund.

Typeconversie

Tijdens de validatie kan Pydantic gegevens in verwachte typen dwingen.

Er zijn twee vormen van dwang: streng en laks. Zie Conversietabel voor meer details over hoe Pydantic gegevens converteert in zowel strikte als lakse modi.

Zie Strenge modus en Strenge typen voor meer informatie over het inschakelen van strikte dwang.

Strikte typen

Pydantic biedt de volgende strikte typen:

Deze typen komen alleen door de validatie als de gevalideerde waarde van het betreffende type is of een subtype van dat type is.

Beperkte typen

Dit gedrag komt ook aan het licht via het strict veld van de beperkte typen en kan worden gecombineerd met een groot aantal complexe validatieregels. Zie de afzonderlijke typehandtekeningen voor ondersteunde argumenten.

De volgende voorbehouden zijn van toepassing:

  • StrictBytes (en de strict optie van conbytes() ) accepteert zowel bytes als bytearray typen.
  • StrictInt (en de strict optie van conint() ) accepteert geen bool typen, ook al is bool een subklasse van int in Python. Andere subklassen zullen werken.
  • StrictFloat (en de strict optie confloat() ) accepteert geen int .

Naast het bovenstaande kun je ook een FiniteFloat type hebben dat alleen eindige waarden accepteert (dwz niet inf , -inf of nan ).

Aangepaste typen

U kunt ook uw eigen aangepaste gegevenstypen definiëren. Er zijn verschillende manieren om dit te bereiken.

Typen samenstellen via Annotated

PEP 593 introduceerde Annotated als een manier om runtime-metagegevens aan typen te koppelen zonder te veranderen hoe typecheckers deze interpreteren. Pydantic maakt hiervan gebruik om u in staat te stellen typen te maken die identiek zijn aan het originele type wat betreft typecheckers, maar validatie toevoegen, anders serialiseren, enz.

Om bijvoorbeeld een type te maken dat een positieve int vertegenwoordigt:

# 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]
    """

Merk op dat u ook beperkingen van geannoteerde typen kunt gebruiken om dit Pydantic-agnostisch te maken:

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]
    """

Validatie en serialisatie toevoegen

U kunt validatie-, serialisatie- en JSON-schema's aan een willekeurig type toevoegen of overschrijven met behulp van de markeringen die Pydantic exporteert:

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

Generieke geneesmiddelen

U kunt typevariabelen binnen Annotated gebruiken om herbruikbare wijzigingen aan typen aan te brengen:

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]
    """

Benoemde type-aliassen

In de bovenstaande voorbeelden wordt gebruik gemaakt van impliciete typealiassen. Dit betekent dat ze geen title in JSON-schema's kunnen hebben en dat hun schema tussen velden wordt gekopieerd. U kunt PEP 695 's TypeAliasType gebruiken via de typing-extensions backport om benoemde aliassen te maken, waardoor u een nieuw type kunt definiëren zonder subklassen te maken. Dit nieuwe type kan zo simpel zijn als een naam of er is complexe validatielogica aan verbonden:

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',
}
"""

Deze benoemde type-aliassen kunnen ook generiek zijn:

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]
    """

Benoemde recursieve typen

U kunt TypeAliasType ook gebruiken om recursieve typen te maken:

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]
    """

Validatie aanpassen met __get_pydantic_core_schema__

Om uitgebreidere aanpassingen uit te voeren van de manier waarop Pydantic aangepaste klassen afhandelt, en in het bijzonder wanneer u toegang hebt tot de klasse of deze kunt subclassificeren, kunt u een speciaal __get_pydantic_core_schema__ implementeren om Pydantic te vertellen hoe het pydantic-core schema moet worden gegenereerd.

Hoewel pydantic intern pydantic-core gebruikt om validatie en serialisatie af te handelen, is het een nieuwe API voor Pydantic V2, dus het is een van de gebieden die in de toekomst waarschijnlijk zullen worden aangepast en je moet proberen vast te houden aan de ingebouwde constructies zoals die worden geleverd door annotated-types , pydantic.Field of BeforeValidator enzovoort.

U kunt __get_pydantic_core_schema__ zowel op een aangepast type als op metagegevens implementeren die bedoeld zijn om in Annotated te worden geplaatst. In beide gevallen is de API middleware-achtig en vergelijkbaar met die van "wrap"-validators: je krijgt een source_type (wat niet noodzakelijkerwijs hetzelfde is als de klasse, vooral voor generieke geneesmiddelen) en een handler die je kunt aanroepen met een type om de volgende metadata in Annotated aan te roepen of om de interne schemageneratie van Pydantic aan te roepen.

De eenvoudigste no-op-implementatie roept de handler aan met het type dat u krijgt, en retourneert dat vervolgens als resultaat. U kunt er ook voor kiezen om het type te wijzigen voordat u de handler aanroept, het kernschema te wijzigen dat door de handler wordt geretourneerd, of de handler helemaal niet aan te roepen.

Als een methode voor een aangepast type

Het volgende is een voorbeeld van een type dat __get_pydantic_core_schema__ gebruikt om aan te passen hoe het wordt gevalideerd. Dit komt overeen met het implementeren __get_validators__ in 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'

Zie JSON-schema voor meer informatie over het aanpassen van JSON-schema's voor aangepaste typen.

Als annotatie

Vaak wilt u uw aangepaste type parametriseren met meer dan alleen generieke typeparameters (wat u kunt doen via het typesysteem en dat later zal worden besproken). Of het kan u niet echt interesseren (of willen) om een instance van uw subklasse te maken; je wilt eigenlijk het originele type, alleen met wat extra validatie.

Als u bijvoorbeeld pydantic.AfterValidator (zie Validatie en serialisatie toevoegen ) zelf zou implementeren, zou u iets doen dat lijkt op het volgende:

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. De frozen=True -specificatie maakt MyAfterValidator hashbaar. Zonder dit zou een unie zoals Username | None zal een fout opleveren.
  2. Merk op dat typecontroleurs niet zullen klagen over het toewijzen van 'ABC' aan Username zoals ze deden in het vorige voorbeeld, omdat ze Username niet als een ander type beschouwen dan str .

Typen van derden verwerken

Een ander gebruiksscenario voor het patroon in de vorige sectie is het verwerken van typen van derden.

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',
}

U kunt deze aanpak gebruiken om bijvoorbeeld gedrag voor Panda's of Numpy-typen te definiëren.

Gebruik GetPydanticSchema om de boilerplate te verminderen

??? api "API-documentatie" pydantic.types.GetPydanticSchema

Het zal je misschien opvallen dat de bovenstaande voorbeelden, waarin we een markerklasse maken, een flinke hoeveelheid standaardwerk vereisen. Voor veel eenvoudige gevallen kunt u dit aanzienlijk minimaliseren door pydantic.GetPydanticSchema te gebruiken:

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'

Samenvatting

Laten we samenvatten:

  1. Pydantic biedt hooks op hoog niveau om typen aan te passen via Annotated zoals AfterValidator en Field . Gebruik deze indien mogelijk.
  2. Onder de motorkap gebruiken deze pydantic-core om de validatie aan te passen, en je kunt daar rechtstreeks op aansluiten met behulp van GetPydanticSchema of een markerklasse met __get_pydantic_core_schema__ .
  3. Als je echt een aangepast type wilt, kun je __get_pydantic_core_schema__ op het type zelf implementeren.

Aangepaste generieke klassen verwerken

!!! waarschuwing Dit is een geavanceerde techniek die je in het begin misschien niet nodig hebt. In de meeste gevallen zult u waarschijnlijk prima uit de voeten kunnen met standaard Pydantic-modellen.

U kunt generieke klassen als veldtypen gebruiken en aangepaste validatie uitvoeren op basis van de "typeparameters" (of subtypen) met __get_pydantic_core_schema__ .

Als de Generieke klasse die u als subtype gebruikt een klassenmethode __get_pydantic_core_schema__ heeft, hoeft u arbitrary_types_allowed niet te gebruiken om te laten werken.

Omdat de source_type parameter niet hetzelfde is als de cls parameter, kunt u typing.get_args (of typing_extensions.get_args ) gebruiken om de generieke parameters te extraheren. Vervolgens kunt u de handler gebruiken om een schema voor hen te genereren door handler.generate_schema aan te roepen. Houd er rekening mee dat we zoiets niet doen handler(get_args(source_type)[0]) omdat we een niet-gerelateerd schema voor die generieke parameter willen genereren, en niet een schema dat wordt beïnvloed door de huidige context van Annotated metadata en dergelijke. Dit is minder belangrijk voor aangepaste typen, maar cruciaal voor geannoteerde metagegevens die het opbouwen van schema's wijzigen.

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]
    """

Generieke containers

Hetzelfde idee kan worden toegepast om generieke containertypen te maken, zoals een aangepast Sequence :

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]
    """

Toegang tot veldnaam

!!!note Dit was niet mogelijk met Pydantic V2 tot V2.3, het werd opnieuw toegevoegd in Pydantic V2.4.

Vanaf Pydantic V2.4 kunt u toegang krijgen tot de veldnaam via de handler.field_name binnen __get_pydantic_core_schema__ en daardoor de veldnaam instellen die beschikbaar zal zijn via 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'>

U kunt ook toegang krijgen tot field_name via de markeringen die worden gebruikt met Annotated , zoals 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'>

本文总阅读量