Skip to content

Commit bf782ce

Browse files
authored
validate now checks values before required so that errors related to wrong level in a config are easier to understand (#681)
1 parent 8b95fd2 commit bf782ce

File tree

3 files changed

+15
-8
lines changed

3 files changed

+15
-8
lines changed

CHANGELOG.rst

+7-1
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,15 @@ The semantic versioning only considers the public API as described in
1212
paths are considered internals and can change in minor and patch releases.
1313

1414

15-
v4.37.1 (2025-02-??)
15+
v4.38.0 (2025-02-??)
1616
--------------------
1717

18+
Changed
19+
^^^^^^^
20+
- ``validate`` now checks values before required so that errors related to wrong
21+
level in a config are easier to understand (`#681
22+
<https://github.com/omni-us/jsonargparse/pull/681>`__).
23+
1824
Fixed
1925
^^^^^
2026
- ``add_class_arguments`` with dashes in the ``nested_key`` fail to instantiate

jsonargparse/_core.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,7 @@ def validate(
10511051
skip_none: bool = True,
10521052
skip_required: bool = False,
10531053
branch: Optional[str] = None,
1054+
**kwargs,
10541055
) -> None:
10551056
"""Checks that the content of a given configuration object conforms with the parser.
10561057
@@ -1064,13 +1065,14 @@ def validate(
10641065
TypeError: If any of the values are not valid.
10651066
KeyError: If a key in cfg is not defined in the parser.
10661067
"""
1068+
prefix = get_private_kwargs(kwargs, _prefix="")
10671069
cfg = ccfg = cfg.clone()
10681070
if isinstance(branch, str):
10691071
branch_cfg = cfg
10701072
cfg = Namespace()
10711073
cfg[branch] = branch_cfg
10721074

1073-
def check_required(cfg, parser, prefix=""):
1075+
def check_required(cfg, parser, prefix):
10741076
for reqkey in parser.required_args:
10751077
try:
10761078
val = cfg[reqkey]
@@ -1082,7 +1084,7 @@ def check_required(cfg, parser, prefix=""):
10821084
) from ex
10831085
subcommand, subparser = _ActionSubCommands.get_subcommand(parser, cfg, fail_no_subcommand=False)
10841086
if subcommand is not None and subparser is not None:
1085-
check_required(cfg.get(subcommand), subparser, subcommand + ".")
1087+
check_required(cfg.get(subcommand), subparser, prefix + subcommand + ".")
10861088

10871089
def check_values(cfg):
10881090
sorted_keys = {k: _find_action(self, k) for k in cfg.get_sorted_keys()}
@@ -1119,10 +1121,10 @@ def check_values(cfg):
11191121
raise NSKeyError(f"Key '{key}' is not expected")
11201122

11211123
try:
1122-
if not skip_required and not lenient_check.get():
1123-
check_required(cfg, self)
11241124
with parser_context(load_value_mode=self.parser_mode):
11251125
check_values(cfg)
1126+
if not skip_required and not lenient_check.get():
1127+
check_required(cfg, self, prefix)
11261128
except (TypeError, KeyError) as ex:
11271129
prefix = "Validation failed: "
11281130
message = ex.args[0]
@@ -1391,7 +1393,7 @@ def _check_value_key(self, action: argparse.Action, value: Any, key: str, cfg: O
13911393
if leaf_key == action.dest:
13921394
return value
13931395
subparser = action._name_parser_map[leaf_key] # type: ignore[attr-defined]
1394-
subparser.validate(value)
1396+
subparser.validate(value, _prefix=key + ".")
13951397
elif isinstance(action, _ActionConfigLoad):
13961398
if isinstance(value, str):
13971399
value = action.check_type(value, self)

jsonargparse_tests/test_subcommands.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,8 @@ def test_subcommands_not_given_when_many_subcommands(parser, subparser):
7272

7373

7474
def test_subcommands_missing_required_subargument(subcommands_parser):
75-
with pytest.raises(ArgumentError) as ctx:
75+
with pytest.raises(ArgumentError, match='Key "a.ap1" is required'):
7676
subcommands_parser.parse_args(["a"])
77-
ctx.match('"a.ap1" is required')
7877

7978

8079
def test_subcommands_undefined_subargument(subcommands_parser):

0 commit comments

Comments
 (0)