Repository: omni-us/jsonargparse

Issue:
instantiate_classes does not instantiate my classes with configured args when classes are added to subparser.
<!-- If you like this project, please ⭐ star it https://github.com/omni-us/jsonargparse/ -->

## 🐛 Bug report

Hey, I am trying to add a subparser to my parser, read in a config specific to that parser and subsequently instantiate classes tied to that subparser. However, while my configuration gets read in seemingly properly, instantiate_classes does not use the parameters from my configuration to actually instantiate the classes.


### To reproduce
I have roughly the following code 
```
def parse_opt(imported=False, config_file=""):
    parser = ArgumentParser(description="Pytorch augmentations testing",
                            default_config_files=["../config/test/config_data_proc"],
                            add_help=True)
    parser.add_class_arguments(adapters.Blur, prefix + "pixel.blur")


def main():
    parser = ArgumentParser()
    augment_parser = parse_opt()
    parser.add_argument("--augmentations", action=ActionParser(parser=augment_parser))

    args = parser.parse_args()
    init = parser.instantiate_classes(args)
```

adapters.Blur would for instance be a class with typed __init__ arguments.
```
class Blur(A.Blur):
        def __init__(self, 
                     blur_limit: tuple[int, int] | int = (3, 7), 
                     p:float = 0.0):
            self.blur_limit = blur_limit
            self.p = p
````

Then I have the following yaml to read in:
```
augmentations:
  pixel:
    blur:
      p: 1.0
      blur_limit: [3,7]
```



### Expected behavior

I expect init.augmentations or init.augmentations.pixel to contain a list of instantiated classes according to my configuration. But what I see currently in my debugger is that args.augmentations.spatial.blur would have the correct configuration:  Namespace(p=1.0, blur_limit: [3,7]), as well as init.augmentations.spatial. 

But then init also contains what I presume are the instantiated classes (init.spatial and init.pixel in the full code I have, init.pixel in the example above). But those are instantiated with the default arguments for the classes, not the values from my config file.

I am not sure from the documentation how to do all of this properly, and it feels like I'm kind of stuck at this point. It would already help to know what the expected structure for 'args' and 'init' should look like and how I could best structure my parser and config file to work with this.

### Environment

<!-- Fill in the list below. -->

- jsonargparse version: 4.37.0
- Python version: 3.10.13
- How jsonargparse was installed: pip (in conda virtual enviroment, installed after conda installs)
- OS: ubuntu 22.04

Hints from issue comments:
@JHeuverRiwo: The examples above are snippets from the complete program I have ofcourse, and are not tested, I think it's enough to get my problem across, but if you need more info or a runable example program please let me know asap!@mauvilsa: Thank you for reporting! This does look like a bug. From what I remember there are no unit tests that combine `ActionParser` and parsers from signatures. So could be that this is the first time someone tries to do this. Though I am not sure if this is the case. I will look at it.@JHeuverRiwo: Ah I see! Interesting. 

I kind of missed potential other ways to do this in the documentation, but on further inspection I found that:
a) maybe I don't need an ActionParser, I think I accidentally chose this, instead of the ArgumentParser
b) I can merge namespaces with `ArgumentParser.merge_config()`

merge_config does actually give me the same issue, when I combine the non-instantiated arguments, and then try to instantiate them. Instantiating two parser separately and then merging those namespaces does resolve my issue for now however, so I do have a 'workaround' (or maybe more of a more intended way to solve this problem? Not sure)

@mauvilsa: > a) maybe I don't need an ActionParser, I think I accidentally chose this, instead of the ArgumentParser

Using `ActionParser` could be a good choice. It depends on how the code is structured. But I wouldn't be able to tell only from the code above.

Anyway, this is a bug, and all existing features should work correctly even when combined. So it should be fixed regardless. And it seems to be very simple. I created pull request #707. Please try your `ActionParser` code with that branch to be sure that it actually fixes the problem.

> b) I can merge namespaces with `ArgumentParser.merge_config()`

I don't think `merge_config` is a good choice. I think `merge_config` will only work correctly for two namespaces produced using exactly the same parser.
@mauvilsa: I merged the pull request. Anyway you can test your code from the main branch. If there are any issues please comment.@JHeuverRiwo: Thanks for taking the time to solve it! 

I tried to test the changes, but for some reason my configuration is not loading in at all now (although the instantiation step does seem to work now). I don't really have the time to dig into this further today, but hope to find some in the coming workdays. This might as well just be me doing something wrong atm. I'll keep you posted!

Solution patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index feef512c..222ae42b 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -30,6 +30,11 @@ Changed
   ``fail_untyped=True`` (`#697
   <https://github.com/omni-us/jsonargparse/pull/697>`__).
 
+Fixed
+^^^^^
+- ``ActionParser`` not updating ``dest`` of groups for instantiation (`#707
+  <https://github.com/omni-us/jsonargparse/pull/707>`__).
+
 
 v4.38.0 (2025-03-26)
 --------------------
diff --git a/jsonargparse/_actions.py b/jsonargparse/_actions.py
index 62ba176e..f4ba852d 100644
--- a/jsonargparse/_actions.py
+++ b/jsonargparse/_actions.py
@@ -578,6 +578,9 @@ def add_prefix(key):
         base_action_group._actions = filter_default_actions(base_action_group._actions)
         base_action_group._group_actions = filter_default_actions(base_action_group._group_actions)
         extra_action_groups = subparser._action_groups[2:]
+        for group in extra_action_groups:
+            if group.dest is not None:
+                group.dest = dest + "." + group.dest
 
         parser.add_argument(args[0], action=_ActionConfigLoad)
         parser.required_args.update(required_args)


Test patch:
diff --git a/jsonargparse_tests/test_signatures.py b/jsonargparse_tests/test_signatures.py
index a45f22db..4930dbd8 100644
--- a/jsonargparse_tests/test_signatures.py
+++ b/jsonargparse_tests/test_signatures.py
@@ -8,6 +8,7 @@
 import pytest
 
 from jsonargparse import (
+    ActionParser,
     ArgumentError,
     Namespace,
     lazy_instance,
@@ -434,6 +435,36 @@ def test_add_class_unmatched_default_type(parser):
     assert json_or_yaml_load(dump) == {"cls": {"p1": "x", "p2": "deprecated"}}
 
 
+class LeafClass:
+    def __init__(self, p1: int = 1, p2: str = "-"):
+        self.p1 = p1
+        self.p2 = p2
+
+
+def test_add_class_and_action_parser(parser, subparser):
+    subparser.add_class_arguments(LeafClass, "deep.leaf")
+    parser.add_argument("--nested", action=ActionParser(parser=subparser))
+    parser.add_argument("--config", action="config")
+
+    config = {
+        "nested": {
+            "deep": {
+                "leaf": {
+                    "p1": 2,
+                    "p2": "x",
+                }
+            }
+        }
+    }
+    cfg = parser.parse_args([f"--config={json.dumps(config)}"])
+    init = parser.instantiate_classes(cfg)
+    assert list(cfg.keys()) == ["nested.deep.leaf.p1", "nested.deep.leaf.p2", "config"]
+    assert list(init.keys()) == ["nested.deep.leaf", "config"]
+    assert isinstance(init.nested.deep.leaf, LeafClass)
+    assert init.nested.deep.leaf.p1 == 2
+    assert init.nested.deep.leaf.p2 == "x"
+
+
 # add_method_arguments tests