Repository: omni-us/jsonargparse
Pull request #709 (2025-04-21 06:39:13)

Fixed nested generic dataclasses not working correctly
## What does this PR do?

Fixes https://github.com/omni-us/jsonargparse/issues/366#issuecomment-2816151415

## Before submitting

- [x] Did you read the [contributing guideline](https://github.com/omni-us/jsonargparse/blob/main/CONTRIBUTING.rst)?
- [n/a] Did you update **the documentation**? (readme and public docstrings)
- [x] Did you write **unit tests** such that there is 100% coverage on related code? (required for bug fixes and new features)
- [x] Did you verify that new and existing **tests pass locally**?
- [x] Did you make sure that all changes preserve **backward compatibility**?
- [x] Did you update **the CHANGELOG**? (not for typos, docs, test updates, or minor internal changes/refactors)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/omni-us/jsonargparse/pull/709?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 [(`7637f7f`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/7637f7f59ecf6640a0f0c91b7eef47b639e159ee?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) to head [(`9cc707f`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/9cc707facd5b3bb428585f8d2b7b4da89e3841a1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).

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

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


```diff
@@            Coverage Diff            @@
##              main      #709   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           22        22           
  Lines         6792      6796    +4     
=========================================
+ Hits          6792      6796    +4     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/omni-us/jsonargparse/pull/709?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).

sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=709) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=709&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=709&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=709&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=709&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=709&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=709)



Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index d365fd0..ba75c85 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -36,6 +36,8 @@ Fixed
 ^^^^^
 - ``ActionParser`` not updating ``dest`` of groups for instantiation (`#707
   <https://github.com/omni-us/jsonargparse/pull/707>`__).
+- Nested generic dataclasses not working correctly (`#709
+  <https://github.com/omni-us/jsonargparse/pull/709>`__).
 
 
 v4.38.0 (2025-03-26)
diff --git a/jsonargparse/_signatures.py b/jsonargparse/_signatures.py
index 14b6359..7c6586b 100644
--- a/jsonargparse/_signatures.py
+++ b/jsonargparse/_signatures.py
@@ -537,7 +537,7 @@ class SignatureArguments(LoggerProperty):
                     doc_group = str(obj[0])
                 else:
                     doc_group = str(obj)
-            name = obj.__name__ if nested_key is None else nested_key
+            name = get_object_name(obj) if nested_key is None else nested_key
             group = self.add_argument_group(strip_title(doc_group), name=name)
             if config_load and nested_key is not None:
                 group.add_argument("--" + nested_key, action=_ActionConfigLoad(basetype=config_load_type))
@@ -548,6 +548,12 @@ class SignatureArguments(LoggerProperty):
         return group
 
 
+def get_object_name(obj) -> str:
+    if hasattr(obj, "__name__"):
+        return obj.__name__
+    return str(obj).split(".")[-1].replace("[", "_").replace("]", "")
+
+
 def group_instantiate_class(group, cfg):
     try:
         value, parent, key = cfg.get_value_and_parent(group.dest)
diff --git a/jsonargparse/_typehints.py b/jsonargparse/_typehints.py
index 8ea4fc1..015853c 100644
--- a/jsonargparse/_typehints.py
+++ b/jsonargparse/_typehints.py
@@ -644,7 +644,7 @@ class ActionTypeHint(Action):
         parser = parent_parser.get()
         parser = type(parser)(exit_on_error=False, logger=parser.logger, parser_mode=parser.parser_mode)
         remove_actions(parser, (ActionConfigFile, _ActionPrintConfig))
-        if inspect.isclass(val_class):
+        if inspect.isclass(val_class) or inspect.isclass(get_typehint_origin(val_class)):
             parser.add_class_arguments(val_class, **kwargs)
         else:
             kwargs = {k: v for k, v in kwargs.items() if k != "instantiate"}


Test patch:
diff --git a/jsonargparse_tests/test_dataclass_like.py b/jsonargparse_tests/test_dataclass_like.py
index 8d794a3..7395fd8 100644
--- a/jsonargparse_tests/test_dataclass_like.py
+++ b/jsonargparse_tests/test_dataclass_like.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import dataclasses
 import json
+import sys
 from typing import Any, Dict, Generic, List, Literal, Optional, Tuple, TypeVar, Union
 from unittest.mock import patch
 
@@ -539,6 +540,31 @@ def test_nested_generic_dataclass(parser):
     assert "--x.y.g4 g4          (required, type: dict[str, union[float, bool]])" in help_str
 
 
+if sys.version_info >= (3, 9):
+    V = TypeVar("V")
+
+    @dataclasses.dataclass(frozen=True)
+    class GenericChild(Generic[V]):
+        value: V
+
+    @dataclasses.dataclass(frozen=True)
+    class GenericBase(Generic[V]):
+        children: tuple[GenericChild[V], ...]
+
+    @dataclasses.dataclass(frozen=True)
+    class GenericSubclass(GenericBase[str]):
+        children: tuple[GenericChild[str], ...]
+
+    def test_generic_dataclass_subclass(parser):
+        parser.add_class_arguments(GenericSubclass, "x")
+        cfg = parser.parse_args(['--x.children=[{"value": "a"}, {"value": "b"}]'])
+        init = parser.instantiate_classes(cfg)
+        assert cfg.x.children == (Namespace(value="a"), Namespace(value="b"))
+        assert isinstance(init.x, GenericSubclass)
+        assert isinstance(init.x.children[0], GenericChild)
+        assert isinstance(init.x.children[1], GenericChild)
+
+
 # union mixture tests