Repository: omni-us/jsonargparse
Pull request #339 (2023-08-01 04:20:51)

Fix class_from_function failing when called on the same function multiple times 
## What does this PR do?

Fixes https://github.com/Lightning-AI/lightning/issues/18180

## 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]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=339)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=339&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=339&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=339&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=339&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=339&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=339&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=339&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=339&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=339&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=339&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=339&resolved=false&types=CODE_SMELL) [1 Code Smell](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=339&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=339) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=339&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=339&metric=new_duplicated_lines_density&view=list)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/omni-us/jsonargparse/pull/339?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) Report
Patch coverage: **`100.00%`** and project coverage change: **`-0.24%`** :warning:
> Comparison is base [(`adefc21`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/adefc21356472a5109628a00e0647850e54df0ce?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) 100.00% compared to head [(`79b23f3`)](https://app.codecov.io/gh/omni-us/jsonargparse/pull/339?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) 99.76%.

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


```diff
@@             Coverage Diff             @@
##              main     #339      +/-   ##
===========================================
- Coverage   100.00%   99.76%   -0.24%     
===========================================
  Files           21       21              
  Lines         5541     5545       +4     
===========================================
- Hits          5541     5532       -9     
- Misses           0       13      +13     
```

| Flag | Coverage Δ | |
|---|---|---|
| py3.10 | `86.58% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.10_all | `98.34% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.10_types | `98.34% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.11 | `?` | |
| py3.11_all | `?` | |
| py3.11_types | `?` | |
| py3.6 | `?` | |
| py3.6_all | `?` | |
| py3.7 | `?` | |
| py3.7_all | `?` | |
| py3.7_types | `?` | |
| py3.8 | `?` | |
| py3.8_all | `?` | |
| py3.8_types | `?` | |
| py3.9 | `88.07% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.9_all | `99.54% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.9_types | `99.54% <100.00%> (+<0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Files Changed](https://app.codecov.io/gh/omni-us/jsonargparse/pull/339?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | Coverage Δ | |
|---|---|---|
| [jsonargparse/\_util.py](https://app.codecov.io/gh/omni-us/jsonargparse/pull/339?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL191dGlsLnB5) | `99.80% <100.00%> (-0.20%)` | :arrow_down: |

... and [3 files with indirect coverage changes](https://app.codecov.io/gh/omni-us/jsonargparse/pull/339/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/omni-us/jsonargparse/pull/339?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 b297343..1ce9f47 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -21,6 +21,9 @@ Fixed
   (`#332 <https://github.com/omni-us/jsonargparse/issues/332>`__).
 - ``typing.Literal`` types skipped when typing_extensions is installed
   (`lightning#18184 <https://github.com/Lightning-AI/lightning/pull/18184>`__).
+- ``class_from_function`` failing when called on the same function multiple
+  times (`lightning#18180
+  <https://github.com/Lightning-AI/lightning/issues/18180>`__).
 
 
 v4.23.0 (2023-07-27)
diff --git a/jsonargparse/_util.py b/jsonargparse/_util.py
index e8ecacf..0d23ae6 100644
--- a/jsonargparse/_util.py
+++ b/jsonargparse/_util.py
@@ -31,7 +31,7 @@ from typing import (
     get_type_hints,
 )
 
-from ._common import parser_capture, parser_context
+from ._common import is_subclass, parser_capture, parser_context
 from ._deprecated import PathDeprecations
 from ._loaders_dumpers import json_dump, load_value
 from ._optionals import (
@@ -394,6 +394,16 @@ def class_from_function(
     caller_module = inspect.getmodule(inspect.stack()[1][0]) or inspect.getmodule(class_from_function)
     assert caller_module
     if hasattr(caller_module, name):
+        cls = getattr(caller_module, name)
+        mro = inspect.getmro(cls) if inspect.isclass(cls) else ()
+        if (
+            len(mro) > 1
+            and mro[1] is func_return
+            and is_subclass(cls, ClassFromFunctionBase)
+            and cls.wrapped_function is func
+            and cls.__name__ == name
+        ):
+            return cls
         raise ValueError(f"{caller_module.__name__} already defines {name!r}, please use a different name")
 
     @wraps(func)


Test patch:
diff --git a/jsonargparse_tests/test_util.py b/jsonargparse_tests/test_util.py
index 57a1f5d..0598969 100644
--- a/jsonargparse_tests/test_util.py
+++ b/jsonargparse_tests/test_util.py
@@ -589,6 +589,7 @@ def test_class_from_function(function, class_type):
     module_path, name = get_import_path(cls).rsplit(".", 1)
     assert module_path == __name__
     assert cls is globals()[name]
+    assert cls is class_from_function(function)
 
 
 def test_class_from_function_name_clash():