Aller au contenu

Pourquoi utiliser Pydantic ?

Aujourd'hui, Pydantic est téléchargé plusieurs fois par mois et utilisé par certaines des organisations les plus grandes et les plus reconnaissables au monde.

Il est difficile de savoir pourquoi tant de gens ont adopté Pydantic depuis sa création il y a six ans, mais voici quelques suppositions.

Tapez des astuces pour la validation du schéma

Le schéma par rapport auquel Pydantic valide est généralement défini par des indices de type Python.

Les indices de type sont parfaits pour cela puisque, si vous écrivez du Python moderne, vous savez déjà comment les utiliser. L'utilisation d'indices de type signifie également que Pydantic s'intègre bien aux outils de typage statique comme mypy et pyright et aux IDE comme pycharm et vscode.

???+ exemple "Exemple - tapez simplement des indices" (Cet exemple nécessite Python 3.9+) ```py nécessite="3.9" en tapant import Annoté, Dict, Liste, Littéral, Tuple

from annotated_types import Gt

from pydantic import BaseModel


class Fruit(BaseModel):
    name: str  # (1)!
    color: Literal['red', 'green']  # (2)!
    weight: Annotated[float, Gt(0)]  # (3)!
    bazam: Dict[str, List[Tuple[int, bool, float]]]  # (4)!


print(
    Fruit(
        name='Apple',
        color='red',
        weight=4.2,
        bazam={'foobar': [(1, True, 0.1)]},
    )
)
#> name='Apple' color='red' weight=4.2 bazam={'foobar': [(1, True, 0.1)]}
```

