diff --git a/dcargs/_calling.py b/dcargs/_calling.py index d81f8e0f..49e96741 100644 --- a/dcargs/_calling.py +++ b/dcargs/_calling.py @@ -115,15 +115,16 @@ def get_value_from_arg(prefixed_field_name: str) -> Any: else: # Unions over dataclasses (subparsers). This is the only other option. assert len(parser_definition.subparsers_from_name) > 0 - assert field.name in parser_definition.subparsers_from_name + assert prefixed_field_name in parser_definition.subparsers_from_name - subparser_def = parser_definition.subparsers_from_name[field.name] + subparser_def = parser_definition.subparsers_from_name[prefixed_field_name] subparser_dest = _strings.make_subparser_dest(name=prefixed_field_name) consumed_keywords.add(subparser_dest) if subparser_dest in value_from_prefixed_field_name: subparser_name = get_value_from_arg(subparser_dest) else: + assert subparser_def.default_instance not in _fields.MISSING_SINGLETONS default_instance = subparser_def.default_instance # assert default_instance is not None subparser_name = None diff --git a/dcargs/_parsers.py b/dcargs/_parsers.py index 014e1d33..5f430595 100644 --- a/dcargs/_parsers.py +++ b/dcargs/_parsers.py @@ -103,15 +103,13 @@ def from_callable( not avoid_subparsers # Required subparsers => must create a subparser. or subparsers_attempt.required - # If the output can be None, the only way to specify whether this is - # or isn't None is with a subparser. - or subparsers_attempt.can_be_none ): - subparsers_from_name[subparsers_attempt.name] = subparsers_attempt + subparsers_from_name[ + _strings.make_field_name([prefix, subparsers_attempt.name]) + ] = subparsers_attempt continue else: field = dataclasses.replace(field, typ=type(field.default)) - assert _fields.is_nested_type(field.typ, field.default) # (2) Handle nested callables. if _fields.is_nested_type(field.typ, field.default): @@ -129,6 +127,10 @@ def from_callable( ) args.extend(nested_parser.args) + # Include nested subparsers. + subparsers_from_name.update(nested_parser.subparsers_from_name) + + # Include nested strings. for k, v in nested_parser.helptext_from_nested_class_field_name.items(): helptext_from_nested_class_field_name[ _strings.make_field_name([field.name, k]) @@ -355,12 +357,7 @@ def apply( for p in prev_subparser_tree_nodes: # Add subparsers to every node in previous level of the tree. argparse_subparsers = p.add_subparsers( - dest=_strings.make_field_name( - [ - parent_parser.prefix, - _strings.make_subparser_dest(name=self.name), - ] - ), + dest=_strings.make_subparser_dest(self.prefix), description=self.description, required=self.required, title=termcolor.colored(title, attrs=["bold"]), diff --git a/pyproject.toml b/pyproject.toml index 570b9198..dbb57d57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "dcargs" -version = "0.2.1" +version = "0.2.2" description = "Strongly typed, zero-effort CLI interfaces" authors = ["brentyi "] include = ["./dcargs/**/*"] diff --git a/tests/test_nested.py b/tests/test_nested.py index 08d8265b..6663de44 100644 --- a/tests/test_nested.py +++ b/tests/test_nested.py @@ -697,3 +697,34 @@ class ChildClass(UnrelatedParentClass, ActualParentClass[int]): assert dcargs.cli( ChildClass, args=["--x", "1", "--y", "2", "--z", "3"] ) == ChildClass(x=1, y=2, z=3) + + +def test_subparser_in_nested(): + @dataclasses.dataclass + class A: + a: int + + @dataclasses.dataclass + class B: + b: int + + @dataclasses.dataclass + class Nested2: + subcommand: Union[A, B] + + @dataclasses.dataclass + class Nested1: + nested2: Nested2 + + @dataclasses.dataclass + class Parent: + nested1: Nested1 + + assert dcargs.cli( + Parent, + args="nested1.nested2.subcommand:a --nested1.nested2.subcommand.a 3".split(" "), + ) == Parent(Nested1(Nested2(A(3)))) + assert dcargs.cli( + Parent, + args="nested1.nested2.subcommand:b --nested1.nested2.subcommand.b 7".split(" "), + ) == Parent(Nested1(Nested2(B(7))))