From 3d625559620f4f44df63c887e4cd1803d2356c4b Mon Sep 17 00:00:00 2001 From: Mauricio Villegas <5780272+mauvilsa@users.noreply.github.com> Date: Wed, 19 Jul 2023 06:00:55 +0200 Subject: [PATCH] Fixed inconsistent ARG: and missing ENV: in help when default_env=True. --- CHANGELOG.rst | 1 + jsonargparse/_formatters.py | 6 ++++-- jsonargparse_tests/test_formatters.py | 9 ++++++++- jsonargparse_tests/test_signatures.py | 11 +++++++++++ jsonargparse_tests/test_subclasses.py | 26 ++++++++++++++++++++++++++ 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3f9fc15f..fff46af6 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 54f8aff9..87dc41a9 100644 --- a/jsonargparse/_formatters.py +++ b/jsonargparse/_formatters.py @@ -17,6 +17,8 @@ ActionConfigFile, ActionYesNo, _ActionConfigLoad, + _ActionHelpClassPath, + _ActionPrintConfig, _ActionSubCommands, _find_action, filter_default_actions, @@ -97,10 +99,10 @@ def _format_usage(self, *args, **kwargs) -> str: 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 diff --git a/jsonargparse_tests/test_formatters.py b/jsonargparse_tests/test_formatters.py index cf9c70a5..26960d82 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 a280f393..1a843953 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 c6fc4194..06d6fb98 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 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