Repository: omni-us/jsonargparse
Pull request #167 (2022-09-30 11:13:51)

Subclass --*.help not available for types in Union
## What does this PR do?

Fixed an issue where subclass --*.help option would not available when type is a Union mixed with not subclass types.

## Before submitting

- [x] Did you read the [contributing guideline](https://github.com/omni-us/jsonargparse/blob/master/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-commenter: # [Codecov](https://codecov.io/gh/omni-us/jsonargparse/pull/167?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) Report
Base: **100.00**% // Head: **100.00**% // No change to project coverage :thumbsup:
> Coverage data is based on head [(`5f45fd9`)](https://codecov.io/gh/omni-us/jsonargparse/pull/167?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) compared to base [(`ee01123`)](https://codecov.io/gh/omni-us/jsonargparse/commit/ee0112315179a456fb209449cfedfcb6165db7d7?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).
> Patch coverage: 100.00% of modified lines in pull request are covered.

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


```diff
@@            Coverage Diff            @@
##            master      #167   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           17        17           
  Lines         4288      4293    +5     
=========================================
+ Hits          4288      4293    +5     
```

| Flag | Coverage Δ | |
|---|---|---|
| py3.10_all | `99.93% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.6 | `90.76% <100.00%> (+0.01%)` | :arrow_up: |
| py3.6_all | `99.71% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.7_all | `99.83% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.8 | `91.03% <100.00%> (+0.01%)` | :arrow_up: |
| py3.8_all | `99.90% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.9_all | `99.90% <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.

| [Impacted Files](https://codecov.io/gh/omni-us/jsonargparse/pull/167?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) | Coverage Δ | |
|---|---|---|
| [jsonargparse/actions.py](https://codecov.io/gh/omni-us/jsonargparse/pull/167/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL2FjdGlvbnMucHk=) | `100.00% <100.00%> (ø)` | |
| [jsonargparse/typehints.py](https://codecov.io/gh/omni-us/jsonargparse/pull/167/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL3R5cGVoaW50cy5weQ==) | `100.00% <100.00%> (ø)` | |
| [jsonargparse/util.py](https://codecov.io/gh/omni-us/jsonargparse/pull/167/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL3V0aWwucHk=) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/omni-us/jsonargparse/pull/167?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](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).

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=167)

[![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=167&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=167&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=167&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=167&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=167&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=167&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=167&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=167&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=167&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=167&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=167&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=167&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=167) 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=167&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=167&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 38f7d48..78af9d3 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -19,6 +19,8 @@ Fixed
 ^^^^^
 - ``compute_fn`` of a argument link applied on parse not given subclass default
   ``init_args`` when loading from config.
+- Subclass ``--*.help`` option not available when type is a ``Union`` mixed with
+  not subclass types.
 
 
 v4.15.0 (2022-09-27)
diff --git a/jsonargparse/actions.py b/jsonargparse/actions.py
index d50b9f3..5556023 100644
--- a/jsonargparse/actions.py
+++ b/jsonargparse/actions.py
@@ -1,5 +1,6 @@
 """Collection of useful actions to define arguments."""
 
+import inspect
 import os
 import re
 import sys
@@ -322,7 +323,11 @@ class _ActionHelpClassPath(Action):
 
     def update_init_kwargs(self, kwargs):
         if getattr(self._baseclass, '__origin__', None) == Union:
-            self._basename = iter_to_set_str(c.__name__ for c in self._baseclass.__args__ if c != NoneType)
+            from .typehints import ActionTypeHint
+            self._basename = iter_to_set_str(
+                c.__name__ for c in self._baseclass.__args__
+                if ActionTypeHint.is_subclass_typehint(c)
+            )
         else:
             self._basename = self._baseclass.__name__
         kwargs.update({
diff --git a/jsonargparse/typehints.py b/jsonargparse/typehints.py
index d356a2e..1263bd3 100644
--- a/jsonargparse/typehints.py
+++ b/jsonargparse/typehints.py
@@ -158,7 +158,7 @@ class ActionTypeHint(Action):
         typehint = kwargs.pop('type')
         if args[0].startswith('--') and ActionTypeHint.supports_append(typehint):
             args = tuple(list(args)+[args[0]+'+'])
-        if ActionTypeHint.is_subclass_typehint(typehint):
+        if ActionTypeHint.is_subclass_typehint(typehint, all_subtypes=False):
             help_action = container.add_argument(args[0]+'.help', action=_ActionHelpClassPath(baseclass=typehint))
             if sub_add_kwargs:
                 help_action.sub_add_kwargs = sub_add_kwargs
diff --git a/jsonargparse/util.py b/jsonargparse/util.py
index 2e2f00a..9d38208 100644
--- a/jsonargparse/util.py
+++ b/jsonargparse/util.py
@@ -170,8 +170,11 @@ def usage_and_exit_error_handler(parser: 'ArgumentParser', message: str) -> None
 
 
 def is_subclass(cls, class_or_tuple):
-    """Extension of issubclass that supports non-class argument."""
-    return inspect.isclass(cls) and issubclass(cls, class_or_tuple)
+    """Extension of issubclass that supports non-class arguments."""
+    try:
+        return inspect.isclass(cls) and issubclass(cls, class_or_tuple)
+    except TypeError:
+        return False
 
 
 def import_object(name: str):


Test patch:
diff --git a/jsonargparse_tests/test_typehints.py b/jsonargparse_tests/test_typehints.py
index e2d3230..48253a4 100755
--- a/jsonargparse_tests/test_typehints.py
+++ b/jsonargparse_tests/test_typehints.py
@@ -723,6 +723,21 @@ class TypeHintsTests(unittest.TestCase):
             self.assertEqual(cfg.data.init_args, Namespace(p1=2, p2='y', p3=True))
 
 
+    def test_class_type_subclass_in_union_help(self):
+        parser = ArgumentParser()
+        parser.add_argument('--op', type=Union[str, Mapping[str, int], Calendar])
+
+        out = StringIO()
+        with redirect_stdout(out), self.assertRaises(SystemExit):
+            parser.parse_args(['--help'])
+        self.assertIn('Show the help for the given subclass of Calendar', out.getvalue())
+
+        out = StringIO()
+        with redirect_stdout(out), self.assertRaises(SystemExit):
+            parser.parse_args([f'--op.help=TextCalendar'])
+        self.assertIn('--op.init_args.firstweekday', out.getvalue())
+
+
     def test_class_type_subclass_nested_help(self):
         class Class:
             def __init__(self, cal: Calendar, p1: int = 0):