jsonargparse
jsonargparse copied to clipboard
Support dataclass inheritance
🚀 Feature request
The documentation describes how dataclass-like classes are "not expected to have subclasses", and therefore do not accept specifying a class_path. However dataclasses.dataclass does support inheritance: python docs
Would it be feasible to add support for this? I can't say whether this makes sense for all dataclass-like objects, or just the plain python ones.
My use-case for dataclass+inheritance is pretty basic, my main reason for using dataclasses is reducing boilerplate of writing __init__/__str__/etc functions.
To reproduce
A working example with plain classes
import dataclasses
import jsonargparse
class Base:
def __init__(self, name: str) -> None:
self.name = name
class Derived(Base):
def __init__(self) -> None:
super().__init__(name="derived plain")
parser = jsonargparse.ArgumentParser()
parser.add_subclass_arguments(Base, "thing")
print(parser.parse_args(["--thing.class_path=Derived"]))
A failing example with dataclasses
import dataclasses
import jsonargparse
@dataclasses.dataclass
class DataclassBase:
name: str
@dataclasses.dataclass
class DataclassDerived(DataclassBase):
def __init__(self) -> None:
super().__init__(name="derived dataclass")
# Does not work, jsonargparse assumes no inheritance for dataclass-like types.
parser = jsonargparse.ArgumentParser()
parser.add_subclass_arguments(DataclassBase, "thing")
# ^ this raises: "ValueError: Not allowed for dataclass-like classes."
print(parser.parse_args(["--thing.class_path=DataclassDerived"]))
A failing example with CLI + config
# Config
thing:
class_path: Derived
# ... snip ...: same definition of DataclassBase and DataclassDerived.
def main(thing: DataclassBase) -> None:
pass
jsonargparse.CLI(main)
# Passing the config above to this CLI produces:
# usage: parser.py [-h] [--config CONFIG] [--print_config[=flags]] [--thing CONFIG] --thing.name NAME
# parser.py: error: Configuration check failed :: Key "thing.name" is required but not included in config object or its value is None.
Expected behavior
The failing dataclass snippet should behave exactly like the working plain-class one.
Environment
- jsonargparse version: 4.21.1
- Python version: 3.8
- How jsonargparse was installed: installed via Pants/PEX, using the requirement line
jsonargparse[signatures]==4.21.1. - OS: Linux
@gautiervarjo thank you for the proposal!
I do have in my list of things to improve, a way for the developer to specify which classes to consider or not as subclasses or as dataclass-like. Though, I haven't decided yet how this will be.
A couple of minor notes until this kind of support is implemented.
-
A use case could be to accept only a limited number of dataclasses, instead of full subclass support. In this case it can be done as:
parser.add_argument("--thing", type=Union[DataclassBase, DataclassDerived])or with
CLIas:def main(thing: Union[DataclassBase, DataclassDerived]) -> None:
-
A class that is a mixture of a dataclass and a normal class is not considered dataclass-like. Thus, the subclass behavior can be enabled by doing:
class NormalClassBase: pass @dataclasses.dataclass class DataclassBase(NormalClassBase): name: str
Thank you for the tips! These seem very low-effort to implement so they're definitely good enough for my use-case :slightly_smiling_face:
@mauvilsa Thank you for your workarounds! I would like to share one more user case: using Equinox in JAX, since every equinox Module is a dataclass.
This feature request also applies to pydantic classes as mentioned in #503.
@mauvilsa Are there going to be any updates in the near future? I'm particularly interested in supporting pydantic.BaseModel, as above workarounds do not work for it
@rusmux thanks for letting me know about your interest. I do consider interest to decide on which new features to work on. Though I can't really say if soon I would work on this one since it is not simple.