Description
MVCE repository: https://github.com/jwodder/mypy-bug-20220227 (Run tox -e typing
to see the failed type-check)
Consider an App
class with a method that creates Widget
instances based on a spec. Widget
is a base class, and the spec determines which child class gets instantiated.
# src/foobar/app.py
from .widgets import BlueWidget, RedWidget, Widget, WidgetSpec
class App:
def make_widget(self, spec: WidgetSpec) -> Widget:
if spec.color == "red":
return RedWidget(app=self, spec=spec)
elif spec.color == "blue":
return BlueWidget(app=self, spec=spec)
else:
raise ValueError(f"Unsupported widget color: {spec.color!r}")
Now, the widgets keep a reference to the App
, so in an attempt to avoid circular imports, the file containing the Widget
definition uses an if TYPE_CHECKING:
guard like so:
# src/foobar/widgets/base.py
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
import attr
if TYPE_CHECKING:
from ..app import App
@attr.define
class WidgetSpec:
color: str
flavor: str
nessness: Optional[int]
@attr.define
class Widget:
app: App
spec: WidgetSpec
The RedWidget
child class is empty, but the BlueWidget
class adds an attribute:
# src/foobar/widgets/blue.py
import attr
from .base import Widget
@attr.define
class BlueWidget(Widget):
nessness: int = attr.field(init=False)
def __attrs_post_init__(self) -> None:
if self.spec.nessness is None:
raise ValueError("Blue widgets must be nessy")
self.nessness = self.spec.nessness
Now, if we put this all together and run mypy, we get an erroneous error:
src/foobar/app.py:9: error: Unexpected keyword argument "app" for "BlueWidget" [call-arg]
return BlueWidget(app=self, spec=spec)
^
src/foobar/app.py:9: error: Unexpected keyword argument "spec" for "BlueWidget" [call-arg]
return BlueWidget(app=self, spec=spec)
^
Found 2 errors in 1 file (checked 6 source files)
Note that mypy only complains about the instantiation of BlueWidget
, not RedWidget
. Also note that the same error occurs if attrs is replaced with dataclasses.
I believe that this problem is caused by the circular import beneath the if TYPE_CHECKING:
guard for some reason, as commenting it out and changing the Widget.app
annotation to Any
gets the type-checking to pass.
Your Environment
-
Mypy version used: both 0.931 and commit feca706
-
Mypy command-line flags: none
-
Mypy configuration options from
mypy.ini
(and other config files):[mypy] pretty = True show_error_codes = True
-
Python version used: 3.9.10
-
Operating system and version: macOS 11.6