Mypy
Pydantic works well with mypy right out of the box.
However, Pydantic also ships with a mypy plugin that adds a number of important Pydantic-specific features that improve its ability to type-check your code.
For example, consider the following script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Without any special configuration, mypy does not catch the missing model field annotation
and errors about the list_of_ints
argument which Pydantic parses correctly:
15: error: List item 1 has incompatible type "str"; expected "int" [list-item]
15: error: List item 2 has incompatible type "bytes"; expected "int" [list-item]
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]
But with the plugin enabled, it gives the correct errors:
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]
With the pydantic mypy plugin, you can fearlessly refactor your models knowing mypy will catch any mistakes if your field names or types change.
Note that mypy already supports some features without using the Pydantic plugin, such as synthesizing a __init__
method for Pydantic models and dataclasses. See the mypy plugin capabilities for a list
of additional features.
Enabling the Plugin¶
To enable the plugin, just add pydantic.mypy
to the list of plugins in your
mypy config file:
[mypy]
plugins = pydantic.mypy
[tool.mypy]
plugins = ['pydantic.mypy']
Note
If you're using pydantic.v1
models, you'll need to add pydantic.v1.mypy
to your list of plugins.
See the plugin configuration for more details.
Supported mypy versions¶
Pydantic supports the mypy versions released less than 6 months ago. Older versions may still work with the plugin but won't be tested. The list of released mypy versions can be found here. Note that the version support policy is subject to change at discretion of contributors.
Mypy plugin capabilities¶
Generate a __init__
signature for Pydantic models¶
- Any required fields that don't have dynamically-determined aliases will be included as required keyword arguments.
- If the
populate_by_name
model configuration value is set toTrue
, the generated signature will use the field names rather than aliases. - The
init_forbid_extra
andinit_typed
plugin configuration values can further fine-tune the synthesized__init__
method.
Generate a typed signature for model_construct
¶
- The
model_construct
method is an alternative to model validation when input data is known to be valid and should not be parsed (see the documentation). Because this method performs no runtime validation, static checking is important to detect errors.
Support for frozen models¶
- If the
frozen
configuration is set toTrue
, you will get an error if you try mutating a model field (see faux immutability)
Respect the type of the Field
's default
and default_factory
¶
- Field with both a
default
and adefault_factory
will result in an error during static checking. - The type of the
default
anddefault_factory
value must be compatible with the one of the field.
Warn about the use of untyped fields¶
- While defining a field without an annotation will result in a runtime error, the plugin will also emit a type checking error.
Prevent the use of required dynamic aliases¶
See the documentation of the warn_required_dynamic_aliases
plugin configuration value.
Configuring the Plugin¶
To change the values of the plugin settings, create a section in your mypy config file called [pydantic-mypy]
,
and add any key-value pairs for settings you want to override.
A configuration file with all plugin strictness flags enabled (and some other mypy strictness flags, too) might look like:
[mypy]
plugins = pydantic.mypy
follow_imports = silent
warn_redundant_casts = True
warn_unused_ignores = True
disallow_any_generics = True
no_implicit_reexport = True
disallow_untyped_defs = True
[pydantic-mypy]
init_forbid_extra = True
init_typed = True
warn_required_dynamic_aliases = True
[tool.mypy]
plugins = ["pydantic.mypy"]
follow_imports = "silent"
warn_redundant_casts = true
warn_unused_ignores = true
disallow_any_generics = true
no_implicit_reexport = true
disallow_untyped_defs = true
[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true
init_typed
¶
Because Pydantic performs data conversion by default, the following is still valid at runtime:
class Model(BaseModel):
a: int
Model(a='1')
For this reason, the plugin will use Any
for field annotations when synthesizing the __init__
method,
unless init_typed
is set or strict mode is enabled on the model.
init_forbid_extra
¶
By default, Pydantic allows (and ignores) any extra provided argument:
class Model(BaseModel):
a: int = 1
Model(unrelated=2)
For this reason, the plugin will add an extra **kwargs: Any
parameter when synthesizing the __init__
method, unless
init_forbid_extra
is set or the extra
is set to 'forbid'
.
warn_required_dynamic_aliases
¶
Whether to error when using a dynamically-determined alias or alias generator on a model with
populate_by_name
set to False
. If such aliases are
present, mypy cannot properly type check calls to __init__
. In this case, it will default to
treating all arguments as not required.
Compatibility with Any
being disallowed
Some mypy configuration options (such as disallow_any_explicit
)
will error because the synthesized __init__
method contains Any
annotations. To circumvent the issue, you will have
to enable both init_forbid_extra
and init_typed
.