Skip to content

Commit

Permalink
fix mypy with default_factory=list etc. (#4471)
Browse files Browse the repository at this point in the history
* fix mypy with default_factory=list etc.

* fix tests

* add error case
  • Loading branch information
samuelcolvin committed Sep 5, 2022
1 parent 5a2ddec commit 02cf7f5
Show file tree
Hide file tree
Showing 7 changed files with 39 additions and 7 deletions.
1 change: 1 addition & 0 deletions changes/4457-samuelcolvin.md
@@ -0,0 +1 @@
Fix mypy plugin when using bare types like `list` and `dict` as `default_factory`.
15 changes: 12 additions & 3 deletions pydantic/mypy.py
Expand Up @@ -83,7 +83,8 @@ def parse_mypy_version(version: str) -> Tuple[int, ...]:
return tuple(int(part) for part in version.split('+', 1)[0].split('.'))


BUILTINS_NAME = 'builtins' if parse_mypy_version(mypy_version) >= (0, 930) else '__builtins__'
MYPY_VERSION_TUPLE = parse_mypy_version(mypy_version)
BUILTINS_NAME = 'builtins' if MYPY_VERSION_TUPLE >= (0, 930) else '__builtins__'


def plugin(version: str) -> 'TypingType[Plugin]':
Expand Down Expand Up @@ -163,14 +164,22 @@ def _pydantic_field_callback(self, ctx: FunctionContext) -> 'Type':
# Functions which use `ParamSpec` can be overloaded, exposing the callable's types as a parameter
# Pydantic calls the default factory without any argument, so we retrieve the first item
if isinstance(default_factory_type, Overloaded):
if float(mypy_version) > 0.910:
if MYPY_VERSION_TUPLE > (0, 910):
default_factory_type = default_factory_type.items[0]
else:
# Mypy0.910 exposes the items of overloaded types in a function
default_factory_type = default_factory_type.items()[0] # type: ignore[operator]

if isinstance(default_factory_type, CallableType):
return default_factory_type.ret_type
ret_type = default_factory_type.ret_type
# mypy doesn't think `ret_type` has `args`, you'd think mypy should know,
# add this check in case it varies by version
args = getattr(ret_type, 'args', None)
if args:
if all(isinstance(arg, TypeVarType) for arg in args):
# Looks like the default factory is a type like `list` or `dict`, replace all args with `Any`
ret_type.args = tuple(default_any_type for _ in args) # type: ignore[attr-defined]
return ret_type

return default_any_type

Expand Down
21 changes: 21 additions & 0 deletions tests/mypy/modules/plugin_default_factory.py
@@ -0,0 +1,21 @@
"""
See https://github.com/pydantic/pydantic/issues/4457
"""

from typing import Dict, List

from pydantic import BaseModel, Field


def new_list() -> List[int]:
return []


class Model(BaseModel):
l1: List[str] = Field(default_factory=list)
l2: List[int] = Field(default_factory=new_list)
l3: List[str] = Field(default_factory=lambda: list())
l4: Dict[str, str] = Field(default_factory=dict)
l5: int = Field(default_factory=lambda: 123)
l6_error: List[str] = Field(default_factory=new_list)
l7_error: int = Field(default_factory=list)
3 changes: 1 addition & 2 deletions tests/mypy/outputs/plugin-fail-strict.txt
Expand Up @@ -36,9 +36,8 @@
219: error: Property "y" defined in "FrozenModel" is read-only [misc]
240: error: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment]
241: error: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment]
244: error: Incompatible types in assignment (expression has type "Set[_T]", variable has type "str") [assignment]
244: error: Incompatible types in assignment (expression has type "Set[Any]", variable has type "str") [assignment]
245: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment]
246: error: Incompatible types in assignment (expression has type "List[_T]", variable has type "List[int]") [assignment]
247: error: Argument "default_factory" to "Field" has incompatible type "int"; expected "Optional[Callable[[], Any]]" [arg-type]
250: error: Field default and default_factory cannot be specified together [pydantic-field]
260: error: Missing positional argument "self" in call to "instance_method" of "ModelWithAnnotatedValidator" [call-arg]
3 changes: 1 addition & 2 deletions tests/mypy/outputs/plugin-fail.txt
Expand Up @@ -25,9 +25,8 @@
219: error: Property "y" defined in "FrozenModel" is read-only [misc]
240: error: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment]
241: error: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment]
244: error: Incompatible types in assignment (expression has type "Set[_T]", variable has type "str") [assignment]
244: error: Incompatible types in assignment (expression has type "Set[Any]", variable has type "str") [assignment]
245: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment]
246: error: Incompatible types in assignment (expression has type "List[_T]", variable has type "List[int]") [assignment]
247: error: Argument "default_factory" to "Field" has incompatible type "int"; expected "Optional[Callable[[], Any]]" [arg-type]
250: error: Field default and default_factory cannot be specified together [pydantic-field]
260: error: Missing positional argument "self" in call to "instance_method" of "ModelWithAnnotatedValidator" [call-arg]
2 changes: 2 additions & 0 deletions tests/mypy/outputs/plugin_default_factory.txt
@@ -0,0 +1,2 @@
20: error: Incompatible types in assignment (expression has type "List[int]", variable has type "List[str]") [assignment]
21: error: Incompatible types in assignment (expression has type "List[Any]", variable has type "int") [assignment]
1 change: 1 addition & 0 deletions tests/mypy/test_mypy.py
Expand Up @@ -49,6 +49,7 @@
('pyproject-plugin-strict.toml', 'plugin_fail.py', 'plugin-fail-strict.txt'),
('pyproject-plugin-strict.toml', 'fail_defaults.py', 'fail_defaults.txt'),
('mypy-plugin-strict.ini', 'settings_config.py', None),
('mypy-plugin-strict.ini', 'plugin_default_factory.py', 'plugin_default_factory.txt'),
]
executable_modules = list({fname[:-3] for _, fname, out_fname in cases if out_fname is None})

Expand Down

0 comments on commit 02cf7f5

Please sign in to comment.