diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e659d84c..9aedd8e0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,15 @@ The semantic versioning only considers the public API as described in paths are considered internals and can change in minor and patch releases. +v4.26.2 (2023-10-??) +-------------------- + +Fixed +^^^^^ +- Failure to parse subclass added via add_argument and required arg as link + target. + + v4.26.1 (2023-10-23) -------------------- diff --git a/jsonargparse/_link_arguments.py b/jsonargparse/_link_arguments.py index f5f0af2b..831291be 100644 --- a/jsonargparse/_link_arguments.py +++ b/jsonargparse/_link_arguments.py @@ -177,7 +177,7 @@ def __init__( if target in parser.required_args: parser.required_args.remove(target) if is_target_subclass and not valid_target_leaf: - sub_add_kwargs = getattr(self.target[1], "sub_add_kwargs", {}) + sub_add_kwargs = self.target[1].sub_add_kwargs # type: ignore if "linked_targets" not in sub_add_kwargs: sub_add_kwargs["linked_targets"] = set() subtarget = target.split(".init_args.", 1)[1] diff --git a/jsonargparse/_typehints.py b/jsonargparse/_typehints.py index 459a1061..b6e31ff3 100644 --- a/jsonargparse/_typehints.py +++ b/jsonargparse/_typehints.py @@ -184,6 +184,7 @@ def __init__(self, typehint: Optional[Type] = None, enable_path: bool = False, * else: self._typehint = kwargs.pop("_typehint") self._enable_path = kwargs.pop("_enable_path") + self.sub_add_kwargs: dict = {} if "metavar" not in kwargs: kwargs["metavar"] = typehint_metavar(self._typehint) super().__init__(**kwargs) diff --git a/jsonargparse_tests/test_link_arguments.py b/jsonargparse_tests/test_link_arguments.py index 982a1f17..6096745c 100644 --- a/jsonargparse_tests/test_link_arguments.py +++ b/jsonargparse_tests/test_link_arguments.py @@ -637,6 +637,38 @@ def test_on_instantiate_subclass_link_ignored_missing_param(parser, caplog): assert "'v.init_args.v1 --> w.init_args.w2' ignored since target" in caplog.text +class SubRequired: + def __init__(self): + pass + + +class RequiredTarget: + def __init__( + self, + a: int, + b: SubRequired, + ): + self.a = a + self.b = b + + +class RequiredSource: + def __init__(self, a: int): + self.a = a + + +def test_on_instantiate_add_argument_subclass_required_params(parser): + parser.add_argument("--cls1", type=RequiredSource) + parser.add_argument("--cls2", type=RequiredTarget) + parser.link_arguments("cls1.a", "cls2.init_args.a", apply_on="instantiate") + cfg = parser.parse_args(["--cls1=RequiredSource", "--cls1.a=1", "--cls2=RequiredTarget", "--cls2.b=SubRequired"]) + init = parser.instantiate_classes(cfg) + assert isinstance(init.cls1, RequiredSource) + assert isinstance(init.cls2, RequiredTarget) + assert isinstance(init.cls2.b, SubRequired) + assert init.cls2.a == 1 + + # link creation failures