Skip to content

Commit 64630c1

Browse files
authored
Fix default_env=True conflicting with default_config_files (#818)
1 parent 8ad02de commit 64630c1

File tree

7 files changed

+39
-38
lines changed

7 files changed

+39
-38
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ Fixed
2828
<https://github.com/omni-us/jsonargparse/pull/814>`__).
2929
- Getting parameter descriptions from docstrings not working for dataclass
3030
inheritance (`#815 <https://github.com/omni-us/jsonargparse/pull/815>`__).
31+
- ``default_env=True`` conflicting with ``default_config_files`` (`#818
32+
<https://github.com/omni-us/jsonargparse/pull/818>`__).
3133

3234
Changed
3335
^^^^^^^

jsonargparse/_actions.py

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -629,21 +629,9 @@ def add_prefix(key):
629629

630630

631631
single_subcommand: ContextVar = ContextVar("single_subcommand", default=True)
632-
parent_parsers: ContextVar = ContextVar("parent_parsers", default=[])
633632
parse_kwargs: ContextVar = ContextVar("parse_kwargs", default={})
634633

635634

636-
@contextmanager
637-
def parent_parsers_context(key, parser):
638-
prev = parent_parsers.get()
639-
curr = [] if parser is None else prev + [(key, parser)]
640-
token = parent_parsers.set(curr)
641-
try:
642-
yield
643-
finally:
644-
parent_parsers.reset(token)
645-
646-
647635
class _ActionSubCommands(_SubParsersAction):
648636
"""Extension of argparse._SubParsersAction to modify subcommands functionality."""
649637

@@ -814,11 +802,10 @@ def handle_subcommands(
814802
# Merge environment variable values and default values
815803
subnamespace = None
816804
key = prefix + subcommand
817-
with parent_parsers_context(key, parser):
818-
if env:
819-
subnamespace = subparser.parse_env(defaults=defaults, _skip_validation=True)
820-
elif defaults:
821-
subnamespace = subparser.get_defaults(skip_validation=True)
805+
if env:
806+
subnamespace = subparser.parse_env(defaults=defaults, _skip_validation=True)
807+
elif defaults:
808+
subnamespace = subparser.get_defaults(skip_validation=True)
822809

823810
# Update all subcommand settings
824811
if subnamespace is not None:

jsonargparse/_core.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
_is_action_value_list,
2929
_is_branch_key,
3030
filter_non_parsing_actions,
31-
parent_parsers,
3231
previous_config,
3332
)
3433
from ._common import (
@@ -673,7 +672,6 @@ def _load_config_parser_mode(
673672
cfg_path: Union[str, os.PathLike] = "",
674673
ext_vars: Optional[dict] = None,
675674
prev_cfg: Optional[Namespace] = None,
676-
key: Optional[str] = None,
677675
) -> Namespace:
678676
"""Loads a configuration string into a namespace.
679677
@@ -689,8 +687,6 @@ def _load_config_parser_mode(
689687
cfg_dict = load_value(cfg_str, path=cfg_path, ext_vars=ext_vars)
690688
except get_loader_exceptions() as ex:
691689
raise TypeError(f"Problems parsing config: {ex}") from ex
692-
if key and isinstance(cfg_dict, dict):
693-
cfg_dict = cfg_dict.get(key, {})
694690
if not isinstance(cfg_dict, dict):
695691
raise TypeError(f"Unexpected config: {cfg_str}")
696692
return self._apply_actions(cfg_dict, prev_cfg=prev_cfg)
@@ -949,24 +945,18 @@ def save_paths(cfg):
949945

950946
## Methods related to defaults ##
951947

952-
def _get_default_config_files(self) -> list[tuple[Optional[str], Path]]:
948+
def _get_default_config_files(self) -> list[Path]:
953949
if getattr(self, "_inner_parser", False):
954950
return []
955951

956952
default_config_files = []
957953

958-
for key, parser in parent_parsers.get():
959-
for pattern in parser.default_config_files:
960-
files = sorted(glob.glob(os.path.expanduser(pattern)))
961-
default_config_files += [(key, v) for v in files]
962-
963954
for pattern in self.default_config_files:
964-
files = sorted(glob.glob(os.path.expanduser(pattern)))
965-
default_config_files += [(None, x) for x in files]
955+
default_config_files += sorted(glob.glob(os.path.expanduser(pattern)))
966956

967957
if len(default_config_files) > 0:
968958
with suppress(TypeError):
969-
return [(k, Path(v, mode=_get_config_read_mode())) for k, v in default_config_files]
959+
return [Path(v, mode=_get_config_read_mode()) for v in default_config_files]
970960
return []
971961

972962
def get_default(self, dest: str) -> Any:
@@ -1017,12 +1007,12 @@ def get_defaults(self, skip_validation: bool = False, **kwargs) -> Namespace:
10171007
self._logger.debug("Loaded parser defaults: %s", cfg)
10181008

10191009
default_config_files = self._get_default_config_files()
1020-
for key, default_config_file in default_config_files:
1010+
for default_config_file in default_config_files:
10211011
default_config_file_content = default_config_file.get_content()
10221012
if not default_config_file_content.strip():
10231013
continue
10241014
with change_to_path_dir(default_config_file), parser_context(parent_parser=self):
1025-
cfg_file = self._load_config_parser_mode(default_config_file_content, key=key, prev_cfg=cfg)
1015+
cfg_file = self._load_config_parser_mode(default_config_file_content, prev_cfg=cfg)
10261016
cfg = self.merge_config(cfg_file, cfg)
10271017
try:
10281018
with _ActionPrintConfig.skip_print_config():

jsonargparse/_typehints.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
_find_action,
4646
_find_parent_action,
4747
_is_action_value_list,
48-
parent_parsers_context,
4948
parse_kwargs,
5049
remove_actions,
5150
)
@@ -473,7 +472,7 @@ def skip_sub_defaults_apply(v):
473472
or (isinstance(v, dict) and any(is_subclass_spec(e) for e in v.values()))
474473
)
475474

476-
with ActionTypeHint.sub_defaults_context(), parent_parsers_context(None, None):
475+
with ActionTypeHint.sub_defaults_context():
477476
parser._apply_actions(cfg, skip_fn=skip_sub_defaults_apply, prev_cfg=cfg.clone())
478477

479478
@staticmethod

jsonargparse_tests/conftest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ def subparser() -> ArgumentParser:
135135
return ArgumentParser(exit_on_error=False)
136136

137137

138+
@pytest.fixture
139+
def subsubparser() -> ArgumentParser:
140+
return ArgumentParser(exit_on_error=False)
141+
142+
138143
@pytest.fixture
139144
def example_parser() -> ArgumentParser:
140145
parser = ArgumentParser(prog="app", exit_on_error=False)

jsonargparse_tests/test_actions.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,7 @@ def test_action_parser_conflict_subparser_key(parser, subparser):
335335
pytest.raises(ValueError, lambda: parser.add_argument("--inner", action=ActionParser(subparser)))
336336

337337

338-
def test_action_parser_nested_dash_names(parser, subparser):
339-
subsubparser = ArgumentParser()
338+
def test_action_parser_nested_dash_names(parser, subparser, subsubparser):
340339
subsubparser.add_argument("--op1-like")
341340
subparser.add_argument("--op2-like", action=ActionParser(parser=subsubparser))
342341
assert "a" == subparser.parse_args(["--op2-like.op1-like=a"]).op2_like.op1_like

jsonargparse_tests/test_subcommands.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,10 +402,9 @@ def test_subcommands_custom_instantiator(parser, subparser, subtests):
402402
assert init.cmd.cls.call == "subparser"
403403

404404

405-
def test_subsubcommand_default_env_true(parser, subparser):
405+
def test_subsubcommand_default_env_true(parser, subparser, subsubparser):
406406
parser.default_env = True
407407
parser.env_prefix = "APP"
408-
subsubparser = ArgumentParser()
409408
subsubparser.add_argument("--v", type=int, default=1)
410409
subcommands1 = parser.add_subcommands()
411410
subcommands1.add_subcommand("s1", subparser)
@@ -417,3 +416,23 @@ def test_subsubcommand_default_env_true(parser, subparser):
417416
with patch.dict(os.environ, {"APP_SUBCOMMAND": "s1", "APP_S1__SUBCOMMAND": "s2"}):
418417
cfg = parser.parse_args([])
419418
assert cfg == Namespace(subcommand="s1", s1=Namespace(subcommand="s2", s2=Namespace(v=1)))
419+
420+
421+
@pytest.mark.parametrize("default_env", [True, False])
422+
def test_subsubcommand_default_config_files(parser, subparser, subsubparser, default_env, tmp_cwd):
423+
config = {"val0": 123, "cmd1": {"val1": 456, "cmd2": {"val2": 789}}}
424+
Path("config.json").write_text(json.dumps(config))
425+
parser.default_env = default_env
426+
parser.default_config_files = ["config.json"]
427+
parser.add_argument("--val0")
428+
subparser.add_argument("--val1")
429+
subsubparser.add_argument("--val2")
430+
subcommands = parser.add_subcommands()
431+
subcommands.add_subcommand("cmd1", subparser)
432+
subsubcommands = subparser.add_subcommands()
433+
subsubcommands.add_subcommand("cmd2", subsubparser)
434+
435+
cfg = parser.parse_args([])
436+
assert cfg.clone(with_meta=False) == Namespace(
437+
val0=123, subcommand="cmd1", cmd1=Namespace(val1=456, subcommand="cmd2", cmd2=Namespace(val2=789))
438+
)

0 commit comments

Comments
 (0)