Aller au contenu

??? 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 obligatoire
  • name , 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:

!!! 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, une ValidationError 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.
  • 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:

  1. Déclarez une ou plusieurs instances typing.TypeVar à utiliser pour paramétrer votre modèle.
  2. Déclarez un modèle pydantic qui hérite de pydantic.BaseModel et typing.Generic , où vous transmettez les instances TypeVar comme paramètres à typing.Generic .
  3. 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 comme Any .

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}}
  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 champ item sont perdues.
  2. 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}
  1. 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.


本文总阅读量