Zum Inhalt

Typen

Wo möglich, verwendet Pydantic Standardbibliothekstypen , um Felder zu definieren und so die Lernkurve zu vereinfachen. Für viele nützliche Anwendungen existiert jedoch kein Standardbibliothekstyp, sodass Pydantic viele häufig verwendete Typen implementiert.

Es gibt auch komplexere Typen, die im Pydantic Extra Types -Paket zu finden sind.

Wenn kein vorhandener Typ Ihren Zweck erfüllt, können Sie auch Ihre eigenen Pydantic-kompatiblen Typen mit benutzerdefinierten Eigenschaften und Validierung implementieren.

In den folgenden Abschnitten werden die von Pydantic unterstützten Typen beschrieben.

Typkonvertierung

Während der Validierung kann Pydantic Daten in erwartete Typen umwandeln.

Es gibt zwei Arten von Zwang: streng und lax. Weitere Informationen dazu, wie Pydantic Daten sowohl im strengen als auch im laxen Modus konvertiert, finden Sie in der Konvertierungstabelle .

Einzelheiten zum Aktivieren der strikten Erzwingung finden Sie unter Strikter Modus und Strikte Typen .

Strenge Typen

Pydantic bietet die folgenden strengen Typen:

Diese Typen bestehen die Validierung nur, wenn der validierte Wert vom jeweiligen Typ oder einem Untertyp dieses Typs ist.

Eingeschränkte Typen

Dieses Verhalten wird auch über das strict Feld der eingeschränkten Typen offengelegt und kann mit einer Vielzahl komplexer Validierungsregeln kombiniert werden. Die unterstützten Argumente finden Sie in den einzelnen Typsignaturen.

Es gelten folgende Einschränkungen:

  • StrictBytes (und die strict Option von conbytes() ) akzeptieren sowohl bytes als auch bytearray Typen.
  • StrictInt (und die Option strict von conint() ) akzeptieren keine bool -Typen, obwohl bool eine Unterklasse von int in Python ist. Andere Unterklassen funktionieren.
  • StrictFloat (und die strict Option von confloat() ) akzeptieren int nicht.

Darüber hinaus können Sie auch einen Typ FiniteFloat haben, der nur endliche Werte akzeptiert (d. h. nicht inf , -inf oder nan ).

Benutzerdefinierte Typen

Sie können auch Ihre eigenen benutzerdefinierten Datentypen definieren. Es gibt mehrere Möglichkeiten, dies zu erreichen.

Verfassen von Typen über Annotated

Mit PEP 593 wurde Annotated eingeführt, um Laufzeitmetadaten an Typen anzuhängen, ohne die Art und Weise zu ändern, wie Typprüfer sie interpretieren. Pydantic macht sich dies zunutze, um Ihnen die Erstellung von Typen zu ermöglichen, die für Typprüfer mit dem Originaltyp identisch sind, aber eine Validierung hinzufügen, anders serialisieren usw.

Um beispielsweise einen Typ zu erstellen, der ein positives int darstellt:

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

Beachten Sie, dass Sie auch Einschränkungen von annotierten Typen verwenden können, um dies Pydantic-agnostisch zu machen:

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

Validierung und Serialisierung hinzufügen

Sie können Validierungs-, Serialisierungs- und JSON-Schemas zu einem beliebigen Typ hinzufügen oder überschreiben, indem Sie die von Pydantic exportierten Markierungen verwenden:

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

Generika

Sie können Typvariablen in Annotated verwenden, um wiederverwendbare Änderungen an Typen vorzunehmen:

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

Benannte Typaliase

In den obigen Beispielen werden implizite Typaliase verwendet. Das bedeutet, dass sie keinen title in JSON-Schemas haben können und ihr Schema zwischen Feldern kopiert wird. Sie können TypeAliasType von PEP 695 über seinen Typing-Extensions -Backport verwenden, um benannte Aliase zu erstellen, sodass Sie einen neuen Typ definieren können, ohne Unterklassen zu erstellen. Dieser neue Typ kann so einfach wie ein Name sein oder mit einer komplexen Validierungslogik verbunden sein:

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

Diese benannten Typaliase können auch generisch sein:

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

Benannte rekursive Typen

Sie können TypeAliasType auch verwenden, um rekursive Typen zu erstellen:

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

Anpassen der Validierung mit __get_pydantic_core_schema__

Um die Art und Weise, wie Pydantic mit benutzerdefinierten Klassen umgeht, umfassender anzupassen, insbesondere wenn Sie Zugriff auf die Klasse haben oder sie in Unterklassen umwandeln können, können Sie ein spezielles __get_pydantic_core_schema__ implementieren, um Pydantic mitzuteilen, wie das pydantic-core -Schema generiert werden soll.

Während pydantic intern pydantic-core für die Validierung und Serialisierung verwendet, handelt es sich um eine neue API für Pydantic V2. Daher ist es einer der Bereiche, in denen in Zukunft am wahrscheinlichsten Änderungen vorgenommen werden, und Sie sollten versuchen, sich an die integrierten Konstrukte wie z diejenigen, die von annotated-types , pydantic.Field oder BeforeValidator usw. bereitgestellt werden.

Sie können __get_pydantic_core_schema__ sowohl für einen benutzerdefinierten Typ als auch für Metadaten implementieren, die in Annotated eingefügt werden sollen. In beiden Fällen ist die API Middleware-ähnlich und ähnelt der von „Wrap“-Validatoren: Sie erhalten einen source_type (der nicht unbedingt mit der Klasse identisch ist, insbesondere bei Generika) und einen handler , den Sie mit einem Typ aufrufen können um entweder die nächsten Metadaten in Annotated aufzurufen oder die interne Schemagenerierung von Pydantic aufzurufen.

