Types
Dans la mesure du possible, Pydantic utilise des types de bibliothèques standard pour définir les champs, lissant ainsi la courbe d'apprentissage. Cependant, pour de nombreuses applications utiles, il n'existe aucun type de bibliothèque standard, c'est pourquoi Pydantic implémente de nombreux types couramment utilisés.
Il existe également des types plus complexes qui peuvent être trouvés dans le package Pydantic Extra Types .
Si aucun type existant ne convient à votre objectif, vous pouvez également implémenter vos propres types compatibles Pydantic avec des propriétés et une validation personnalisées.
Les sections suivantes décrivent les types pris en charge par Pydantic.
- Types de bibliothèque standard: types de la bibliothèque standard Python.
- Types stricts: types qui vous permettent d'empêcher la coercition de types compatibles.
- Types de données personnalisés : créez vos propres types de données personnalisés.
- Conversions de types de champs – conversion stricte et laxiste entre différents types de champs.
Conversion de types¶
Lors de la validation, Pydantic peut contraindre les données aux types attendus.
Il existe deux modes de coercition : stricte et laxiste. Voir Tableau de conversion pour plus de détails sur la façon dont Pydantic convertit les données en modes strict et laxiste.
Voir Mode strict et Types stricts pour plus de détails sur l'activation de la coercition stricte.
Types stricts¶
Pydantic fournit les types stricts suivants:
Ces types ne réussiront la validation que lorsque la valeur validée est du type respectif ou est un sous-type de ce type.
Types contraints¶
Ce comportement est également exposé via le champ strict
des types contraints et peut être combiné avec une multitude de règles de validation complexes. Consultez les signatures de type individuelles pour connaître les arguments pris en charge.
conbytes()
condate()
- [
condecimal()
][pydantic.types.condécimal] confloat()
confrozenset()
conint()
conlist()
conset()
constr()
Les mises en garde suivantes s'appliquent:
StrictBytes
(et l'optionstrict
deconbytes()
) acceptera à la fois les typesbytes
etbytearray
.StrictInt
(et l'optionstrict
deconint()
) n'acceptera pas les typesbool
, même sibool
est une sous-classe deint
en Python. D'autres sous-classes fonctionneront.StrictFloat
(et l'optionstrict
deconfloat()
) n'acceptera pasint
.
Outre ce qui précède, vous pouvez également avoir un type FiniteFloat
qui n'acceptera que des valeurs finies (c'est-à-dire pas inf
, -inf
ou nan
).
Types personnalisés¶
Vous pouvez également définir vos propres types de données personnalisés. Il existe plusieurs façons d’y parvenir.
Composition de types via Annotated
¶
PEP 593 a introduit Annotated
comme moyen d'attacher des métadonnées d'exécution aux types sans changer la façon dont les vérificateurs de types les interprètent. Pydantic en profite pour vous permettre de créer des types identiques au type d'origine en ce qui concerne les vérificateurs de types, mais en ajoutant une validation, en sérialisant différemment, etc.
Par exemple, pour créer un type représentant un entier 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]
"""
Notez que vous pouvez également utiliser des contraintes de types annotés pour rendre ce Pydantic-agnostique:
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]
"""
Ajout de la validation et de la sérialisation¶
Vous pouvez ajouter ou remplacer des schémas de validation, de sérialisation et JSON à un type arbitraire à l'aide des marqueurs exportés par 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'}
Génériques¶
Vous pouvez utiliser des variables de type dans Annotated
pour apporter des modifications réutilisables aux types:
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 de type nommé¶
Les exemples ci-dessus utilisent des alias de type implicites. Cela signifie qu'ils ne pourront pas avoir de title
dans les schémas JSON et que leur schéma sera copié entre les champs. Vous pouvez utiliser TypeAliasType
de PEP 695 via son backport d'extensions de typage pour créer des alias nommés, vous permettant de définir un nouveau type sans créer de sous-classes. Ce nouveau type peut être aussi simple qu'un nom ou être associé à une logique de validation complexe:
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',
}
"""
Ces alias de type nommés peuvent également être génériques:
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]
"""
Types récursifs nommés¶
Vous pouvez également utiliser TypeAliasType
pour créer des types récursifs:
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]
"""
Personnalisation de la validation avec __get_pydantic_core_schema__
¶
Pour effectuer une personnalisation plus approfondie de la façon dont Pydantic gère les classes personnalisées, et en particulier lorsque vous avez accès à la classe ou pouvez la sous-classer, vous pouvez implémenter un __get_pydantic_core_schema__
spécial pour indiquer à Pydantic comment générer le schéma pydantic-core
.
Bien que pydantic
utilise pydantic-core
en interne pour gérer la validation et la sérialisation, il s'agit d'une nouvelle API pour Pydantic V2, c'est donc l'un des domaines les plus susceptibles d'être modifiés à l'avenir et vous devriez essayer de vous en tenir aux constructions intégrées comme ceux fournis par annotated-types
, pydantic.Field
ou BeforeValidator
et ainsi de suite.
Vous pouvez implémenter __get_pydantic_core_schema__
à la fois sur un type personnalisé et sur des métadonnées destinées à être placées dans Annotated
. Dans les deux cas l'API est de type middleware et similaire à celle des validateurs "wrap" : vous obtenez un source_type
(qui n'est pas forcément le même que la classe, notamment pour les génériques) et un handler
que vous pouvez appeler avec un type soit pour appeler les métadonnées suivantes dans Annotated
, soit pour appeler la génération de schéma interne de Pydantic.
L'implémentation sans opération la plus simple appelle le gestionnaire avec le type qui vous est donné, puis le renvoie comme résultat. Vous pouvez également choisir de modifier le type avant d'appeler le gestionnaire, de modifier le schéma principal renvoyé par le gestionnaire ou de ne pas appeler le gestionnaire du tout.
En tant que méthode sur un type personnalisé¶
Ce qui suit est un exemple d'un type qui utilise __get_pydantic_core_schema__
pour personnaliser la façon dont il est validé. Cela équivaut à implémenter __get_validators__
dans 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'
Voir Schéma JSON pour plus de détails sur la façon de personnaliser les schémas JSON pour les types personnalisés.
En guise d'annotation¶
Souvent, vous souhaiterez paramétrer votre type personnalisé avec plus que de simples paramètres de type génériques (ce que vous pouvez faire via le système de types et qui sera discuté plus tard). Ou vous ne vous souciez peut-être pas (ou ne voulez pas) créer une instance de votre sous-classe; vous voulez en fait le type d'origine, juste avec une validation supplémentaire effectuée.
Par exemple, si vous deviez implémenter vous-même pydantic.AfterValidator
(voir Ajout d'une validation et d'une sérialisation ), vous feriez quelque chose de similaire à ce qui suit:
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)!
- La spécification
frozen=True
rendMyAfterValidator
hachable. Sans cela, un syndicat tel queUsername | None
générera une erreur. - Notez que les vérificateurs de type ne se plaindront pas de l'attribution
'ABC'
àUsername
comme ils l'ont fait dans l'exemple précédent, car ils ne considèrent pasUsername
comme un type distinct destr
.
Gestion des types tiers¶
Un autre cas d'utilisation du modèle de la section précédente consiste à gérer des types tiers.
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',
}
Vous pouvez utiliser cette approche pour, par exemple, définir le comportement des types Pandas ou Numpy.
Utiliser GetPydanticSchema
pour réduire le passe-partout¶
??? API "Documentation API" pydantic.types.GetPydanticSchema
Vous remarquerez peut-être que les exemples ci-dessus dans lesquels nous créons une classe de marqueurs nécessitent une bonne quantité de passe-partout. Pour de nombreux cas simples, vous pouvez grandement minimiser cela en utilisant 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'
Résumé¶
Récapitulons :
- Pydantic fournit des hooks de haut niveau pour personnaliser les types via
Annotated
commeAfterValidator
etField
. Utilisez-les lorsque cela est possible. - Sous le capot, ceux-ci utilisent
pydantic-core
pour personnaliser la validation, et vous pouvez vous y connecter directement en utilisantGetPydanticSchema
ou une classe de marqueur avec__get_pydantic_core_schema__
. - Si vous voulez vraiment un type personnalisé, vous pouvez implémenter
__get_pydantic_core_schema__
sur le type lui-même.
Gestion des classes génériques personnalisées¶
!!! avertissement Il s'agit d'une technique avancée dont vous n'aurez peut-être pas besoin au début. Dans la plupart des cas, les modèles Pydantic standard vous conviendront probablement.
Vous pouvez utiliser des classes génériques comme types de champs et effectuer une validation personnalisée basée sur les « paramètres de type » (ou sous-types) avec __get_pydantic_core_schema__
.
Si la classe générique que vous utilisez comme sous-type a une méthode de classe __get_pydantic_core_schema__
, vous n'avez pas besoin d'utiliser arbitrary_types_allowed
pour que cela fonctionne.
Étant donné que le paramètre source_type
n'est pas identique au paramètre cls
, vous pouvez utiliser typing.get_args
(ou typing_extensions.get_args
) pour extraire les paramètres génériques. Ensuite, vous pouvez utiliser le handler
pour générer un schéma pour eux en appelant handler.generate_schema
. Notez que nous ne faisons pas quelque chose comme handler(get_args(source_type)[0])
parce que nous voulons générer un schéma sans rapport pour ce paramètre générique, et non un schéma influencé par le contexte actuel des métadonnées Annotated
et autres. Ceci est moins important pour les types personnalisés, mais crucial pour les métadonnées annotées qui modifient la construction du schéma.
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]
"""
Conteneurs génériques¶
La même idée peut être appliquée pour créer des types de conteneurs génériques, comme un type Sequence
personnalisé:
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]
"""
Accès au nom du champ¶
!!!note Cela n'était pas possible avec Pydantic V2 à V2.3, cela a été ré-ajouté dans Pydantic V2.4.
À partir de Pydantic V2.4, vous pouvez accéder au nom du champ via handler.field_name
dans __get_pydantic_core_schema__
et ainsi définir le nom du champ qui sera disponible à partir de 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'>
Vous pouvez également accéder à field_name
à partir des marqueurs utilisés avec Annotated
, comme [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'>
本文总阅读量次