Aller au contenu

Mypy

Pydantic fonctionne bien avec mypy dès la sortie de la boîte.

Cependant, Pydantic est également livré avec un plugin mypy qui ajoute à mypy un certain nombre de fonctionnalités importantes spécifiques à pydantic qui améliorent sa capacité à vérifier la saisie de votre code.

Par exemple, considérons le script suivant:

from datetime import datetime
from typing import List, Optional

from pydantic import BaseModel


class Model(BaseModel):
    age: int
    first_name = 'John'
    last_name: Optional[str] = None
    signup_ts: Optional[datetime] = None
    list_of_ints: List[int]


m = Model(age=42, list_of_ints=[1, '2', b'3'])
print(m.middle_name)  # not a model field!
Model()  # will raise a validation error for age and list_of_ints

Sans aucune configuration spéciale, mypy ne détecte pas l' annotation de champ de modèle manquante et met en garde contre l'argument list_of_ints que Pydantic analyse correctement:

test.py:15: error: List item 1 has incompatible type "str"; expected "int"  [list-item]
test.py:15: error: List item 2 has incompatible type "bytes"; expected "int"  [list-item]
test.py:16: error: "Model" has no attribute "middle_name"  [attr-defined]
test.py:17: error: Missing named argument "age" for "Model"  [call-arg]
test.py:17: error: Missing named argument "list_of_ints" for "Model"  [call-arg]

Mais avec le plugin activé , cela donne la bonne erreur:

9: error: Untyped fields disallowed  [pydantic-field]
16: error: "Model" has no attribute "middle_name"  [attr-defined]
17: error: Missing named argument "age" for "Model"  [call-arg]
17: error: Missing named argument "list_of_ints" for "Model"  [call-arg]

Avec le plugin pydantic mypy, vous pouvez sans crainte refactoriser vos modèles en sachant que mypy détectera toute erreur si les noms ou types de vos champs changent.

Il y a aussi d’autres avantages ! Voir ci-dessous pour plus de détails.

Utiliser mypy sans le plugin

Vous pouvez exécuter votre code via mypy avec:

mypy \
  --ignore-missing-imports \
  --follow-imports=skip \
  --strict-optional \
  pydantic_mypy_test.py

Stricte Facultatif

