Repository: omni-us/jsonargparse
Pull request #611 (2024-10-31 06:17:02)

Fix failures when resolving forward references from dataclass parameter types
## What does this PR do?

#609

## 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)


sonarcloud[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=611) **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=611&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=611&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=611&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=611&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=611&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=611)


sonarcloud[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=611) **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=611&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=611&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=611&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=611&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=611&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=611)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/omni-us/jsonargparse/pull/611?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 [(`4050cfd`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/4050cfd1f74c06b65286cd3e936ab0115cfcb5e5?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) to head [(`25a8ccc`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/25a8ccce82c84338b0b39bac479588a1f789c56e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).

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


```diff
@@            Coverage Diff            @@
##              main      #611   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           22        22           
  Lines         6484      6491    +7     
=========================================
+ Hits          6484      6491    +7     
```

</details>

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


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 489125e..84bb8c6 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -31,6 +31,8 @@ Fixed
   <https://github.com/omni-us/jsonargparse/pull/603>`__).
 - Custom instantiators not working for nested dependency injection (`#608
   <https://github.com/omni-us/jsonargparse/pull/608>`__).
+- Failure when resolving forward references from dataclass parameter types
+  (`#611 <https://github.com/omni-us/jsonargparse/pull/611>`__).
 
 Changed
 ^^^^^^^
diff --git a/jsonargparse/_postponed_annotations.py b/jsonargparse/_postponed_annotations.py
index 5dbdffd..48136e5 100644
--- a/jsonargparse/_postponed_annotations.py
+++ b/jsonargparse/_postponed_annotations.py
@@ -259,8 +259,20 @@ def type_requires_eval(typehint):
     return isinstance(typehint, (str, ForwardRef))
 
 
-def get_types(obj: Any, logger: Optional[logging.Logger] = None) -> dict:
+def get_global_vars(obj: Any, logger: Optional[logging.Logger]) -> dict:
     global_vars = vars(import_module(obj.__module__))
+    try:
+        module_source = inspect.getsource(sys.modules[obj.__module__]) if obj.__module__ in sys.modules else ""
+        if "TYPE_CHECKING" in module_source:
+            TypeCheckingVisitor().update_aliases(module_source, obj.__module__, global_vars, logger)
+    except Exception as ex:
+        if logger:
+            logger.debug(f"Failed to update aliases for TYPE_CHECKING blocks in {obj.__module__}", exc_info=ex)
+    return global_vars
+
+
+def get_types(obj: Any, logger: Optional[logging.Logger] = None) -> dict:
+    global_vars = get_global_vars(obj, logger)
     try:
         types = get_type_hints(obj, global_vars)
     except Exception as ex1:
@@ -288,10 +300,6 @@ def get_types(obj: Any, logger: Optional[logging.Logger] = None) -> dict:
         ex = types
         types = {}
 
-    module_source = inspect.getsource(sys.modules[obj.__module__]) if obj.__module__ in sys.modules else ""
-    if "TYPE_CHECKING" in module_source:
-        TypeCheckingVisitor().update_aliases(module_source, obj.__module__, aliases, logger)
-
     if isinstance(node, ast.FunctionDef):
         arg_asts = [(a.arg, a.annotation) for a in node.args.args + node.args.kwonlyargs]
     else:


Test patch:
diff --git a/jsonargparse_tests/test_postponed_annotations.py b/jsonargparse_tests/test_postponed_annotations.py
index 05d7d90..afbdccb 100644
--- a/jsonargparse_tests/test_postponed_annotations.py
+++ b/jsonargparse_tests/test_postponed_annotations.py
@@ -256,7 +256,7 @@ def function_type_checking_list(p1: List[Union["TypeCheckingClass1", TypeCheckin
 def test_get_types_type_checking_list():
     types = get_types(function_type_checking_list)
     assert list(types.keys()) == ["p1"]
-    lst = "typing.List" if sys.version_info < (3, 10) else "list"
+    lst = "typing.List"
     assert str(types["p1"]) == f"{lst}[typing.Union[{__name__}.TypeCheckingClass1, {__name__}.TypeCheckingClass2]]"
 
 
@@ -267,7 +267,7 @@ def function_type_checking_tuple(p1: Tuple[TypeCheckingClass1, "TypeCheckingClas
 def test_get_types_type_checking_tuple():
     types = get_types(function_type_checking_tuple)
     assert list(types.keys()) == ["p1"]
-    tpl = "typing.Tuple" if sys.version_info < (3, 10) else "tuple"
+    tpl = "typing.Tuple"
     assert str(types["p1"]) == f"{tpl}[{__name__}.TypeCheckingClass1, {__name__}.TypeCheckingClass2]"
 
 
@@ -278,7 +278,7 @@ def function_type_checking_type(p1: Type["TypeCheckingClass2"]):
 def test_get_types_type_checking_type():
     types = get_types(function_type_checking_type)
     assert list(types.keys()) == ["p1"]
-    tpl = "typing.Type" if sys.version_info < (3, 10) else "type"
+    tpl = "typing.Type"
     assert str(types["p1"]) == f"{tpl}[{__name__}.TypeCheckingClass2]"
 
 
@@ -289,7 +289,7 @@ def function_type_checking_dict(p1: Dict[str, Union[TypeCheckingClass1, "TypeChe
 def test_get_types_type_checking_dict():
     types = get_types(function_type_checking_dict)
     assert list(types.keys()) == ["p1"]
-    dct = "typing.Dict" if sys.version_info < (3, 10) else "dict"
+    dct = "typing.Dict"
     assert str(types["p1"]) == f"{dct}[str, typing.Union[{__name__}.TypeCheckingClass1, {__name__}.TypeCheckingClass2]]"
 
 
@@ -305,6 +305,18 @@ def test_get_types_type_checking_undefined_forward_ref(logger):
     assert "NameError: Name 'Undefined' is not defined" in logs.getvalue()
 
 
+@dataclasses.dataclass
+class DataclassForwardRef:
+    p1: "int"
+    p2: Optional["xml.dom.Node"] = None
+
+
+@pytest.mark.skipif(sys.version_info < (3, 9), reason="not working in python 3.8")
+def test_get_types_type_checking_dataclass_init_forward_ref():
+    types = get_types(DataclassForwardRef.__init__)
+    assert types == {"p1": int, "p2": Optional[xml.dom.Node], "return": type(None)}
+
+
 def function_source_unavailable(p1: List["TypeCheckingClass1"]):
     return p1