1. The `name` field is simply annotated with `str` - any string is allowed.
2. The [`Literal`](https://docs.python.org/3/library/typing.html#typing.Literal) type is used to enforce that `color` is either `'red'` or `'green'`.
3. Even when we want to apply constraints not encapsulated in python types, we can use [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated) and [`annotated-types`](https://github.com/annotated-types/annotated-types) to enforce constraints without breaking type hints.
4. I'm not claiming "bazam" is really an attribute of fruit, but rather to show that arbitrarily complex types can easily be validated.

!!! Astuce "En savoir plus" Consultez la documentation sur les types pris en charge.

Performance

La logique de validation de base de Pydantic est implémentée dans un package distinct pydantic-core, où la validation de la plupart des types est implémentée dans Rust.

En conséquence, Pydantic fait partie des bibliothèques de validation de données les plus rapides pour Python.

??? exemple "Exemple de performances - Pydantic vs. code dédié" En général, le code dédié devrait être beaucoup plus rapide qu'un validateur à usage général, mais dans cet exemple, Pydantic est >300% plus rapide que le code dédié lors de l'analyse JSON et de la validation des URL.

```py title="Performance Example"
import json
import timeit
from urllib.parse import urlparse

import requests

from pydantic import HttpUrl, TypeAdapter

reps = 7
number = 100
r = requests.get('https://api.github.com/emojis')
r.raise_for_status()
emojis_json = r.content


def emojis_pure_python(raw_data):
    data = json.loads(raw_data)
    output = {}
    for key, value in data.items():
        assert isinstance(key, str)
        url = urlparse(value)
        assert url.scheme in ('https', 'http')
        output[key] = url


emojis_pure_python_times = timeit.repeat(
    'emojis_pure_python(emojis_json)',
    globals={
        'emojis_pure_python': emojis_pure_python,
        'emojis_json': emojis_json,
    },
    repeat=reps,
    number=number,
)
print(f'pure python: {min(emojis_pure_python_times) / number * 1000:0.2f}ms')
#> pure python: 5.32ms

type_adapter = TypeAdapter(dict[str, HttpUrl])
emojis_pydantic_times = timeit.repeat(
    'type_adapter.validate_json(emojis_json)',
    globals={
        'type_adapter': type_adapter,
        'HttpUrl': HttpUrl,
        'emojis_json': emojis_json,
    },
    repeat=reps,
    number=number,
)
print(f'pydantic: {min(emojis_pydantic_times) / number * 1000:0.2f}ms')
#> pydantic: 1.54ms

print(
    f'Pydantic {min(emojis_pure_python_times) / min(emojis_pydantic_times):0.2f}x faster'
)
#> Pydantic 3.45x faster
```

Contrairement à d'autres bibliothèques centrées sur les performances écrites dans des langages compilés, Pydantic dispose également d'un excellent support pour la personnalisation de la validation via des validateurs fonctionnels.

!!! tip "En savoir plus" La conférence de Samuel Colvin à la PyCon 2023 explique comment fonctionne pydantic-core et comment il s'intègre à Pydantic.

Sérialisation

Pydantic fournit des fonctionnalités pour sérialiser le modèle de trois manières:

  1. Vers un dict Python composé des objets Python associés
  2. Vers un dict Python composé uniquement de types "jsonable"
  3. Vers une chaîne JSON

Dans les trois modes, la sortie peut être personnalisée en excluant des champs spécifiques, en excluant les champs non définis, en excluant les valeurs par défaut et en excluant les valeurs Aucune.

??? exemple "Exemple - Sérialisation 3 façons" ```py from datetime import datetime

from pydantic import BaseModel


class Meeting(BaseModel):
    when: datetime
    where: bytes
    why: str = 'No idea'


m = Meeting(when='2020-01-01T12:00', where='home')
print(m.model_dump(exclude_unset=True))
#> {'when': datetime.datetime(2020, 1, 1, 12, 0), 'where': b'home'}
print(m.model_dump(exclude={'where'}, mode='json'))
#> {'when': '2020-01-01T12:00:00', 'why': 'No idea'}
print(m.model_dump_json(exclude_defaults=True))
#> {"when":"2020-01-01T12:00:00","where":"home"}
```

!!! tip "En savoir plus" Voir la documentation sur la sérialisation.

Schéma JSON

Le schéma JSON peut être généré pour n'importe quel schéma Pydantic, permettant des API auto-documentées et l'intégration avec une grande variété d'outils prenant en charge le schéma JSON.

??? exemple "Exemple - Schéma JSON" ```py from datetime import datetime

from pydantic import BaseModel Adresse de classe (BaseModel): rue: rue ville: rue code postal: rue classe Réunion (BaseModel): quand: dateheure où: Adresse pourquoi: str = 'Aucune idée' print(Meeting.model_json_schema()) """ { ' $defs':{ 'Adresse': { 'propriétés': { 'rue' : {'titre' : 'Rue', 'type' : 'chaîne'}, 'ville' : {'titre' : 'Ville', 'type' : 'chaîne'}, 'code postal': {'titre': 'code postal', 'type': 'chaîne'}, }, 'obligatoire': ['rue', 'ville', 'code postal'], 'titre' : 'Adresse', 'type' : 'objet', } }, 'propriétés': { 'quand' : {'format' : 'date-heure', 'titre' : 'Quand', 'type' : 'string'}, 'où' : {'$ ref' : '#/$defs/Adresse'}, 'pourquoi': {'default': 'Aucune idée', 'title': 'Pourquoi', 'type': 'string'}, }, 'obligatoire': ['quand', 'où'], 'titre' : 'Réunion', 'type' : 'objet', } """ ```

Pydantic génère JSON Schema version 2020-12, la dernière version du standard compatible avec OpenAPI 3.1.

!!! tip "En savoir plus" Voir la documentation sur le schéma JSON.

Mode strict et coercition des données

Par défaut, Pydantic tolère les types incorrects courants et force les données vers le bon type — par exemple, une chaîne numérique passée à un champ int sera analysée comme un int.

Pydantic a également un mode strict=True — également connu sous le nom de « mode strict » — dans lequel les types ne sont pas contraints et une erreur de validation est générée à moins que les données d'entrée ne correspondent exactement au schéma ou à l'indice de type.

Mais le mode strict serait plutôt inutile lors de la validation des données JSON, car JSON n'a pas de types correspondant à de nombreux types Python courants comme datetime, UUID ou bytes.

Pour résoudre ce problème, Pydantic peut analyser et valider JSON en une seule étape. Cela permet une conversion de données sensible comme les chaînes RFC3339 (alias ISO8601) en objets datetime. Puisque l’analyse JSON est implémentée dans Rust, elle est également très performante.

??? exemple "Exemple - Mode strict réellement utile" ```py from datetime import datetime

from pydantic import BaseModel, ValidationError


class Meeting(BaseModel):
    when: datetime
    where: bytes


m = Meeting.model_validate({'when': '2020-01-01T12:00', 'where': 'home'})
print(m)
#> when=datetime.datetime(2020, 1, 1, 12, 0) where=b'home'
try:
    m = Meeting.model_validate(
        {'when': '2020-01-01T12:00', 'where': 'home'}, strict=True
    )
except ValidationError as e:
    print(e)
    """
    2 validation errors for Meeting
    when
      Input should be a valid datetime [type=datetime_type, input_value='2020-01-01T12:00', input_type=str]
    where
      Input should be a valid bytes [type=bytes_type, input_value='home', input_type=str]
    """

m_json = Meeting.model_validate_json(
    '{"when": "2020-01-01T12:00", "where": "home"}'
)
print(m_json)
#> when=datetime.datetime(2020, 1, 1, 12, 0) where=b'home'
```

!!! tip "En savoir plus" Voir la documentation sur le mode strict.

Classes de données, TypedDicts et plus

Pydantic propose quatre façons de créer des schémas et d'effectuer la validation et la sérialisation:

  1. BaseModel — La super classe de Pydantic avec de nombreux utilitaires courants disponibles via des méthodes d'instance.
  2. pydantic.dataclasses.dataclass - un wrapper autour des classes de données standard qui effectue la validation lorsqu'une classe de données est initialisée.
  3. TypeAdapter — un moyen général d'adapter n'importe quel type pour la validation et la sérialisation. Cela permet de valider des types comme TypedDict et NamedTuple ainsi que des valeurs scalaires simples comme int ou timedelta — tous les types pris en charge peuvent être utilisés avec TypeAdapter.
  4. validate_call — un décorateur pour effectuer une validation lors de l'appel d'une fonction.

??? exemple "Exemple - schéma basé sur TypedDict" ```py from datetime import datetime

from typing_extensions import NotRequired, TypedDict

from pydantic import TypeAdapter


class Meeting(TypedDict):
    when: datetime
    where: bytes
    why: NotRequired[str]


meeting_adapter = TypeAdapter(Meeting)
m = meeting_adapter.validate_python(  # (1)!
    {'when': '2020-01-01T12:00', 'where': 'home'}
)
print(m)
#> {'when': datetime.datetime(2020, 1, 1, 12, 0), 'where': b'home'}
meeting_adapter.dump_python(m, exclude={'where'})  # (2)!

print(meeting_adapter.json_schema())  # (3)!
"""
{
    'properties': {
        'when': {'format': 'date-time', 'title': 'When', 'type': 'string'},
        'where': {'format': 'binary', 'title': 'Where', 'type': 'string'},
        'why': {'title': 'Why', 'type': 'string'},
    },
    'required': ['when', 'where'],
    'title': 'Meeting',
    'type': 'object',
}
"""
```

1. `TypeAdapter` for a `TypedDict` performing validation, it can also validate JSON data directly with `validate_json`
2. `dump_python` to serialise a `TypedDict` to a python object, it can also serialise to JSON with `dump_json`
3. `TypeAdapter` can also generate JSON Schema

Personnalisation

Des validateurs et sérialiseurs fonctionnels, ainsi qu'un protocole puissant pour les types personnalisés, signifient que le fonctionnement de Pydantic peut être personnalisé par champ ou par type.

??? example "Exemple de personnalisation - validateurs d'enveloppement" Les "validateurs d'enveloppement" sont nouveaux dans Pydantic V2 et constituent l'un des moyens les plus puissants de personnaliser la validation Pydantic. ```py depuis datetime importer datetime, fuseau horaire

from pydantic import BaseModel, field_validator


class Meeting(BaseModel):
    when: datetime

    @field_validator('when', mode='wrap')
    def when_now(cls, input_value, handler):
        if input_value == 'now':
            return datetime.now()
        when = handler(input_value)
        # in this specific application we know tz naive datetimes are in UTC
        if when.tzinfo is None:
            when = when.replace(tzinfo=timezone.utc)
        return when


print(Meeting(when='2020-01-01T12:00+01:00'))
#> when=datetime.datetime(2020, 1, 1, 12, 0, tzinfo=TzInfo(+01:00))
print(Meeting(when='now'))
#> when=datetime.datetime(2032, 1, 2, 3, 4, 5, 6)
print(Meeting(when='2020-01-01T12:00'))
#> when=datetime.datetime(2020, 1, 1, 12, 0, tzinfo=datetime.timezone.utc)
```

!!! Astuce "En savoir plus" Consultez la documentation sur les validateurs, les sérialiseurs personnalisés et les types personnalisés.

Écosystème

Au moment de la rédaction de cet article, il existe 214100 référentiels sur GitHub et 8119 packages sur PyPI qui dépendent de Pydantic.

Quelques bibliothèques notables qui dépendent de Pydantic:

D'autres bibliothèques utilisant Pydantic peuvent être trouvées sur Kludex/awesome-pydantic.

Organisations utilisant Pydantic

Certaines entreprises et organisations notables utilisant Pydantic ainsi que des commentaires sur pourquoi/comment nous savons qu'ils utilisent Pydantic.

Les organisations ci-dessous sont incluses car elles répondent à un ou plusieurs des critères suivants:

  • Utiliser pydantic comme dépendance dans un référentiel public
  • Trafic référant vers le site de documentation pydantic à partir d'un domaine interne à l'organisation - les référents spécifiques ne sont pas inclus car ils ne sont généralement pas dans le domaine public
  • Communication directe entre l'équipe Pydantic et les ingénieurs employés par l'organisation sur l'utilisation de Pydantic au sein de l'organisation

Nous avons inclus des détails supplémentaires le cas échéant et déjà dans le domaine public.

{{ organisations }}


本文总阅读量