Die einfachste No-Op-Implementierung ruft den Handler mit dem Typ auf, den Sie erhalten, und gibt diesen dann als Ergebnis zurück. Sie können auch den Typ ändern, bevor Sie den Handler aufrufen, das vom Handler zurückgegebene Kernschema ändern oder den Handler überhaupt nicht aufrufen.

Als Methode für einen benutzerdefinierten Typ

Das Folgende ist ein Beispiel für einen Typ, der __get_pydantic_core_schema__ verwendet, um anzupassen, wie er validiert wird. Dies entspricht der Implementierung __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'

Weitere Informationen zum Anpassen von JSON-Schemas für benutzerdefinierte Typen finden Sie unter JSON-Schema .

Als Anmerkung

Häufig möchten Sie Ihren benutzerdefinierten Typ über mehr als nur generische Typparameter parametrisieren (was über das Typsystem möglich ist und später erläutert wird). Oder es ist Ihnen vielleicht egal (oder Sie wollen es nicht), eine Instanz Ihrer Unterklasse zu erstellen; Sie möchten tatsächlich den ursprünglichen Typ, nur mit einer zusätzlichen Validierung.

Wenn Sie beispielsweise pydantic.AfterValidator (siehe Hinzufügen von Validierung und Serialisierung ) selbst implementieren würden, würden Sie etwa Folgendes tun:

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. Die frozen=True -Spezifikation macht MyAfterValidator hashbar. Ohne dies wäre eine Union wie Username | None wird einen Fehler auslösen.
  2. Beachten Sie, dass Typprüfer sich nicht über die Zuweisung 'ABC' zu Username beschweren, wie sie es im vorherigen Beispiel getan haben, da sie Username nicht als einen von str unterschiedlichen Typ betrachten.

Umgang mit Typen von Drittanbietern

Ein weiterer Anwendungsfall für das Muster im vorherigen Abschnitt ist die Verarbeitung von Drittanbietertypen.

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

Mit diesem Ansatz können Sie beispielsweise das Verhalten für Pandas- oder Numpy-Typen definieren.

Verwenden von GetPydanticSchema zur Reduzierung des Boilerplates

??? API „API-Dokumentation“ pydantic.types.GetPydanticSchema

Möglicherweise stellen Sie fest, dass die obigen Beispiele, in denen wir eine Markerklasse erstellen, eine Menge Boilerplate erfordern. In vielen einfachen Fällen können Sie dies durch die Verwendung von pydantic.GetPydanticSchema erheblich minimieren:

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'

Zusammenfassung

Fassen wir noch einmal zusammen:

  1. Pydantic bietet High-Level-Hooks zum Anpassen von Typen über Annotated wie AfterValidator und Field . Verwenden Sie diese, wenn möglich.
  2. Unter der Haube verwenden diese pydantic-core , um die Validierung anzupassen, und Sie können sich direkt daran anschließen, indem Sie GetPydanticSchema oder eine Markierungsklasse mit __get_pydantic_core_schema__ verwenden.
  3. Wenn Sie wirklich einen benutzerdefinierten Typ wünschen, können Sie __get_pydantic_core_schema__ für den Typ selbst implementieren.

Umgang mit benutzerdefinierten generischen Klassen

!!! Warnung Dies ist eine fortgeschrittene Technik, die Sie am Anfang möglicherweise nicht benötigen. In den meisten Fällen werden Sie wahrscheinlich mit Standard-Pydantic-Modellen zufrieden sein.

Sie können generische Klassen als Feldtypen verwenden und eine benutzerdefinierte Validierung basierend auf den „Typparametern“ (oder Untertypen) mit __get_pydantic_core_schema__ durchführen.

Wenn die generische Klasse, die Sie als Untertyp verwenden, über eine Klassenmethode __get_pydantic_core_schema__ verfügt, müssen Sie arbitrary_types_allowed nicht verwenden, damit sie funktioniert.

Da der Parameter source_type nicht mit dem Parameter cls identisch ist, können Sie typing.get_args (oder typing_extensions.get_args ) verwenden, um die generischen Parameter zu extrahieren. Anschließend können Sie den handler verwenden, um ein Schema für sie zu generieren, indem Sie handler.generate_schema aufrufen. Beachten Sie, dass wir so etwas nicht tun handler(get_args(source_type)[0]) weil wir ein unabhängiges Schema für diesen generischen Parameter generieren möchten, nicht eines, das vom aktuellen Kontext der Annotated Metadaten und dergleichen beeinflusst wird. Dies ist für benutzerdefinierte Typen weniger wichtig, aber entscheidend für annotierte Metadaten, die die Schemaerstellung ändern.

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

Generische Container

Die gleiche Idee kann zum Erstellen generischer Containertypen angewendet werden, beispielsweise eines benutzerdefinierten 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]
    """

Zugriff auf Feldnamen

!!!Hinweis: Dies war mit Pydantic V2 bis V2.3 nicht möglich, es wurde in Pydantic V2.4 erneut hinzugefügt .

Ab Pydantic V2.4 können Sie über den handler.field_name in __get_pydantic_core_schema__ auf den Feldnamen zugreifen und so den Feldnamen festlegen, der über info.field_name verfügbar sein wird.

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

Sie können auch über die mit Annotated verwendeten Markierungen auf field_name zugreifen, z. B. [AfterValidator][pydantic.Functional_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'>

本文总阅读量