msgspec
msgspec copied to clipboard
Subclasses of frozen Structs causing mypy error: `Cannot inherit non-frozen dataclass from a frozen one`
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=Trueto the definition ofChildas well (this is what I recommend) - Add
type: ignoreon 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.