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


Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/omni-us/jsonargparse/pull/707?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 100.00%. Comparing base [(`a6d283d`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/a6d283dee67eaa13a65c111a64aa48c8cface7d4?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) to head [(`358d0c7`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/358d0c786d199bc7009e122a98182089f2af4809?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).
> Report is 1 commits behind head on main.

:white_check_mark: All tests successful. No failed tests found.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff            @@
##              main      #707   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           22        22           
  Lines         6768      6771    +3     
=========================================
+ Hits          6768      6771    +3     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/omni-us/jsonargparse/pull/707?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).


Fix (patch):
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index feef512..222ae42 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 62ba176..f4ba852 100644
--- a/jsonargparse/_actions.py
+++ b/jsonargparse/_actions.py
@@ -578,6 +578,9 @@ class ActionParser:
         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 changes:
diff --git a/jsonargparse_tests/test_signatures.py b/jsonargparse_tests/test_signatures.py
index a45f22d..4930dbd 100644
--- a/jsonargparse_tests/test_signatures.py
+++ b/jsonargparse_tests/test_signatures.py
@@ -8,6 +8,7 @@ from unittest.mock import patch
 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