I might be doing some shenanigans here, but I’m trying to write a decorator that 1. adds a base class (mixin-style) and 2. makes it a dataclass.
In this example, I’m using Foo.__call__ as the decorator which adds MyBase.
from dataclasses import asdict, dataclass
from typing import dataclass_transform, TYPE_CHECKING
if TYPE_CHECKING:
from _typeshed import DataclassInstance
else:
DataclassInstance = object
class MyBase(DataclassInstance):
@classmethod
def lorem(cls, *args, **kwargs):
return cls(*args, **kwargs)
def ipsum(self):
return asdict(self)
class Foo:
@dataclass_transform()
def __call__(self, cls, **kwargs): # -> ???
def wrap(cls):
if MyBase not in cls.__bases__:
cls = type(cls.__name__, (MyBase,) + cls.__bases__, dict(cls.__dict__))
return dataclass(cls, **kwargs)
if cls is None:
return wrap # @foo(...)
return wrap(cls) # @foo
foo = Foo()
@foo
class Struct:
bar: str
baz: int
s = Struct(bar="yo", baz="something")
print(s)
s = Struct("yo", "something")
print(s)
s = Struct.lorem(bar="yo", baz="something")
print(s)
print(s.ipsum())
this runs fine:
Struct(bar='yo', baz='something')
Struct(bar='yo', baz='something')
Struct(bar='yo', baz='something')
{'bar': 'yo', 'baz': 'something'}
but type checkers can’t figure out that Struct has MyBase as a parent and is a dataclass:
$ mypy test.py
test.py:38: error: Unexpected keyword argument "bar" for "Struct" [call-arg]
/usr/lib/python3.14/site-packages/mypy/typeshed/stdlib/builtins.pyi:101: note: "Struct" defined here
test.py:38: error: Unexpected keyword argument "baz" for "Struct" [call-arg]
/usr/lib/python3.14/site-packages/mypy/typeshed/stdlib/builtins.pyi:101: note: "Struct" defined here
test.py:42: error: Too many arguments for "Struct" [call-arg]
test.py:46: error: "type[Struct]" has no attribute "lorem" [attr-defined]
test.py:50: error: "Struct" has no attribute "ipsum" [attr-defined]
Found 5 errors in 1 file (checked 1 source file)
$ ty check test.py
error[unknown-argument]: Argument `bar` does not match any known parameter of bound method `__init__`
--> test.py:38:12
|
36 | # No parameter named "bar"
37 | # No parameter named "baz"
38 | s = Struct(bar="yo", baz="something")
| ^^^^^^^^
39 | print(s)
|
info: Method signature here
--> stdlib/builtins.pyi:136:9
|
134 | @__class__.setter
135 | def __class__(self, type: type[Self], /) -> None: ...
136 | def __init__(self) -> None: ...
| ^^^^^^^^^^^^^^^^^^^^^^
137 | def __new__(cls) -> Self: ...
138 | # N.B. `object.__setattr__` and `object.__delattr__` are heavily special-cased by type checkers.
|
info: rule `unknown-argument` is enabled by default
error[unknown-argument]: Argument `baz` does not match any known parameter of bound method `__init__`
--> test.py:38:22
|
36 | # No parameter named "bar"
37 | # No parameter named "baz"
38 | s = Struct(bar="yo", baz="something")
| ^^^^^^^^^^^^^^^
39 | print(s)
|
info: Method signature here
--> stdlib/builtins.pyi:136:9
|
134 | @__class__.setter
135 | def __class__(self, type: type[Self], /) -> None: ...
136 | def __init__(self) -> None: ...
| ^^^^^^^^^^^^^^^^^^^^^^
137 | def __new__(cls) -> Self: ...
138 | # N.B. `object.__setattr__` and `object.__delattr__` are heavily special-cased by type checkers.
|
info: rule `unknown-argument` is enabled by default
error[too-many-positional-arguments]: Too many positional arguments to bound method `__init__`: expected 1, got 3
--> test.py:42:12
|
41 | # Expected 0 positional arguments
42 | s = Struct("yo", "something")
| ^^^^
43 | print(s)
|
info: Method signature here
--> stdlib/builtins.pyi:136:9
|
134 | @__class__.setter
135 | def __class__(self, type: type[Self], /) -> None: ...
136 | def __init__(self) -> None: ...
| ^^^^^^^^^^^^^^^^^^^^^^
137 | def __new__(cls) -> Self: ...
138 | # N.B. `object.__setattr__` and `object.__delattr__` are heavily special-cased by type checkers.
|
info: rule `too-many-positional-arguments` is enabled by default
error[unresolved-attribute]: Class `Struct` has no attribute `lorem`
--> test.py:46:5
|
45 | # Cannot access attribute "lorem" for class "type[Struct]"
46 | s = Struct.lorem(bar="yo", baz="something")
| ^^^^^^^^^^^^
47 | print(s)
|
info: rule `unresolved-attribute` is enabled by default
Found 4 diagnostics
What can I do to help type checkers understand this?