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.
- Standardbibliothekstypen – Typen aus der Python-Standardbibliothek.
- Strikte Typen – Typen, die es Ihnen ermöglichen, Zwang durch kompatible Typen zu verhindern.
- Benutzerdefinierte Datentypen – Erstellen Sie Ihre eigenen benutzerdefinierten Datentypen.
- Feldtypkonvertierungen – strikte und laxe Konvertierung zwischen verschiedenen Feldtypen.
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 diestrict
Option vonconbytes()
) akzeptieren sowohlbytes
als auchbytearray
Typen.StrictInt
(und die Optionstrict
vonconint()
) akzeptieren keinebool
-Typen, obwohlbool
eine Unterklasse vonint
in Python ist. Andere Unterklassen funktionieren.StrictFloat
(und diestrict
Option vonconfloat()
) akzeptierenint
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)!
- Die
frozen=True
-Spezifikation machtMyAfterValidator
hashbar. Ohne dies wäre eine Union wieUsername | None
wird einen Fehler auslösen. - Beachten Sie, dass Typprüfer sich nicht über die Zuweisung
'ABC'
zuUsername
beschweren, wie sie es im vorherigen Beispiel getan haben, da sieUsername
nicht als einen vonstr
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:
- Pydantic bietet High-Level-Hooks zum Anpassen von Typen über
Annotated
wieAfterValidator
undField
. Verwenden Sie diese, wenn möglich. - Unter der Haube verwenden diese
pydantic-core
, um die Validierung anzupassen, und Sie können sich direkt daran anschließen, indem SieGetPydanticSchema
oder eine Markierungsklasse mit__get_pydantic_core_schema__
verwenden. - 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'>
本文总阅读量次