Mypy
Pydanticはすぐにmypyとうまく連携します。
Pydanticにはmypyプラグインも付属しており、コードの型チェック機能を改善するpydantic固有の重要な機能がmypyに追加されています。
たとえば、次のスクリプトを考えてみます。
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
特別な設定がなければ、mypyはmissing model field annotationをキャッチせず、Pydanticが正しく解析するlist_of_ints
引数について警告します。
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]
しかし、with the plugin enabled、正しいエラーが表示されます。
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]
pydantic mypyプラグインを使用すると、フィールド名や型が変更された場合にmypyが間違いをキャッチすることを知っているので、恐れずにモデルをリファクタリングすることができます。
他にも利点があります!詳細は以下を参照してください。
Using mypy without the plugin¶
コードをmypyで実行するには、次のようにします。
mypy \
--ignore-missing-imports \
--follow-imports=skip \
--strict-optional \
pydantic_mypy_test.py
Strict Optional¶
コードで--strict-optional
を渡すためには、デフォルトとしてNone
を持つすべてのフィールドにOptional[]
またはOptional[]
のエイリアスを使用する必要があります(これはmypyでは標準です)。
Other Pydantic interfaces¶
Pydanticdataclassesとvalidate_call
decoratorもmypyでうまく動作するはずです。
Mypy Plugin Capabilities¶
Generate a signature for Model.__init__
¶
- 動的に決定されるエイリアスを持たない必須フィールドは、必須キーワード引数として含まれます。
Config.populate_by_name=True
の場合、生成されたシグネチャはエイリアスではなくフィールド名を使用します。Config.extra='forbid'
で、動的に決定されるエイリアスを使用しない場合、生成されたシグネチャは予期しない入力を許可しません。- オプション:
init_forbid_extra
plugin settingがTrue
に設定されている場合、Config.extra
が'forbid'
でなくても、__init__
への予期しない入力によってエラーが発生します。 - オプション:
init_typed
plugin settingがTrue
に設定されている場合、生成されたシグネチャはモデルフィールドの型を使用します(そうでない場合は、解析を可能にするためにAny
として注釈が付けられます)。
Generate a typed signature for Model.model_construct
¶
model_construct
メソッドは、入力データが有効であることがわかっており、解析する必要がない場合に、__init__
の代わりに使用できます。このメソッドは実行時に検証を行わないため、エラーを検出するには静的チェックが重要です。
Respect Config.frozen
¶
Config.frozen
がTrue
の場合、モデルフィールドの値を変更しようとするとmypyエラーが発生します。faux immutability
Generate a signature for dataclasses
¶
@pydantic.dataclasses.dataclass
で装飾されたクラスは、標準のPythonデータクラスと同様に型チェックされます。@pydantic.dataclasses.dataclass
デコレータは、theConfig
sub-classと同じ意味を持つconfig
キーワード引数を受け入れます。
Respect the type of the Field
's default
and default_factory
¶
default
とdefault_factory
の両方を持つフィールドは、静的チェック中にエラーになります。default
およびdefault_factory
値の型は、フィールドの型と互換性がなければなりません。
Warn about the use of untyped fields¶
- 型に注釈を付けずにモデルにパブリック属性を割り当てると、mypyエラーが発生します。
- 目的がClassVarを設定することである場合は、入力を使用してフィールドに明示的に注釈を付ける必要があります。ClassVar
Optional Capabilities:¶
Prevent the use of required dynamic aliases¶
warn_required_dynamic_aliases
plugin settingがTrue
に設定されている場合、Config.populate_by_name=False
のモデルで動的に決定されるエイリアスまたはエイリアスジェネレータを使用すると、mypyエラーが発生します。- このようなエイリアスが存在すると、mypyは
__init__
を正しく型チェックできないので、これは重要です。 この場合、デフォルトですべての引数がオプションとして扱われます。
Enabling the Plugin¶
プラグインを有効にするには、mypy config fileのプラグインのリストにpydantic.mypy
を追加します(mypy.ini
,pyproject.toml
,setup.cfg
)。
開始するには、以下の内容のmypy.ini
ファイルを作成するだけです。
[mypy]
plugins = pydantic.mypy
Note
pydantic.v1
モデルを使用している場合は、プラグインのリストにpydantic.v1.mypy
を追加する必要があります。
このプラグインはmypyバージョン>=0.930
と互換性があります。
詳細については、plugin configurationドキュメントを参照してください。
Configuring the Plugin¶
プラグイン設定の値を変更するには、mypy設定ファイルに[pydantic-mypy]
というセクションを作成し、オーバーライドしたい設定のキーと値のペアを追加します。
プラグインのstrictnessフラグをすべて有効にしたmypy.ini
ファイル(およびその他のmypy strictnessフラグも)は、次のようになります。
[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
mypy>=0.900
の時点で、mypy configはmypy.ini
ではなくpyproject.toml
ファイルに含めることもできます。
上記と同じ設定は次のようになります。
[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
Note on --disallow-any-explicit
¶
--disallow-any-explicit
mypy設定(またはAny
を禁止するその他の設定)を使用している場合、BaseModel
を拡張するときにno-any-explicit
エラーが発生する可能性があります。これは、デフォルトでは、Pydanticのmypy
プラグインがdef__init__(self, field_1:Any, field_2:Any, **kwargs:Any):
のようなシグネチャを持つ__init__
メソッドを追加するためです。
Why the extra signature?
Pydanticmypy
プラグインは、フィールドアノテーションと一致しない型でモデルを初期化する際の型エラーを避けるために、def__init__(self, field_1:Any, field_2:Any, **kwargs:Any):
のようなシグネチャを持つ__init__
メソッドを追加します。
例えばModel(date='2024-01-01')
はこのAny
シグネチャがなければ型エラーになりますが、Pydanticには文字列'2024-01-01'
をdatetime.date
型に解析する機能があります。
この問題を解決するには、Pydantic mypyプラグインの厳密なモード設定を有効にする必要があります。具体的には、[pydantic-mypy]
セクションに次のオプションを追加します。
[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
init_prohibit_extra=True
の場合、**kwargs
は生成された__init__
シグネチャから削除されます。init_typed=True
の場合、フィールドのAny
型は実際の型ヒントに置き換えられます。
この設定により、Pydanticモデルでエラーを発生させることなく--disallow-any-explicit
を使用することができます。ただし、この厳密なチェックによって、有効なPydanticユースケース(datetimeフィールドに文字列を渡すなど)が型エラーとしてフラグ付けされる可能性があることに注意してください。