??? API "Documentation API" pydantic.main.BaseModel
L'un des principaux moyens de définir un schéma dans Pydantic consiste à utiliser des modèles. Les modèles sont simplement des classes qui héritent de pydantic.BaseModel
et définissent des champs comme attributs annotés.
Vous pouvez considérer les modèles comme similaires aux structures dans des langages comme C, ou comme les exigences d'un seul point de terminaison dans une API.
Les modèles partagent de nombreuses similitudes avec les classes de données de Python, mais ont été conçus avec des différences subtiles mais importantes qui rationalisent certains flux de travail liés à la validation, à la sérialisation et à la génération de schémas JSON. Vous pouvez trouver plus d'informations à ce sujet dans la section Dataclasses de la documentation.
Des données non fiables peuvent être transmises à un modèle et, après analyse et validation, Pydantic garantit que les champs de l'instance de modèle résultante seront conformes aux types de champs définis sur le modèle.
!!! note "Validation - un terme délibérément abusif" ### TL;DR
We use the term "validation" to refer to the process of instantiating a model (or other type) that adheres to specified types and
constraints. This task, which Pydantic is well known for, is most widely recognized as "validation" in colloquial terms,
even though in other contexts the term "validation" may be more restrictive.
---
### The long version
The potential confusion around the term "validation" arises from the fact that, strictly speaking, Pydantic's
primary focus doesn't align precisely with the dictionary definition of "validation":
> ### validation
> _noun_
> the action of checking or proving the validity or accuracy of something.
In Pydantic, the term "validation" refers to the process of instantiating a model (or other type) that adheres to specified
types and constraints. Pydantic guarantees the types and constraints of the output, not the input data.
This distinction becomes apparent when considering that Pydantic's `ValidationError` is raised
when data cannot be successfully parsed into a model instance.
While this distinction may initially seem subtle, it holds practical significance.
In some cases, "validation" goes beyond just model creation, and can include the copying and coercion of data.
This can involve copying arguments passed to the constructor in order to perform coercion to a new type
without mutating the original input data. For a more in-depth understanding of the implications for your usage,
refer to the [Data Conversion](#data-conversion) and [Attribute Copies](#attribute-copies) sections below.
In essence, Pydantic's primary goal is to assure that the resulting structure post-processing (termed "validation")
precisely conforms to the applied type hints. Given the widespread adoption of "validation" as the colloquial term
for this process, we will consistently use it in our documentation.
While the terms "parse" and "validation" were previously used interchangeably, moving forward, we aim to exclusively employ "validate",
with "parse" reserved specifically for discussions related to [JSON parsing](../concepts/json.md).
Utilisation du modèle de base¶
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = 'Jane Doe'
Dans cet exemple, User
est un modèle avec deux champs:
id
, qui est un entier et est obligatoirename
, qui est une chaîne et n'est pas obligatoire (il a une valeur par défaut).
utilisateur = Utilisateur (id = '123')
Dans cet exemple, user
est une instance de User
. L'initialisation de l'objet effectuera toutes les analyses et validations. Si aucune ValidationError
n'est levée, vous savez que l'instance de modèle résultante est valide.
assert user.id == 123
assert isinstance(user.id, int)
# Note that '123' was coerced to an int and its value is 123
Plus de détails sur la logique de coercition de pydantic peuvent être trouvés dans Conversion de données . Les champs d'un modèle sont accessibles en tant qu'attributs normaux de l'objet user
. La chaîne '123'
a été convertie en entier selon le type de champ.
assert user.name == 'Jane Doe'
name
n'a pas été défini lors de l'initialisation user
, il a donc la valeur par défaut.
assert user.model_fields_set == {'id'}
Les champs qui ont été fournis lors de l'initialisation de l'utilisateur.
assert user.model_dump() == {'id': 123, 'name': 'Jane Doe'}
Soit .model_dump()
soit dict(user)
fournira un dict de champs, mais .model_dump()
peut prendre de nombreux autres arguments. (Notez que dict(user)
ne convertira pas de manière récursive les modèles imbriqués en dicts, mais .model_dump()
le fera.)
user.id = 321
assert user.id == 321
Par défaut, les modèles sont modifiables et les valeurs des champs peuvent être modifiées via l'attribution d'attributs.
Méthodes et propriétés du modèle¶
L’exemple ci-dessus ne montre que la pointe de l’iceberg de ce que les modèles peuvent faire. Les modèles possèdent les méthodes et attributs suivants:
model_computed_fields
: un dictionnaire des champs calculés de cette instance de modèle.model_construct()
: une méthode de classe pour créer des modèles sans exécuter de validation. Voir Création de modèles sans validation .model_copy()
: renvoie une copie (par défaut, une copie superficielle) du modèle. Voir Sérialisation .model_dump()
: renvoie un dictionnaire des champs et des valeurs du modèle. Voir Sérialisation .model_dump_json()
: renvoie une représentation sous forme de chaîne JSON demodel_dump()
. Voir Sérialisation .model_extra
: obtient des champs supplémentaires définis lors de la validation.model_fields_set
: ensemble de champs qui ont été définis lors de l'initialisation de l'instance de modèle.model_json_schema()
: renvoie un dictionnaire jsonable représentant le modèle sous forme de schéma JSON. Voir Schéma JSON .model_parametrized_name()
: calcule le nom de classe pour les paramétrages des classes génériques.model_post_init()
: effectuez une initialisation supplémentaire après l'initialisation du modèle.model_rebuild()
: reconstruit le schéma du modèle, qui prend également en charge la création de modèles génériques récursifs. Voir Reconstruire le schéma du modèle .model_validate()
: un utilitaire pour charger n'importe quel objet dans un modèle. Voir Fonctions d'assistance .model_validate_json()
: un utilitaire pour valider les données JSON données par rapport au modèle Pydantic. Voir Fonctions d'assistance .
!!! note Voir BaseModel
pour la définition de classe comprenant une liste complète des méthodes et des attributs.
!!! astuce Voir Modifications apportées à pydantic.BaseModel
dans le Guide de migration pour plus de détails sur les modifications apportées à Pydantic V1.
Modèles imbriqués¶
Des structures de données hiérarchiques plus complexes peuvent être définies en utilisant les modèles eux-mêmes comme types dans les annotations.
from typing import List, Optional
from pydantic import BaseModel
class Foo(BaseModel):
count: int
size: Optional[float] = None
class Bar(BaseModel):
apple: str = 'x'
banana: str = 'y'
class Spam(BaseModel):
foo: Foo
bars: List[Bar]
m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
print(m)
"""
foo=Foo(count=4, size=None) bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y')]
"""
print(m.model_dump())
"""
{
'foo': {'count': 4, 'size': None},
'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y'}],
}
"""
Pour les modèles auto-référencés, voir annotations reportées .
!!! note Lors de la définition de vos modèles, faites attention aux collisions de noms entre le nom de votre champ et son type, un modèle précédemment défini ou une bibliothèque importée.
For example, the following would yield a validation error:
```py
from typing import Optional
from pydantic import BaseModel
class Boo(BaseModel):
int: Optional[int] = None
m = Boo(int=123) # errors
```
An error occurs since the field `int` is set to a default value of `None` and has the exact same name as its type, so both are interpreted to be `None`.
Reconstruire le schéma du modèle¶
Le schéma du modèle peut être reconstruit à l'aide de model_rebuild()
. Ceci est utile pour créer des modèles génériques récursifs.
from pydantic import BaseModel, PydanticUserError
class Foo(BaseModel):
x: 'Bar'
try:
Foo.model_json_schema()
except PydanticUserError as e:
print(e)
"""
`Foo` is not fully defined; you should define `Bar`, then call `Foo.model_rebuild()`.
For further information visit https://errors.pydantic.dev/2/u/class-not-fully-defined
"""
class Bar(BaseModel):
pass
Foo.model_rebuild()
print(Foo.model_json_schema())
"""
{
'$defs': {'Bar': {'properties': {}, 'title': 'Bar', 'type': 'object'}},
'properties': {'x': {'$ref': '#/$defs/Bar'}},
'required': ['x'],
'title': 'Foo',
'type': 'object',
}
"""
Pydantic essaie de déterminer automatiquement quand cela est nécessaire et génère une erreur si cela n'a pas été fait, mais vous souhaiterez peut-être appeler model_rebuild()
de manière proactive lorsque vous traitez des modèles récursifs ou génériques.
Dans la V2, model_rebuild()
a remplacé update_forward_refs()
de la V1. Il existe quelques légères différences avec le nouveau comportement. Le plus grand changement est que lors de l'appel de model_rebuild()
sur le modèle le plus externe, il crée un schéma de base utilisé pour la validation de l'ensemble du modèle (modèles imbriqués et tous), donc de tous les types. les niveaux doivent être prêts avant que model_rebuild()
soit appelé.
Instances de classe arbitraires¶
(Anciennement connu sous le nom de « Mode ORM »/ from_orm
.)
Des modèles pydantiques peuvent également être créés à partir d'instances de classe arbitraires en lisant les attributs d'instance correspondant aux noms de champs du modèle. Une application courante de cette fonctionnalité est l'intégration avec les mappages objet-relationnel (ORM).
Pour ce faire, définissez l'attribut config model_config['from_attributes'] = True
. Voir Model Config et ConfigDict pour plus d'informations.
L'exemple ici utilise SQLAlchemy , mais la même approche devrait fonctionner pour n'importe quel ORM.
from typing import List
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.orm import declarative_base
from typing_extensions import Annotated
from pydantic import BaseModel, ConfigDict, StringConstraints
Base = declarative_base()
class CompanyOrm(Base):
__tablename__ = 'companies'
id = Column(Integer, primary_key=True, nullable=False)
public_key = Column(String(20), index=True, nullable=False, unique=True)
name = Column(String(63), unique=True)
domains = Column(ARRAY(String(255)))
class CompanyModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
public_key: Annotated[str, StringConstraints(max_length=20)]
name: Annotated[str, StringConstraints(max_length=63)]
domains: List[Annotated[str, StringConstraints(max_length=255)]]
co_orm = CompanyOrm(
id=123,
public_key='foobar',
name='Testing',
domains=['example.com', 'foobar.com'],
)
print(co_orm)
#> <__main__.CompanyOrm object at 0x0123456789ab>
co_model = CompanyModel.model_validate(co_orm)
print(co_model)
"""
id=123 public_key='foobar' name='Testing' domains=['example.com', 'foobar.com']
"""
Noms réservés¶
Vous souhaiterez peut-être nommer une Column
après un champ SQLAlchemy réservé. Dans ce cas, les alias Field
seront pratiques:
import typing
import sqlalchemy as sa
from sqlalchemy.orm import declarative_base
from pydantic import BaseModel, ConfigDict, Field
class MyModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
metadata: typing.Dict[str, str] = Field(alias='metadata_')
Base = declarative_base()
class SQLModel(Base):
__tablename__ = 'my_table'
id = sa.Column('id', sa.Integer, primary_key=True)
# 'metadata' is reserved by SQLAlchemy, hence the '_'
metadata_ = sa.Column('metadata', sa.JSON)
sql_model = SQLModel(metadata_={'key': 'val'}, id=1)
pydantic_model = MyModel.model_validate(sql_model)
print(pydantic_model.model_dump())
#> {'metadata': {'key': 'val'}}
print(pydantic_model.model_dump(by_alias=True))
#> {'metadata_': {'key': 'val'}}
!!! note L'exemple ci-dessus fonctionne car les alias ont la priorité sur les noms de champs pour le remplissage des champs. L'accès à l'attribut metadata
de SQLModel
entraînerait une ValidationError
.
Attributs imbriqués¶
Lorsque vous utilisez des attributs pour analyser des modèles, des instances de modèle seront créées à partir d'attributs de niveau supérieur et d'attributs imbriqués plus profondément, selon le cas.
Voici un exemple démontrant le principe :
from typing import List
from pydantic import BaseModel, ConfigDict
class PetCls:
def __init__(self, *, name: str, species: str):
self.name = name
self.species = species
class PersonCls:
def __init__(self, *, name: str, age: float = None, pets: List[PetCls]):
self.name = name
self.age = age
self.pets = pets
class Pet(BaseModel):
model_config = ConfigDict(from_attributes=True)
name: str
species: str
class Person(BaseModel):
model_config = ConfigDict(from_attributes=True)
name: str
age: float = None
pets: List[Pet]
bones = PetCls(name='Bones', species='dog')
orion = PetCls(name='Orion', species='cat')
anna = PersonCls(name='Anna', age=20, pets=[bones, orion])
anna_model = Person.model_validate(anna)
print(anna_model)
"""
name='Anna' age=20.0 pets=[Pet(name='Bones', species='dog'), Pet(name='Orion', species='cat')]
"""
Gestion des erreurs¶
Pydantic déclenchera ValidationError
chaque fois qu'il trouvera une erreur dans les données qu'il valide.
Une seule exception de type ValidationError
sera déclenchée quel que soit le nombre d'erreurs trouvées, et cette ValidationError
contiendra des informations sur toutes les erreurs et comment elles se sont produites.
Voir Gestion des erreurs pour plus de détails sur les erreurs standard et personnalisées.
En guise de démonstration :
from typing import List
from pydantic import BaseModel, ValidationError
class Model(BaseModel):
list_of_ints: List[int]
a_float: float
data = dict(
list_of_ints=['1', 2, 'bad'],
a_float='not a float',
)
try:
Model(**data)
except ValidationError as e:
print(e)
"""
2 validation errors for Model
list_of_ints.2
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='bad', input_type=str]
a_float
Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='not a float', input_type=str]
"""
Fonctions d'assistance¶
Pydantic fournit trois fonctions d'assistance classmethod
sur les modèles pour analyser les données:
model_validate()
: ceci est très similaire à la méthode__init__
du modèle, sauf qu'elle prend un dict ou un objet plutôt que des arguments de mot-clé. Si l'objet passé ne peut pas être validé, ou s'il ne s'agit pas d'un dictionnaire ou d'une instance du modèle en question, uneValidationError
sera levée.model_validate_json()
: cela prend une chaîne ou des octets et l'analyse comme json , puis transmet le résultat àmodel_validate()
.-
model_validate_strings()
: cela prend un dict (peut être imbriqué) avec des clés et des valeurs de chaîne et valide les données en mode json afin que lesdites chaînes puissent être contraintes dans les types corrects.from datetime import datetime from typing import Optional
from pydantic import BaseModel, ValidationError
class User(BaseModel): id: int name: str = 'John Doe' signup_ts: Optional[datetime] = None
m = User.model_validate({'id': 123, 'name': 'James'}) print(m)
> id=123 name='James' signup_ts=None¶
try: User.model_validate(['not', 'a', 'dict']) except ValidationError as e: print(e) """ 1 validation error for User Input should be a valid dictionary or instance of User [type=model_type, input_value=['not', 'a', 'dict'], input_type=list] """
m = User.model_validate_json('{"id": 123, "name": "James"}') print(m)
> id=123 name='James' signup_ts=None¶
try: m = User.model_validate_json('{"id": 123, "name": 123}') except ValidationError as e: print(e) """ 1 validation error for User name Input should be a valid string [type=string_type, input_value=123, input_type=int] """
try: m = User.model_validate_json('invalid JSON') except ValidationError as e: print(e) """ 1 validation error for User Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='invalid JSON', input_type=str] """
m = User.model_validate_strings({'id': '123', 'name': 'James'}) print(m)
> id=123 name='James' signup_ts=None¶
m = User.model_validate_strings( {'id': '123', 'name': 'James', 'signup_ts': '2024-04-01T12:00:00'} ) print(m)
> id=123 name='James' signup_ts=datetime.datetime(2024, 4, 1, 12, 0)¶
try: m = User.model_validate_strings( {'id': '123', 'name': 'James', 'signup_ts': '2024-04-01'}, strict=True ) except ValidationError as e: print(e) """ 1 validation error for User signup_ts Input should be a valid datetime, invalid datetime separator, expected
T
,t
,_
or space [type=datetime_parsing, input_value='2024-04-01', input_type=str] """
Si vous souhaitez valider des données sérialisées dans un format autre que JSON, vous devez charger vous-même les données dans un dict, puis les transmettre à model_validate
.
!!! note En fonction des types et des configurations de modèle impliqués, model_validate
et model_validate_json
peuvent avoir un comportement de validation différent. Si vous avez des données provenant d'une source non JSON, mais que vous souhaitez le même comportement de validation et les mêmes erreurs que vous obtiendriez de model_validate_json
, notre recommandation pour l'instant est d'utiliser soit model_validate_json(json.dumps(data))
, ou utilisez model_validate_strings
si les données prennent la forme d'un dict (potentiellement imbriqué) avec des clés et des valeurs de chaîne.
!!! note Apprenez-en davantage sur l'analyse JSON dans la section JSON de la documentation.
!!! note Si vous transmettez une instance d'un modèle à model_validate
, vous souhaiterez envisager de définir revalidate_instances
dans la configuration du modèle. Si vous ne définissez pas cette valeur, la validation sera ignorée sur les instances de modèle. Voir l'exemple ci-dessous:
\=== " revalidate_instances='never'
" ```py depuis l'importation pydantic BaseModel
class Model(BaseModel):
a: int
m = Model(a=0)
# note: the `model_config` setting validate_assignment=True` can prevent this kind of misbehavior
m.a = 'not an int'
# doesn't raise a validation error even though m is invalid
m2 = Model.model_validate(m)
```
\=== " revalidate_instances='always'
" ```py depuis l'importation pydantic BaseModel, ConfigDict, ValidationError
class Model(BaseModel):
a: int
model_config = ConfigDict(revalidate_instances='always')
m = Model(a=0)
# note: the `model_config` setting validate_assignment=True` can prevent this kind of misbehavior
m.a = 'not an int'
try:
m2 = Model.model_validate(m)
except ValidationError as e:
print(e)
"""
1 validation error for Model
a
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='not an int', input_type=str]
"""
```
Créer des modèles sans validation¶
Pydantic fournit également la méthode model_construct()
, qui permet de créer des modèles sans validation . Cela peut être utile dans au moins quelques cas:
- lorsque vous travaillez avec des données complexes déjà connues pour être valides (pour des raisons de performances)
- lorsqu'une ou plusieurs des fonctions du validateur sont non idempotentes, ou
- lorsqu'une ou plusieurs fonctions du validateur ont des effets secondaires que vous ne souhaitez pas déclencher.
!!! note Dans Pydantic V2, l'écart de performances entre BaseModel.__init__
et BaseModel.model_construct
a été considérablement réduit. Pour les modèles simples, appeler BaseModel.__init__
peut même être plus rapide. Si vous utilisez model_construct()
pour des raisons de performances, vous souhaiterez peut-être profiler votre cas d'utilisation avant de supposer que model_construct()
est plus rapide.
!!! avertissement model_construct()
n'effectue aucune validation, ce qui signifie qu'il peut créer des modèles invalides. Vous ne devez utiliser la méthode model_construct()
qu'avec des données qui ont déjà été validées ou auxquelles vous avez vraiment confiance.
from pydantic import BaseModel
class User(BaseModel):
id: int
age: int
name: str = 'John Doe'
original_user = User(id=123, age=32)
user_data = original_user.model_dump()
print(user_data)
#> {'id': 123, 'age': 32, 'name': 'John Doe'}
fields_set = original_user.model_fields_set
print(fields_set)
#> {'age', 'id'}
# ...
# pass user_data and fields_set to RPC or save to the database etc.
# ...
# you can then create a new instance of User without
# re-running validation which would be unnecessary at this point:
new_user = User.model_construct(_fields_set=fields_set, **user_data)
print(repr(new_user))
#> User(id=123, age=32, name='John Doe')
print(new_user.model_fields_set)
#> {'age', 'id'}
# construct can be dangerous, only use it with validated data!:
bad_user = User.model_construct(id='dog')
print(repr(bad_user))
#> User(id='dog', name='John Doe')
L'argument mot-clé _fields_set
de model_construct()
est facultatif, mais vous permet d'être plus précis sur les champs qui ont été initialement définis et ceux qui ne l'ont pas été. S'il est omis, model_fields_set
ne seront que les clés des données fournies.
Par exemple, dans l'exemple ci-dessus, si _fields_set
n'était pas fourni, new_user.model_fields_set
serait {'id', 'age', 'name'}
.
Notez que pour les sous-classes de RootModel
, la valeur racine peut être transmise à model_construct()
de manière positionnelle, au lieu d'utiliser un argument mot-clé.
Voici quelques notes supplémentaires sur le comportement de model_construct()
:
- Lorsque nous disons « aucune validation n'est effectuée », cela inclut la conversion des dicts en instances de modèle. Donc si tu as un champ avec un type
Model
, vous devrez convertir vous-même le dict interne en modèle avant de le transmettre àmodel_construct()
.- En particulier, la méthode
model_construct()
ne prend pas en charge la construction récursive de modèles à partir de dicts.
- En particulier, la méthode
- Si vous ne transmettez pas d'arguments de mot-clé pour les champs avec des valeurs par défaut, les valeurs par défaut seront toujours utilisées.
- Pour les modèles avec des attributs privés, le
__pydantic_private__
dict sera initialisé de la même manière que lors de l'appel__init__
. - Lors de la construction d'une instance à l'aide de
model_construct()
, aucune méthode__init__
du modèle ou de l'une de ses classes parentes ne sera appelée, même lorsqu'une méthode__init__
personnalisée est définie.
!!! note "Sur le comportement extra
avec model_construct
" * Pour les modèles avec model_config['extra'] == 'allow'
, les données ne correspondant pas aux champs seront correctement stockées dans le __pydantic_extra__
dict et enregistrées dans le __dict__
du modèle. *Pour les modèles avec model_config['extra'] == 'ignore'
, les données ne correspondant pas aux champs seront ignorées, c'est-à-dire non stockées dans __pydantic_extra__
ou __dict__
sur l'instance. * Contrairement à un appel à __init__
, un appel à model_construct
avec model_config['extra'] == 'forbid'
ne génère pas d'erreur en présence de données ne correspondant pas aux champs. Au contraire, lesdites données d'entrée sont simplement ignorées.
Modèles génériques¶
Pydantic prend en charge la création de modèles génériques pour faciliter la réutilisation d'une structure de modèle commune.
Afin de déclarer un modèle générique, vous effectuez les étapes suivantes:
- Déclarez une ou plusieurs instances
typing.TypeVar
à utiliser pour paramétrer votre modèle. - Déclarez un modèle pydantic qui hérite de
pydantic.BaseModel
ettyping.Generic
, où vous transmettez les instancesTypeVar
comme paramètres àtyping.Generic
. - Utilisez les instances
TypeVar
comme annotations où vous souhaiterez les remplacer par d'autres types ou modèles pydantiques.
Voici un exemple utilisant une sous-classe générique BaseModel
pour créer un wrapper de charge utile de réponse HTTP facilement réutilisé:
from typing import Generic, List, Optional, TypeVar
from pydantic import BaseModel, ValidationError
DataT = TypeVar('DataT')
class DataModel(BaseModel):
numbers: List[int]
people: List[str]
class Response(BaseModel, Generic[DataT]):
data: Optional[DataT] = None
print(Response[int](data=1))
#> data=1
print(Response[str](data='value'))
#> data='value'
print(Response[str](data='value').model_dump())
#> {'data': 'value'}
data = DataModel(numbers=[1, 2, 3], people=[])
print(Response[DataModel](data=data).model_dump())
#> {'data': {'numbers': [1, 2, 3], 'people': []}}
try:
Response[int](data='value')
except ValidationError as e:
print(e)
"""
1 validation error for Response[int]
data
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='value', input_type=str]
"""
Si vous définissez model_config
ou utilisez @field_validator
ou d'autres décorateurs Pydantic dans votre définition de modèle générique, ils seront appliqués aux sous-classes paramétrées de la même manière que lors de l'héritage d'une sous-classe BaseModel
. Toutes les méthodes définies sur votre classe générique seront également héritées.
Les génériques de Pydantic s'intègrent également correctement aux vérificateurs de types, vous obtenez donc toute la vérification de type à laquelle vous vous attendez si vous deviez déclarer un type distinct pour chaque paramétrage.
!!! note En interne, Pydantic crée des sous-classes de BaseModel
au moment de l'exécution lorsque les modèles génériques sont paramétrés. Ces classes sont mises en cache, il devrait donc y avoir une surcharge minimale introduite par l'utilisation de modèles génériques.
Pour hériter d'un modèle générique et conserver le fait qu'il soit générique, la sous-classe doit également hériter du typing.Generic
:
from typing import Generic, TypeVar
from pydantic import BaseModel
TypeX = TypeVar('TypeX')
class BaseClass(BaseModel, Generic[TypeX]):
X: TypeX
class ChildClass(BaseClass[TypeX], Generic[TypeX]):
# Inherit from Generic[TypeX]
pass
# Replace TypeX by int
print(ChildClass[int](X=1))
#> X=1
Vous pouvez également créer une sous-classe générique d'un BaseModel
qui remplace partiellement ou totalement les paramètres de type dans la superclasse:
from typing import Generic, TypeVar
from pydantic import BaseModel
TypeX = TypeVar('TypeX')
TypeY = TypeVar('TypeY')
TypeZ = TypeVar('TypeZ')
class BaseClass(BaseModel, Generic[TypeX, TypeY]):
x: TypeX
y: TypeY
class ChildClass(BaseClass[int, TypeY], Generic[TypeY, TypeZ]):
z: TypeZ
# Replace TypeY by str
print(ChildClass[str, int](x='1', y='y', z='3'))
#> x=1 y='y' z=3
Si le nom des sous-classes concrètes est important, vous pouvez également remplacer la génération de nom par défaut:
from typing import Any, Generic, Tuple, Type, TypeVar
from pydantic import BaseModel
DataT = TypeVar('DataT')
class Response(BaseModel, Generic[DataT]):
data: DataT
@classmethod
def model_parametrized_name(cls, params: Tuple[Type[Any], ...]) -> str:
return f'{params[0].__name__.title()}Response'
print(repr(Response[int](data=1)))
#> IntResponse(data=1)
print(repr(Response[str](data='a')))
#> StrResponse(data='a')
Vous pouvez utiliser des modèles génériques paramétrés comme types dans d'autres modèles:
from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar('T')
class ResponseModel(BaseModel, Generic[T]):
content: T
class Product(BaseModel):
name: str
price: float
class Order(BaseModel):
id: int
product: ResponseModel[Product]
product = Product(name='Apple', price=0.5)
response = ResponseModel[Product](content=product)
order = Order(id=1, product=response)
print(repr(order))
"""
Order(id=1, product=ResponseModel[Product](content=Product(name='Apple', price=0.5)))
"""
!!! Astuce Lorsque vous utilisez un modèle générique paramétré comme type dans un autre modèle (comme product: ResponseModel[Product]
), assurez-vous de paramétrer ledit modèle générique lorsque vous initialisez l'instance de modèle (comme response = ResponseModel[Product](content=product)
). Si vous ne le faites pas, une ValidationError
sera générée, car Pydantic ne déduit pas le type du modèle générique en fonction des données qui lui sont transmises.
L'utilisation du même TypeVar
dans des modèles imbriqués vous permet d'appliquer des relations de saisie à différents points de votre modèle:
from typing import Generic, TypeVar
from pydantic import BaseModel, ValidationError
T = TypeVar('T')
class InnerT(BaseModel, Generic[T]):
inner: T
class OuterT(BaseModel, Generic[T]):
outer: T
nested: InnerT[T]
nested = InnerT[int](inner=1)
print(OuterT[int](outer=1, nested=nested))
#> outer=1 nested=InnerT[int](inner=1)
try:
nested = InnerT[str](inner='a')
print(OuterT[int](outer='a', nested=nested))
except ValidationError as e:
print(e)
"""
2 validation errors for OuterT[int]
outer
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
nested
Input should be a valid dictionary or instance of InnerT[int] [type=model_type, input_value=InnerT[str](inner='a'), input_type=InnerT[str]]
"""
Lors de l'utilisation de paramètres de type liés et lorsque les paramètres de type ne sont pas spécifiés, Pydantic traite les modèles génériques de la même manière qu'il traite les types génériques intégrés tels que List
et Dict
:
- Si vous ne spécifiez pas de paramètres avant d'instancier le modèle générique, ils sont validés comme limite du
TypeVar
. - Si les
TypeVar
impliqués n’ont pas de limites, ils sont traités commeAny
.
De plus, comme List
et Dict
, tous les paramètres spécifiés à l'aide d'un TypeVar
peuvent ensuite être remplacés par des types concrets:
from typing import Generic, TypeVar
from pydantic import BaseModel, ValidationError
AT = TypeVar('AT')
BT = TypeVar('BT')
class Model(BaseModel, Generic[AT, BT]):
a: AT
b: BT
print(Model(a='a', b='a'))
#> a='a' b='a'
IntT = TypeVar('IntT', bound=int)
typevar_model = Model[int, IntT]
print(typevar_model(a=1, b=1))
#> a=1 b=1
try:
typevar_model(a='a', b='a')
except ValidationError as exc:
print(exc)
"""
2 validation errors for Model[int, TypeVar]
a
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
b
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
"""
concrete_model = typevar_model[int]
print(concrete_model(a=1, b=1))
#> a=1 b=1
!!! avertissement Même si cela ne génère pas d'erreur, nous vous déconseillons fortement d'utiliser des génériques paramétrés dans les vérifications d'isinstance.
For example, you should not do `isinstance(my_model, MyGenericModel[int])`. However, it is fine to do `isinstance(my_model, MyGenericModel)`. (Note that, for standard generics, it would raise an error to do a subclass check with a parameterized generic.)
If you need to perform isinstance checks against parametrized generics, you can do this by subclassing the parametrized generic class. This looks like `class MyIntModel(MyGenericModel[int]): ...` and `isinstance(my_model, MyIntModel)`.
Si un modèle Pydantic est utilisé dans une limite TypeVar
et que le type générique n'est jamais paramétré, alors Pydantic utilisera la limite pour la validation mais traitera la valeur comme Any
en termes de sérialisation:
from typing import Generic, Optional, TypeVar
from pydantic import BaseModel
class ErrorDetails(BaseModel):
foo: str
ErrorDataT = TypeVar('ErrorDataT', bound=ErrorDetails)
class Error(BaseModel, Generic[ErrorDataT]):
message: str
details: Optional[ErrorDataT]
class MyErrorDetails(ErrorDetails):
bar: str
# serialized as Any
error = Error(
message='We just had an error',
details=MyErrorDetails(foo='var', bar='var2'),
)
assert error.model_dump() == {
'message': 'We just had an error',
'details': {
'foo': 'var',
'bar': 'var2',
},
}
# serialized using the concrete parametrization
# note that `'bar': 'var2'` is missing
error = Error[ErrorDetails](
message='We just had an error',
details=ErrorDetails(foo='var'),
)
assert error.model_dump() == {
'message': 'We just had an error',
'details': {
'foo': 'var',
},
}
Voici un autre exemple du comportement ci-dessus, énumérant toutes les permutations concernant la spécification liée et la paramétrisation de type générique:
from typing import Generic
from typing_extensions import TypeVar
from pydantic import BaseModel
TBound = TypeVar('TBound', bound=BaseModel)
TNoBound = TypeVar('TNoBound')
class IntValue(BaseModel):
value: int
class ItemBound(BaseModel, Generic[TBound]):
item: TBound
class ItemNoBound(BaseModel, Generic[TNoBound]):
item: TNoBound
item_bound_inferred = ItemBound(item=IntValue(value=3))
item_bound_explicit = ItemBound[IntValue](item=IntValue(value=3))
item_no_bound_inferred = ItemNoBound(item=IntValue(value=3))
item_no_bound_explicit = ItemNoBound[IntValue](item=IntValue(value=3))
# calling `print(x.model_dump())` on any of the above instances results in the following:
#> {'item': {'value': 3}}
Si vous utilisez un default=...
(disponible en Python >= 3.13 ou via typing-extensions
) ou des contraintes ( TypeVar('T', str, int)
; notez que vous souhaitez rarement utiliser cette forme de TypeVar
) alors la ou les contraintes par défaut seront utilisées à la fois pour la validation et la sérialisation si la variable de type n'est pas paramétrée. Vous pouvez remplacer ce comportement en utilisant pydantic.SerializeAsAny
:
from typing import Generic, Optional
from typing_extensions import TypeVar
from pydantic import BaseModel, SerializeAsAny
class ErrorDetails(BaseModel):
foo: str
ErrorDataT = TypeVar('ErrorDataT', default=ErrorDetails)
class Error(BaseModel, Generic[ErrorDataT]):
message: str
details: Optional[ErrorDataT]
class MyErrorDetails(ErrorDetails):
bar: str
# serialized using the default's serializer
error = Error(
message='We just had an error',
details=MyErrorDetails(foo='var', bar='var2'),
)
assert error.model_dump() == {
'message': 'We just had an error',
'details': {
'foo': 'var',
},
}
class SerializeAsAnyError(BaseModel, Generic[ErrorDataT]):
message: str
details: Optional[SerializeAsAny[ErrorDataT]]
# serialized as Any
error = SerializeAsAnyError(
message='We just had an error',
details=MyErrorDetails(foo='var', bar='baz'),
)
assert error.model_dump() == {
'message': 'We just had an error',
'details': {
'foo': 'var',
'bar': 'baz',
},
}
!!! note Remarque : vous risquez de rencontrer quelques problèmes si vous ne paramétrez pas un générique alors que le cas d'une validation par rapport à la limite du générique pourrait entraîner une perte de données. Voir l'exemple ci-dessous:
from typing import Generic
from typing_extensions import TypeVar
from pydantic import BaseModel
TItem = TypeVar('TItem', bound='ItemBase')
class ItemBase(BaseModel): ...
class IntItem(ItemBase):
value: int
class ItemHolder(BaseModel, Generic[TItem]):
item: TItem
loaded_data = {'item': {'value': 1}}
print(ItemHolder(**loaded_data).model_dump()) # (1)!
#> {'item': {}}
print(ItemHolder[IntItem](**loaded_data).model_dump()) # (2)!
#> {'item': {'value': 1}}
- Lorsque le générique n'est pas paramétré, les données d'entrée sont validées par rapport à la limite générique. Étant donné que
ItemBase
ne comporte aucun champ, les informations du champitem
sont perdues. - Dans ce cas, les informations de type d'exécution sont fournies explicitement via la paramétrisation générique, de sorte que les données d'entrée sont validées par rapport à la classe
IntItem
et que la sortie de sérialisation correspond à ce qui est attendu.
Création de modèle dynamique¶
??? API "Documentation API" pydantic.main.create_model
Dans certains cas, il est souhaitable de créer un modèle utilisant les informations d'exécution pour spécifier les champs. Pour cela, Pydantic fournit la fonction create_model
pour permettre de créer des modèles à la volée:
from pydantic import BaseModel, create_model
DynamicFoobarModel = create_model(
'DynamicFoobarModel', foo=(str, ...), bar=(int, 123)
)
class StaticFoobarModel(BaseModel):
foo: str
bar: int = 123
Ici, StaticFoobarModel
et DynamicFoobarModel
sont identiques.
Les champs sont définis par l'une des formes de tuple suivantes:
(<type>, <default value>)
(<type>, Field(...))
typing.Annotated[<type>, Field(...)]
L'utilisation d'un appel Field(...)
comme deuxième argument dans le tuple (la valeur par défaut) permet une configuration de champ plus avancée. Ainsi, les éléments suivants sont analogues:
from pydantic import BaseModel, Field, create_model
DynamicModel = create_model(
'DynamicModel',
foo=(str, Field(..., description='foo description', alias='FOO')),
)
class StaticModel(BaseModel):
foo: str = Field(..., description='foo description', alias='FOO')
Les arguments de mots clés spéciaux __config__
et __base__
peuvent être utilisés pour personnaliser le nouveau modèle. Cela inclut l'extension d'un modèle de base avec des champs supplémentaires.
from pydantic import BaseModel, create_model
class FooModel(BaseModel):
foo: str
bar: int = 123
BarModel = create_model(
'BarModel',
apple=(str, 'russet'),
banana=(str, 'yellow'),
__base__=FooModel,
)
print(BarModel)
#> <class '__main__.BarModel'>
print(BarModel.model_fields.keys())
#> dict_keys(['foo', 'bar', 'apple', 'banana'])
Vous pouvez également ajouter des validateurs en passant un dict à l'argument __validators__
.
from pydantic import ValidationError, create_model, field_validator
def username_alphanumeric(cls, v):
assert v.isalnum(), 'must be alphanumeric'
return v
validators = {
'username_validator': field_validator('username')(username_alphanumeric)
}
UserModel = create_model(
'UserModel', username=(str, ...), __validators__=validators
)
user = UserModel(username='scolvin')
print(user)
#> username='scolvin'
try:
UserModel(username='scolvi%n')
except ValidationError as e:
print(e)
"""
1 validation error for UserModel
username
Assertion failed, must be alphanumeric [type=assertion_error, input_value='scolvi%n', input_type=str]
"""
!!! note Pour décaper un modèle créé dynamiquement:
- the model must be defined globally
- it must provide `__module__`
RootModel
et types de racines personnalisés¶
??? API "Documentation API" pydantic.root_model.RootModel
Les modèles Pydantic peuvent être définis avec un « type racine personnalisé » en sous-classant pydantic.RootModel
.
Le type racine peut être n'importe quel type pris en charge par Pydantic et est spécifié par le paramètre générique de RootModel
. La valeur racine peut être transmise au modèle __init__
ou model_validate
via le premier et unique argument.
Voici un exemple de la façon dont cela fonctionne:
from typing import Dict, List
from pydantic import RootModel
Pets = RootModel[List[str]]
PetsByName = RootModel[Dict[str, str]]
print(Pets(['dog', 'cat']))
#> root=['dog', 'cat']
print(Pets(['dog', 'cat']).model_dump_json())
#> ["dog","cat"]
print(Pets.model_validate(['dog', 'cat']))
#> root=['dog', 'cat']
print(Pets.model_json_schema())
"""
{'items': {'type': 'string'}, 'title': 'RootModel[List[str]]', 'type': 'array'}
"""
print(PetsByName({'Otis': 'dog', 'Milo': 'cat'}))
#> root={'Otis': 'dog', 'Milo': 'cat'}
print(PetsByName({'Otis': 'dog', 'Milo': 'cat'}).model_dump_json())
#> {"Otis":"dog","Milo":"cat"}
print(PetsByName.model_validate({'Otis': 'dog', 'Milo': 'cat'}))
#> root={'Otis': 'dog', 'Milo': 'cat'}
Si vous souhaitez accéder directement aux éléments du champ root
ou parcourir les éléments, vous pouvez implémenter les fonctions personnalisées __iter__
et __getitem__
, comme indiqué dans l'exemple suivant.
from typing import List
from pydantic import RootModel
class Pets(RootModel):
root: List[str]
def __iter__(self):
return iter(self.root)
def __getitem__(self, item):
return self.root[item]
pets = Pets.model_validate(['dog', 'cat'])
print(pets[0])
#> dog
print([pet for pet in pets])
#> ['dog', 'cat']
Vous pouvez également créer directement des sous-classes du modèle racine paramétré:
from typing import List
from pydantic import RootModel
class Pets(RootModel[List[str]]):
def describe(self) -> str:
return f'Pets: {", ".join(self.root)}'
my_pets = Pets.model_validate(['dog', 'cat'])
print(my_pets.describe())
#> Pets: dog, cat
Fausse immuabilité¶
Les modèles peuvent être configurés pour être immuables via model_config['frozen'] = True
. Lorsque cette valeur est définie, toute tentative de modification des valeurs des attributs d'instance générera des erreurs. Voir la référence API pour plus de détails.
!!! note Ce comportement a été obtenu dans Pydantic V1 via le paramètre de configuration allow_mutation = False
. Cet indicateur de configuration est obsolète dans Pydantic V2 et a été remplacé par frozen
.
!!! avertissement En Python, l'immuabilité n'est pas appliquée. Les développeurs ont la possibilité de modifier des objets traditionnellement considérés comme « immuables » s’ils choisissent de le faire.
from pydantic import BaseModel, ConfigDict, ValidationError
class FooBarModel(BaseModel):
model_config = ConfigDict(frozen=True)
a: str
b: dict
foobar = FooBarModel(a='hello', b={'apple': 'pear'})
try:
foobar.a = 'different'
except ValidationError as e:
print(e)
"""
1 validation error for FooBarModel
a
Instance is frozen [type=frozen_instance, input_value='different', input_type=str]
"""
print(foobar.a)
#> hello
print(foobar.b)
#> {'apple': 'pear'}
foobar.b['apple'] = 'grape'
print(foobar.b)
#> {'apple': 'grape'}
Essayer de modifier a
provoqué une erreur et a
reste inchangé. Cependant, le dict b
est mutable et l'immuabilité de foobar
n'empêche pas b
d'être modifié.
Classes de base abstraites¶
Les modèles Pydantic peuvent être utilisés avec les classes de base abstraites (ABC) de Python.
import abc
from pydantic import BaseModel
class FooBarModel(BaseModel, abc.ABC):
a: str
b: int
@abc.abstractmethod
def my_abstract_method(self):
pass
Ordre des champs¶
L'ordre des champs affecte les modèles des manières suivantes:
- l'ordre des champs est conservé dans le schéma du modèle
- l'ordre des champs est conservé dans les erreurs de validation
-
l'ordre des champs est conservé par
.model_dump()
et.model_dump_json()
etc.from pydantic import BaseModel, ValidationError
class Model(BaseModel): a: int b: int = 2 c: int = 1 d: int = 0 e: float
print(Model.model_fields.keys())
> dict_keys(['a', 'b', 'c', 'd', 'e'])¶
m = Model(e=2, a=1) print(m.model_dump())
>¶
try: Model(a='x', b='x', c='x', d='x', e='x') except ValidationError as err: error_locations = [e['loc'] for e in err.errors()]
print(error_locations)
> [('a',), ('b',), ('c',), ('d',), ('e',)]¶
Champs obligatoires¶
Pour déclarer un champ comme requis, vous pouvez le déclarer à l'aide d'une annotation ou d'une annotation en combinaison avec une spécification Field
. Vous pouvez également utiliser Ellipsis
/ ...
pour souligner qu'un champ est obligatoire, notamment lors de l'utilisation du constructeur Field
.
La fonction Field
est principalement utilisée pour configurer des paramètres tels que alias
ou description
d'un attribut. Le constructeur prend en charge Ellipsis
/ ...
comme seul argument de position. Ceci est utilisé pour indiquer que ledit champ est obligatoire, bien que ce soit l'indication de type qui applique cette exigence.
from pydantic import BaseModel, Field
class Model(BaseModel):
a: int
b: int = ...
c: int = Field(..., alias='C')
Ici, a
, b
et c
sont tous obligatoires. Cependant, cette utilisation de b: int = ...
ne fonctionne pas correctement avec mypy et, depuis la version 1.0, doit être évitée dans la plupart des cas.
!!! note Dans Pydantic V1, les champs annotés avec Optional
ou Any
recevraient la valeur par défaut implicite de None
même si aucune valeur par défaut n'était explicitement spécifiée. Ce comportement a changé dans Pydantic V2 et il n'y a plus d'annotations de type qui donneraient à un champ une valeur par défaut implicite.
See [the migration guide](../migration.md#required-optional-and-nullable-fields) for more details on changes
to required and nullable fields.
Champs avec des valeurs par défaut non hachables¶
Une source courante de bugs en python consiste à utiliser un objet mutable comme valeur par défaut pour un argument de fonction ou de méthode, car la même instance finit par être réutilisée à chaque appel.
Le module dataclasses
génère en fait une erreur dans ce cas, indiquant que vous devez utiliser l'argument default_factory
pour dataclasses.field
.
Pydantic prend également en charge l'utilisation d'un default_factory
pour les valeurs par défaut non hachables, mais ce n'est pas obligatoire. Dans le cas où la valeur par défaut n'est pas hachable, Pydantic copiera en profondeur la valeur par défaut lors de la création de chaque instance du modèle:
from typing import Dict, List
from pydantic import BaseModel
class Model(BaseModel):
item_counts: List[Dict[str, int]] = [{}]
m1 = Model()
m1.item_counts[0]['a'] = 1
print(m1.item_counts)
#> [{'a': 1}]
m2 = Model()
print(m2.item_counts)
#> [{}]
Champs avec des valeurs dynamiques par défaut¶
Lorsque vous déclarez un champ avec une valeur par défaut, vous souhaiterez peut-être qu'il soit dynamique (c'est-à-dire différent pour chaque modèle). Pour ce faire, vous souhaiterez peut-être utiliser un default_factory
.
Voici un exemple:
from datetime import datetime, timezone
from uuid import UUID, uuid4
from pydantic import BaseModel, Field
def datetime_now() -> datetime:
return datetime.now(timezone.utc)
class Model(BaseModel):
uid: UUID = Field(default_factory=uuid4)
updated: datetime = Field(default_factory=datetime_now)
m1 = Model()
m2 = Model()
assert m1.uid != m2.uid
Vous pouvez trouver plus d'informations dans la documentation de la fonction Field
.
Attributs automatiquement exclus¶
Vars de classe¶
Les attributs annotés avec typing.ClassVar
sont correctement traités par Pydantic comme des variables de classe et ne deviendront pas des champs sur les instances du modèle:
from typing import ClassVar
from pydantic import BaseModel
class Model(BaseModel):
x: int = 2
y: ClassVar[int] = 1
m = Model()
print(m)
#> x=2
print(Model.y)
#> 1
Attributs du modèle privé¶
??? API "Documentation API" pydantic.fields.PrivateAttr
Les attributs dont le nom est précédé d'un trait de soulignement ne sont pas traités comme des champs par Pydantic et ne sont pas inclus dans le schéma du modèle. Au lieu de cela, ceux-ci sont convertis en un "attribut privé" qui n'est pas validé ni même défini lors des appels à __init__
, model_validate
, etc.
!!! note À partir de Pydantic v2.1.0, vous recevrez une NameError si vous essayez d'utiliser la fonction Field
avec un attribut privé. Étant donné que les attributs privés ne sont pas traités comme des champs, la fonction Field() ne peut pas être appliquée.
Voici un exemple d'utilisation :
from datetime import datetime
from random import randint
from pydantic import BaseModel, PrivateAttr
class TimeAwareModel(BaseModel):
_processed_at: datetime = PrivateAttr(default_factory=datetime.now)
_secret_value: str
def __init__(self, **data):
super().__init__(**data)
# this could also be done with default_factory
self._secret_value = randint(1, 5)
m = TimeAwareModel()
print(m._processed_at)
#> 2032-01-02 03:04:05.000006
print(m._secret_value)
#> 3
Les noms d'attributs privés doivent commencer par un trait de soulignement pour éviter les conflits avec les champs du modèle. Cependant, les noms plus étranges (tels que __attr__
) ne sont pas pris en charge.
Conversion de données¶
Pydantic peut convertir les données d'entrée pour les forcer à se conformer aux types de champs du modèle, ce qui, dans certains cas, peut entraîner une perte d'informations. Par exemple:
from pydantic import BaseModel
class Model(BaseModel):
a: int
b: float
c: str
print(Model(a=3.000, b='2.72', c=b'binary data').model_dump())
#> {'a': 3, 'b': 2.72, 'c': 'binary data'}
Il s’agit d’une décision délibérée de Pydantic et constitue souvent l’approche la plus utile. Voir ici pour une discussion plus longue sur le sujet.
Néanmoins, une vérification de type stricte est également prise en charge.
Signature du modèle¶
Tous les modèles Pydantic verront leur signature générée en fonction de leurs champs:
import inspect
from pydantic import BaseModel, Field
class FooModel(BaseModel):
id: int
name: str = None
description: str = 'Foo'
apple: int = Field(alias='pear')
print(inspect.signature(FooModel))
#> (*, id: int, name: str = None, description: str = 'Foo', pear: int) -> None
Une signature précise est utile à des fins d'introspection et pour des bibliothèques comme FastAPI
ou hypothesis
.
La signature générée respectera également les fonctions __init__
personnalisées:
import inspect
from pydantic import BaseModel
class MyModel(BaseModel):
id: int
info: str = 'Foo'
def __init__(self, id: int = 1, *, bar: str, **data) -> None:
"""My custom init!"""
super().__init__(id=id, bar=bar, **data)
print(inspect.signature(MyModel))
#> (id: int = 1, *, bar: str, info: str = 'Foo') -> None
Pour être inclus dans la signature, l'alias ou le nom d'un champ doit être un identifiant Python valide. Pydantic donnera la priorité à l'alias d'un champ par rapport à son nom lors de la génération de la signature, mais pourra utiliser le nom du champ si l'alias n'est pas un identifiant Python valide.
Si l'alias et le nom d'un champ ne sont pas tous deux des identifiants valides (ce qui peut être possible grâce à une utilisation exotique de create_model
), un argument **data
sera ajouté. De plus, l'argument **data
sera toujours présent dans la signature si model_config['extra'] == 'allow'
.
Correspondance des modèles structurels¶
Pydantic prend en charge la correspondance de modèles structurels pour les modèles, comme introduit par PEP 636 dans Python 3.10.
from pydantic import BaseModel
class Pet(BaseModel):
name: str
species: str
a = Pet(name='Bones', species='dog')
match a:
# match `species` to 'dog', declare and initialize `dog_name`
case Pet(species='dog', name=dog_name):
print(f'{dog_name} is a dog')
#> Bones is a dog
# default case
case _:
print('No dog matched')
!!! note Une instruction match-case peut donner l'impression qu'elle crée un nouveau modèle, mais ne vous y trompez pas ; c'est juste du sucre syntaxique pour obtenir un attribut et soit le comparer, soit le déclarer et l'initialiser.
Copies d'attributs¶
Dans de nombreux cas, les arguments passés au constructeur seront copiés afin d'effectuer la validation et, si nécessaire, la coercition.
Dans cet exemple, notez que l'ID de la liste change après la construction de la classe car elle a été copiée lors de la validation:
from typing import List
from pydantic import BaseModel
class C1:
arr = []
def __init__(self, in_arr):
self.arr = in_arr
class C2(BaseModel):
arr: List[int]
arr_orig = [1, 9, 10, 3]
c1 = C1(arr_orig)
c2 = C2(arr=arr_orig)
print('id(c1.arr) == id(c2.arr):', id(c1.arr) == id(c2.arr))
#> id(c1.arr) == id(c2.arr): False
!!! note Il existe certaines situations dans lesquelles Pydantic ne copie pas les attributs, comme lors de la transmission de modèles — nous utilisons le modèle tel quel. Vous pouvez remplacer ce comportement en définissant model_config['revalidate_instances'] = 'always'
.
Champs supplémentaires¶
Par défaut, les modèles Pydantic n'afficheront pas d'erreur lorsque vous fournirez des données pour des champs non reconnus, ils seront simplement ignorés:
from pydantic import BaseModel
class Model(BaseModel):
x: int
m = Model(x=1, y='a')
assert m.model_dump() == {'x': 1}
Si vous souhaitez que cela génère une erreur, vous pouvez y parvenir via model_config
:
from pydantic import BaseModel, ConfigDict, ValidationError
class Model(BaseModel):
x: int
model_config = ConfigDict(extra='forbid')
try:
Model(x=1, y='a')
except ValidationError as exc:
print(exc)
"""
1 validation error for Model
y
Extra inputs are not permitted [type=extra_forbidden, input_value='a', input_type=str]
"""
Pour conserver les données supplémentaires fournies, vous pouvez définir extra='allow'
. Les champs supplémentaires seront ensuite stockés dans BaseModel.__pydantic_extra__
:
from pydantic import BaseModel, ConfigDict
class Model(BaseModel):
x: int
model_config = ConfigDict(extra='allow')
m = Model(x=1, y='a')
assert m.__pydantic_extra__ == {'y': 'a'}
Par défaut, aucune validation ne sera appliquée à ces éléments supplémentaires, mais vous pouvez définir un type pour les valeurs en remplaçant l'annotation de type pour __pydantic_extra__
:
from typing import Dict
from pydantic import BaseModel, ConfigDict, Field, ValidationError
class Model(BaseModel):
__pydantic_extra__: Dict[str, int] = Field(init=False) # (1)!
x: int
model_config = ConfigDict(extra='allow')
try:
Model(x=1, y='a')
except ValidationError as exc:
print(exc)
"""
1 validation error for Model
y
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
"""
m = Model(x=1, y='2')
assert m.x == 1
assert m.y == 2
assert m.model_dump() == {'x': 1, 'y': 2}
assert m.__pydantic_extra__ == {'y': 2}
- Le
= Field(init=False)
n'a aucun effet au moment de l'exécution, mais empêche le champ__pydantic_extra__
d'être traité comme un argument de la méthode__init__
du modèle par les vérificateurs de type.
Les mêmes configurations s'appliquent à TypedDict
et dataclass
', sauf que la configuration est contrôlée en définissant l'attribut __pydantic_config__
de la classe sur un ConfigDict
valide.
本文总阅读量次