Subclasses of frozen Structs causing mypy error: `Cannot inherit non-frozen dataclass from a frozen one`
wikiped opened this issue · comments
Description
With the following code:
import msgspec as ms
class Base(ms.Struct, frozen=True):
...
class Child(Base):
...
assert Child.__struct_config__.frozen is True
In a file: frozen.py
And the following command:
python -m mypy ./frozen.py
Outputs a false positive:
<...>frozen.py:8: error: Cannot inherit non-frozen dataclass from a frozen one [misc]
Found 1 error in 1 file (checked 1 source file)
In a similar mypy issue the cause has been attributed to pydantic's use of dataclass_transform
which might be having similar effects here.
This has been tested with:
mypy: 1.9.0 (compiled: yes)
msgspec: 0.18.6
Yeah, this is a restriction of how dataclass_transform
works (PEP 681). Since we want to support both mypy
and pyright
, I'm unwilling to fix this by writing a custom mypy
extension - we want to follow the upstream python standards alone. You have two options:
- pass
frozen=True
to the definition ofChild
as well (this is what I recommend) - Add
type: ignore
on the same line asclass Child
. This will ignore that error in mypy, but won't catch issues where you try to mutateChild
.
We run into the same option-isn't-inherited issue with kw_only
(which we made non-inherited to match what mypy
/pyright
expect). In that case I'm considering adding a new base class like KWStruct
that defines the default to be kw_only=True
, which would work better with dataclass_transform
for cases where you want all subclasses to be keyword-only.
We could do the same here and define a FrozenStruct
base class that defaults to frozen=True
. If we do this, we might also want to change frozen
to be non-inherited to match the dataclass_transform
implementation.
Thanks for quick feedback.
I have had considered two options you mentioned, but both look like an "inconvience" and therefore was hoping to find "a better way" to address this.
The FrozenStruct
idea (and KWStruct
) sounds like both developer and linter friendly way to solve this.