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
MVCE repository: https://github.com/jwodder/mypy-bug-20220227 (Run
tox -e typingto see the failed type-check)Consider an
Appclass with a method that createsWidgetinstances based on a spec.Widgetis a base class, and the spec determines which child class gets instantiated.Now, the widgets keep a reference to the
App, so in an attempt to avoid circular imports, the file containing theWidgetdefinition uses anif TYPE_CHECKING:guard like so:The
RedWidgetchild class is empty, but theBlueWidgetclass adds an attribute:Now, if we put this all together and run mypy, we get an erroneous error:
Note that mypy only complains about the instantiation of
BlueWidget, notRedWidget. 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 theWidget.appannotation toAnygets 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):Python version used: 3.9.10
Operating system and version: macOS 11.6