Repository: omni-us/jsonargparse
Pull request #324 (2023-07-14 02:29:02)

Subcommands main parser help changes
## What does this PR do?

- Set notation of subcommands choices now only included in usage.
- In subcommands section, now each subcommand is always shown separately, including the name, and if available aliases and help.
- When default_env=True include subcommand environment variable name.

## Before submitting

- [x] Did you read the [contributing guideline](https://github.com/omni-us/jsonargparse/blob/main/CONTRIBUTING.rst)?
- [x] 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=324)

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


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/omni-us/jsonargparse/pull/324?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.02`** :warning:
> Comparison is base [(`854bbd0`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/854bbd08efd65b2810db815aaf6e46a1c9fd24a7?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) 100.00% compared to head [(`b5a6db9`)](https://app.codecov.io/gh/omni-us/jsonargparse/pull/324?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) 99.98%.

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


```diff
@@             Coverage Diff             @@
##              main     #324      +/-   ##
===========================================
- Coverage   100.00%   99.98%   -0.02%     
===========================================
  Files           21       21              
  Lines         5516     5522       +6     
===========================================
+ Hits          5516     5521       +5     
- Misses           0        1       +1     
```

| Flag | Coverage Δ | |
|---|---|---|
| py3.10 | `?` | |
| py3.10_all | `?` | |
| py3.10_types | `?` | |
| py3.11 | `?` | |
| py3.11_all | `98.33% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.11_types | `98.33% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.6 | `85.95% <100.00%> (+0.01%)` | :arrow_up: |
| py3.6_all | `97.82% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.7 | `87.94% <100.00%> (+0.01%)` | :arrow_up: |
| py3.7_all | `99.48% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.7_types | `99.47% <100.00%> (+<0.01%)` | :arrow_up: |
| py3.8 | `88.15% <100.00%> (+0.01%)` | :arrow_up: |
| py3.8_all | `?` | |
| py3.8_types | `?` | |
| py3.9 | `?` | |
| py3.9_all | `?` | |
| py3.9_types | `?` | |

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://app.codecov.io/gh/omni-us/jsonargparse/pull/324?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://app.codecov.io/gh/omni-us/jsonargparse/pull/324?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL19hY3Rpb25zLnB5) | `100.00% <100.00%> (ø)` | |
| [jsonargparse/\_formatters.py](https://app.codecov.io/gh/omni-us/jsonargparse/pull/324?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us#diff-anNvbmFyZ3BhcnNlL19mb3JtYXR0ZXJzLnB5) | `100.00% <100.00%> (ø)` | |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/omni-us/jsonargparse/pull/324/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/324?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=324)

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



Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 07a48a1..cc24e9c 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -12,7 +12,7 @@ The semantic versioning only considers the public API as described in
 paths are considered internals and can change in minor and patch releases.
 
 
-v4.22.2 (2023-07-??)
+v4.23.0 (2023-07-??)
 --------------------
 
 Fixed
@@ -25,6 +25,14 @@ Fixed
   installed (`lightning#18125 comment
   <https://github.com/Lightning-AI/lightning/pull/18125#issuecomment-1644797707>`__).
 
+Changed
+^^^^^^^
+- Subcommands main parser help changes:
+    - Set notation of subcommands choices now only included in usage.
+    - In subcommands section, now each subcommand is always shown separately,
+      including the name, and if available aliases and help.
+    - When ``default_env=True`` include subcommand environment variable name.
+
 
 v4.22.1 (2023-07-07)
 --------------------
diff --git a/jsonargparse/_actions.py b/jsonargparse/_actions.py
index ebeff61..57a6269 100644
--- a/jsonargparse/_actions.py
+++ b/jsonargparse/_actions.py
@@ -595,10 +595,11 @@ class _ActionSubCommands(_SubParsersAction):
 
         # create a pseudo-action to hold the choice help
         aliases = kwargs.pop("aliases", ())
+        help_arg = None
         if "help" in kwargs:
             help_arg = kwargs.pop("help")
-            choice_action = self._ChoicesPseudoAction(name, aliases, help_arg)
-            self._choices_actions.append(choice_action)
+        choice_action = self._ChoicesPseudoAction(name, aliases, help_arg)
+        self._choices_actions.append(choice_action)
 
         # add the parser to the name-parser map
         self._name_parser_map[name] = parser
diff --git a/jsonargparse/_formatters.py b/jsonargparse/_formatters.py
index 87dc41a..9ddc566 100644
--- a/jsonargparse/_formatters.py
+++ b/jsonargparse/_formatters.py
@@ -99,7 +99,12 @@ class DefaultHelpFormatter(HelpFormatter):
 
     def _format_action_invocation(self, action: Action) -> str:
         parser = parent_parser.get()
-        if not (parser.default_env and action.option_strings):
+        if isinstance(action, _ActionSubCommands):
+            value = "Available subcommands:"
+            if parser.default_env:
+                value = f"ENV:   {get_env_var(self, action)}\n\n  {value}"
+            return value
+        if action.option_strings == [] or not parser.default_env:
             return super()._format_action_invocation(action)
         extr = ""
         if not isinstance(action, (_ActionHelpClassPath, _ActionPrintConfig, _HelpAction)):


Test patch:
diff --git a/jsonargparse_tests/test_subcommands.py b/jsonargparse_tests/test_subcommands.py
index 3e4421c..a5d2461 100644
--- a/jsonargparse_tests/test_subcommands.py
+++ b/jsonargparse_tests/test_subcommands.py
@@ -15,7 +15,7 @@ from jsonargparse import (
     Namespace,
     strip_meta,
 )
-from jsonargparse_tests.conftest import get_parse_args_stdout
+from jsonargparse_tests.conftest import get_parse_args_stdout, get_parser_help
 
 
 @pytest.fixture
@@ -81,6 +81,13 @@ def test_subcommands_parse_args_basics(subcommands_parser):
     assert "a" not in cfg
 
 
+def test_main_subcommands_help(subcommands_parser):
+    help_str = get_parser_help(subcommands_parser)
+    assert help_str.count("{a,b,B}") == 1
+    assert "Available subcommands:" in help_str
+    assert "b (B)" in help_str
+
+
 def test_subcommands_parse_args_alias(subcommands_parser):
     cfg = subcommands_parser.parse_args(["B"])
     assert cfg["subcommand"] == "B"
@@ -163,6 +170,12 @@ def test_subcommands_parse_env(subcommands_parser):
     assert cfg["a"] == {"ap1": "ap1_env", "ao1": "ao1_env"}
 
 
+def test_subcommands_help_default_env_true(subcommands_parser):
+    subcommands_parser.default_env = True
+    help_str = get_parser_help(subcommands_parser)
+    assert "ENV:   APP_SUBCOMMAND" in help_str
+
+
 def test_subcommand_required_false(parser, subparser):
     subcommands = parser.add_subcommands(required=False)
     subcommands.add_subcommand("foo", subparser)