Pour que votre code soit transmis avec --strict-optional , vous devez utiliserOptional Optional[] ou un alias de Optional[] pour tous les champs avec None par défaut. (C'est standard avec mypy.)

Autres interfaces Pydantiques

Les classes de données Pydantic et le décorateur validate_call devraient également bien fonctionner avec mypy.

Capacités du plugin Mypy

Générer une signature pour Model.__init__

  • Tous les champs obligatoires qui n'ont pas d'alias déterminés dynamiquement seront inclus en tant qu'arguments de mot clé obligatoires.
  • Si Config.populate_by_name=True , la signature générée utilisera les noms de champs plutôt que les alias.
  • Si Config.extra='forbid' et que vous n'utilisez pas d'alias déterminés dynamiquement, la signature générée n'autorisera pas les entrées inattendues.
  • Facultatif: si le paramètre du plugin init_forbid_extra est défini sur True , les entrées inattendues dans __init__ généreront des erreurs même si Config.extra n'est pas 'forbid' .
  • Facultatif: Si le paramètre du plugin init_typed est défini sur True , la signature générée utilisera les types des champs du modèle (sinon, ils seront annotés comme Any pour permettre l'analyse).

Générer une signature tapée pour Model.model_construct

  • La méthode model_construct est une alternative à __init__ lorsque les données d'entrée sont connues pour être valides et ne doivent pas être analysées. Étant donné que cette méthode n'effectue aucune validation à l'exécution, la vérification statique est importante pour détecter les erreurs.

Respecter Config.frozen

  • Si Config.frozen vaut True , vous obtiendrez une erreur mypy si vous essayez de modifier la valeur d'un champ de modèle; cf. fausse immuabilité .

Générer une signature pour dataclasses

  • classes décorées avec @pydantic.dataclasses.dataclass sont vérifiés de la même manière que les classes de données Python standard
  • Le @pydantic.dataclasses.dataclass decorator accepte un argument de mot-clé config qui a la même signification que la sous-classe Config .

Respecter le type du Field default et default_factory

  • Un champ avec à la fois un default et un default_factory entraînera une erreur lors de la vérification statique.
  • Le type de la valeur default et default_factory doit être compatible avec celui du champ.

Avertir de l'utilisation de champs non typés

  • Vous obtiendrez une erreur mypy chaque fois que vous attribuerez un attribut public à un modèle sans annoter son type.
  • Si votre objectif est de définir un ClassVar, vous devez annoter explicitement le champ en utilisant typing.ClassVar

Capacités optionnelles:

Empêcher l'utilisation des alias dynamiques requis

  • Si le paramètre du plugin warn_required_dynamic_aliases est défini sur True , vous obtiendrez une erreur mypy chaque fois que vous utiliserez un alias ou un générateur d'alias déterminé dynamiquement sur un modèle avec Config.populate_by_name=False .
  • Ceci est important car si de tels alias sont présents, mypy ne peut pas taper correctement les appels de vérification à __init__ . Dans ce cas, tous les arguments seront traités par défaut comme facultatifs.

Activation du plugin

Pour activer le plugin, ajoutez simplement pydantic.mypy à la liste des plugins dans votre fichier de configuration mypy (cela pourrait être mypy.ini , pyproject.toml ou setup.cfg ).

Pour commencer, tout ce que vous avez à faire est de créer un fichier mypy.ini avec le contenu suivant:

[mypy]
plugins = pydantic.mypy

!!! note Si vous utilisez des modèles pydantic.v1 , vous devrez ajouter pydantic.v1.mypy à votre liste de plugins.

Le plugin est compatible avec les versions mypy >=0.930 .

Consultez la documentation de configuration du plugin pour plus de détails.

Configuration du plugin

Pour modifier les valeurs des paramètres du plugin, créez une section dans votre fichier de configuration mypy appelée [pydantic-mypy] et ajoutez toutes les paires clé-valeur pour les paramètres que vous souhaitez remplacer.

Un fichier mypy.ini avec tous les indicateurs de rigueur du plugin activés (et quelques autres indicateurs de rigueur mypy également) pourrait ressembler à:

[mypy]
plugins = pydantic.mypy

follow_imports = silent
warn_redundant_casts = True
warn_unused_ignores = True
disallow_any_generics = True
check_untyped_defs = True
no_implicit_reexport = True

# for strict mypy: (this is the tricky one :-))
disallow_untyped_defs = True

[pydantic-mypy]
init_forbid_extra = True
init_typed = True
warn_required_dynamic_aliases = True

À partir de mypy>=0.900 , la configuration mypy peut également être incluse dans le fichier pyproject.toml plutôt que dans mypy.ini . La même configuration que ci-dessus serait:

[tool.mypy]
plugins = [
  "pydantic.mypy"
]

follow_imports = "silent"
warn_redundant_casts = true
warn_unused_ignores = true
disallow_any_generics = true
check_untyped_defs = true
no_implicit_reexport = true

# for strict mypy: (this is the tricky one :-))
disallow_untyped_defs = true

[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true

Remarque sur --disallow-any-explicit

Si vous utilisez le paramètre de configuration --disallow-any-explicit mypy (ou d'autres paramètres qui interdisent Any ), vous risquez de rencontrer des erreurs no-any-explicit lors de l'extension BaseModel . En effet, par défaut, le plugin mypy de Pydantic ajoute une méthode __init__ avec une signature comme def __init__(self, field_1: Any, field_2: Any, **kwargs: Any):

!!! note "Pourquoi la signature supplémentaire ?" Le plugin Pydantic mypy ajoute une méthode __init__ avec une signature comme def __init__(self, field_1: Any, field_2: Any, **kwargs: Any): afin d'éviter les erreurs de type lors de l'initialisation de modèles avec des types qui ne correspondent pas aux annotations de champ. Par exemple, Model(date='2024-01-01') générerait une erreur de type sans cette signature Any , mais Pydantic a la capacité d'analyser la chaîne '2024-01-01' dans un type datetime.date .

Pour résoudre ce problème, vous devez activer les paramètres du mode strict pour le plugin Pydantic mypy. Plus précisément, ajoutez ces options à votre section [pydantic-mypy]:

[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true

Avec init_forbid_extra = True , les **kwargs sont supprimés de la signature __init__ générée. Avec init_typed = True , les types Any des champs sont remplacés par leurs indications de type réelles.

Cette configuration vous permet d'utiliser --disallow-any-explicit sans obtenir d'erreurs sur vos modèles Pydantic. Cependant, sachez que cette vérification plus stricte peut signaler certains cas d'utilisation valides de Pydantic (comme le passage d'une chaîne pour un champ datetime) comme des erreurs de type.


本文总阅读量