lebrice / SimpleParsing

Simple, Elegant, Typed Argument Parsing with argparse
MIT License
399 stars 49 forks source link

Nested subgroups don't appear to work correctly #160

Closed lebrice closed 1 year ago

lebrice commented 2 years ago
def test_nested_subgroups():
    @dataclass
    class FooConfig:
        ...

    @dataclass
    class FooAConfig(FooConfig):
        foo_param_a: float = 0.0

    @dataclass
    class FooBConfig(FooConfig):
        foo_param_b: str = "foo_b"

    @dataclass
    class BarConfig:
        ...

    @dataclass
    class Bar1Config(BarConfig):
        bar_config_1: FooConfig = subgroups(
            {"foo_a": FooAConfig, "foo_b": FooBConfig},
            default=FooAConfig(),
        )

    @dataclass
    class Bar2Config(BarConfig):
        bar_config_2: FooConfig = subgroups(
            {"foo_a": FooAConfig, "foo_b": FooBConfig},
            default=FooBConfig(),
        )

    @dataclass
    class Config(TestSetup):
        bar_config: BarConfig = subgroups(
            {"bar_1": Bar1Config, "bar_2": Bar2Config},
            default=Bar2Config(),
        )

    assert Config.setup("") == Config(bar_config=Bar2Config(bar_config_2=FooBConfig()))

gives:

>       assert Config.setup("") == Config(bar_config=Bar2Config(bar_config_2=FooBConfig()))

test/test_subgroups.py:319: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
test/testutils.py:174: in setup
    args = parser.parse_args(splits)
../../.conda/envs/simple/lib/python3.9/argparse.py:1826: in parse_args
    args, argv = self.parse_known_args(args, namespace)
simple_parsing/parsing.py:393: in parse_known_args
    parsed_args, unparsed_args = parser.parse_known_args(args, namespace=parsed_args)
simple_parsing/parsing.py:393: in parse_known_args
    parsed_args, unparsed_args = parser.parse_known_args(args, namespace=parsed_args)
simple_parsing/parsing.py:385: in parse_known_args
    parser.add_arguments(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = ArgumentParser(prog='pytest', usage=None, description=None, formatter_class=<class 'simple_parsing.help_formatter.SimpleHelpFormatter'>, conflict_handler='error', add_help=True)
dataclass = <class 'test.test_subgroups.test_nested_subgroups.<locals>.Bar2Config'>, dest = 'config.bar_config'

    def add_arguments(
        self,
        dataclass: type[Dataclass] | Dataclass,
        dest: str,
        *,
        prefix: str = "",
        default: Dataclass = None,
        dataclass_wrapper_class: type[DataclassWrapper] = DataclassWrapper,
    ):
        """Adds command-line arguments for the fields of `dataclass`.

        Parameters
        ----------
        dataclass : Union[Dataclass, Type[Dataclass]]
            The dataclass whose fields are to be parsed from the command-line.
            If an instance of a dataclass is given, it is used as the default
            value if none is provided.
        dest : str
            The destination attribute of the `argparse.Namespace` where the
            dataclass instance will be stored after calling `parse_args()`
        prefix : str, optional
            An optional prefix to add prepend to the names of the argparse
            arguments which will be generated for this dataclass.
            This can be useful when registering multiple distinct instances of
            the same dataclass, by default ""
        default : Dataclass, optional
            An instance of the dataclass type to get default values from, by
            default None
        """
        # Save this call so that we can replay it on any child later.
        self._add_argument_replay.append(
            partial(
                type(self).add_arguments,
                dataclass=dataclass,
                dest=dest,
                prefix=prefix,
                default=default,
                dataclass_wrapper_class=dataclass_wrapper_class,
            )
        )
        for wrapper in self._wrappers:
            if wrapper.dest == dest:
                if wrapper.dataclass == dataclass:
>                   raise argparse.ArgumentError(
                        argument=None,
                        message=f"Destination attribute {dest} is already used for "
                        f"dataclass of type {dataclass}. Make sure all destinations"
                        f" are unique. (new dataclass type: {dataclass})",
                    )
E                   argparse.ArgumentError: Destination attribute config.bar_config is already used for dataclass of type <class 'test.test_subgroups.test_nested_subgroups.<locals>.Bar2Config'>. Make sure all destinations are unique. (new dataclass type: <class 'test.test_subgroups.test_nested_subgroups.<locals>.Bar2Config'>)

simple_parsing/parsing.py:244: ArgumentError
====================================================================== short test summary info =======================================================================
FAILED test/test_subgroups.py::test_nested_subgroups - argparse.ArgumentError: Destination attribute config.bar_config is already used for dataclass of type <class...