jsonargparse icon indicating copy to clipboard operation
jsonargparse copied to clipboard

Support dataclass inheritance

Open gautiervarjo opened this issue 2 years ago • 9 comments
trafficstars

🚀 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 avatar May 11 '23 13:05 gautiervarjo

@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.

  1. 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 CLI as:

    def main(thing: Union[DataclassBase, DataclassDerived]) -> None:
    
  1. 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
    

mauvilsa avatar May 13 '23 16:05 mauvilsa

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:

gautiervarjo avatar May 15 '23 06:05 gautiervarjo

@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.

CM-BF avatar Jan 29 '24 21:01 CM-BF

This feature request also applies to pydantic classes as mentioned in #503.

mauvilsa avatar Jun 09 '24 08:06 mauvilsa

@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 avatar Jun 10 '24 17:06 rusmux

@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.

mauvilsa avatar Jun 11 '24 05:06 mauvilsa