Repository: omni-us/jsonargparse
Pull request #327 (2023-07-19 04:02:59)

Fix inconsistent ARG and missing ENV in help when default_env=True
## 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=327)

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


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/omni-us/jsonargparse/pull/327?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 no project coverage change.
> Comparison is base [(`453a961`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/453a96149268e35ae13f350a04c1200efb72480e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) 100.00% compared to head [(`3d62555`)](https://app.codecov.io/gh/omni-us/jsonargparse/pull/327?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) 100.00%.

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


```diff
@@            Coverage Diff            @@
##              main      #327   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           21        21           
  Lines         5518      5518           
=========================================
  Hits          5518      5518           
```

| Flag | Coverage Δ | |
|---|---|---|
| py3.10 | `86.55% <100.00%> (ø)` | |
| py3.10_all | `98.36% <100.00%> (ø)` | |
| py3.10_types | `98.36% <100.00%> (ø)` | |
| py3.11 | `86.53% <100.00%> (ø)` | |
| py3.11_all | `98.35% <100.00%> (ø)` | |
| py3.11_types | `98.35% <100.00%> (ø)` | |
| py3.6 | `85.96% <100.00%> (ø)` | |
| py3.6_all | `97.84% <100.00%> (ø)` | |
| py3.7 | `87.95% <100.00%> (ø)` | |
| py3.7_all | `99.50% <100.00%> (ø)` | |
| py3.7_types | `99.48% <100.00%> (ø)` | |
| py3.8 | `88.12% <100.00%> (ø)` | |
| py3.8_all | `99.54% <100.00%> (ø)` | |
| py3.8_types | `99.52% <100.00%> (ø)` | |
| py3.9 | `88.05% <100.00%> (ø)` | |
| py3.9_all | `99.54% <100.00%> (ø)` | |
| py3.9_types | `99.54% <100.00%> (ø)` | |

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


</details>

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


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 3f9fc15..fff46af 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -20,6 +20,7 @@ Fixed
 - Invalid environment variable names when ``env_prefix`` is derived from
   a ``prog`` containing dashes.
 - Pylance unable to resolve types from ``jsonargparse.typing``.
+- Inconsistent ``ARG:`` and missing ``ENV:`` in help when ``default_env=True``.
 
 
 v4.22.1 (2023-07-07)
diff --git a/jsonargparse/_formatters.py b/jsonargparse/_formatters.py
index 54f8aff..87dc41a 100644
--- a/jsonargparse/_formatters.py
+++ b/jsonargparse/_formatters.py
@@ -17,6 +17,8 @@ from ._actions import (
     ActionConfigFile,
     ActionYesNo,
     _ActionConfigLoad,
+    _ActionHelpClassPath,
+    _ActionPrintConfig,
     _ActionSubCommands,
     _find_action,
     filter_default_actions,
@@ -97,10 +99,10 @@ class DefaultHelpFormatter(HelpFormatter):
 
     def _format_action_invocation(self, action: Action) -> str:
         parser = parent_parser.get()
-        if action.option_strings == [] or action.default == SUPPRESS or not parser.default_env:
+        if not (parser.default_env and action.option_strings):
             return super()._format_action_invocation(action)
         extr = ""
-        if parser.default_env:
+        if not isinstance(action, (_ActionHelpClassPath, _ActionPrintConfig, _HelpAction)):
             extr += "\n  ENV:   " + get_env_var(self, action)
         return "ARG:   " + super()._format_action_invocation(action) + extr
 


Test patch:
diff --git a/jsonargparse_tests/test_formatters.py b/jsonargparse_tests/test_formatters.py
index cf9c70a..26960d8 100644
--- a/jsonargparse_tests/test_formatters.py
+++ b/jsonargparse_tests/test_formatters.py
@@ -14,13 +14,20 @@ def parser() -> ArgumentParser:
     return ArgumentParser(prog="app", default_env=True)
 
 
+def test_help_basics(parser):
+    help_str = get_parser_help(parser)
+    assert "ARG:   -h, --help" in help_str
+    assert "APP_HELP" not in help_str
+
+
 def test_help_action_config_file(parser):
     parser.add_argument("-c", "--cfg", help="Config in yaml/json.", action=ActionConfigFile)
     help_str = get_parser_help(parser)
-    assert "--print_config" in help_str
+    assert "ARG:   --print_config" in help_str
     assert "ARG:   -c CFG, --cfg CFG" in help_str
     assert "ENV:   APP_CFG" in help_str
     assert "Config in yaml/json." in help_str
+    assert "APP_PRINT_CONFIG" not in help_str
 
 
 def test_help_required_and_default(parser):
diff --git a/jsonargparse_tests/test_signatures.py b/jsonargparse_tests/test_signatures.py
index a280f39..1a84395 100644
--- a/jsonargparse_tests/test_signatures.py
+++ b/jsonargparse_tests/test_signatures.py
@@ -164,6 +164,17 @@ def test_add_class_with_default(parser):
     assert defaults == Namespace(cls=Namespace(p1=2, p2="-"))
 
 
+def test_add_class_env_help(parser):
+    parser.env_prefix = "APP"
+    parser.default_env = True
+    parser.add_class_arguments(WithDefault, "cls")
+    help_str = get_parser_help(parser)
+    assert "ARG:   --cls CONFIG" in help_str
+    assert "ARG:   --cls.p1 P1" in help_str
+    assert "ENV:   APP_CLS\n" in help_str
+    assert "ENV:   APP_CLS__P1" in help_str
+
+
 class NoParams:
     pass
 
diff --git a/jsonargparse_tests/test_subclasses.py b/jsonargparse_tests/test_subclasses.py
index c6fc419..06d6fb9 100644
--- a/jsonargparse_tests/test_subclasses.py
+++ b/jsonargparse_tests/test_subclasses.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 import json
+import os
 import textwrap
 import warnings
 from calendar import Calendar, HTMLCalendar, January, TextCalendar  # type: ignore
@@ -9,6 +10,7 @@ from dataclasses import dataclass
 from gzip import GzipFile
 from pathlib import Path
 from typing import Any, Dict, Iterable, List, Mapping, Optional, Union
+from unittest.mock import patch
 
 import pytest
 import yaml
@@ -324,6 +326,30 @@ def test_subclass_in_subcommand_with_global_default_config_file(parser, subparse
     assert cfg.fit.model.foo == 123
 
 
+# environment tests
+
+
+def test_subclass_env_help(parser):
+    parser.env_prefix = "APP"
+    parser.default_env = True
+    parser.add_argument("--cal", type=Calendar)
+    help_str = get_parser_help(parser)
+    assert "ARG:   --cal CAL" in help_str
+    assert "ARG:   --cal.help" in help_str
+    assert "ENV:   APP_CAL" in help_str
+    assert "APP_CAL_HELP" not in help_str
+
+
+def test_subclass_env_config(parser):
+    parser.env_prefix = "APP"
+    parser.default_env = True
+    parser.add_argument("--cal", type=Calendar)
+    env = {"APP_CAL": "{'class_path': 'TextCalendar', 'init_args': {'firstweekday': 4}}"}
+    with patch.dict(os.environ, env):
+        cfg = parser.parse_env()
+    assert cfg.cal == Namespace(class_path="calendar.TextCalendar", init_args=Namespace(firstweekday=4))
+
+
 # nested subclass tests