Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix key related errors printing messages within single quotes #401

Merged
merged 1 commit into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Fixed
^^^^^
- Failures with subcommands and default_config_files when keys are repeated
(`#160 <https://github.com/omni-us/jsonargparse/issues/160>`__).
- Key related errors printing messages within single quotes.

Changed
^^^^^^^
Expand Down
6 changes: 4 additions & 2 deletions jsonargparse/_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from ._common import get_class_instantiator, is_subclass, parser_context
from ._loaders_dumpers import get_loader_exceptions, load_value
from ._namespace import Namespace, split_key, split_key_root
from ._namespace import Namespace, NSKeyError, split_key, split_key_root
from ._optionals import FilesCompleterMethod, get_config_read_mode
from ._type_checking import ArgumentParser
from ._util import (
Expand Down Expand Up @@ -715,7 +715,9 @@ def get_subcommands(
candidate_subcommands_str = "{" + ",".join(available_subcommands) + "}"
else:
candidate_subcommands_str = "{" + ",".join(available_subcommands[:5]) + ", ...}"
raise KeyError(f'expected "{dest}" to be one of {candidate_subcommands_str}, but it was not provided.')
raise NSKeyError(
f'expected "{dest}" to be one of {candidate_subcommands_str}, but it was not provided.'
)

return subcommand_keys, [action._name_parser_map.get(s) for s in subcommand_keys] # type: ignore

Expand Down
11 changes: 6 additions & 5 deletions jsonargparse/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
)
from ._namespace import (
Namespace,
NSKeyError,
is_meta_key,
patch_namespace,
recreate_branches,
Expand Down Expand Up @@ -881,7 +882,7 @@ def set_defaults(self, *args: Dict[str, Any], **kwargs: Any) -> None:
for dest, default in arg.items():
action = _find_action(self, dest)
if action is None:
raise KeyError(f'No action for destination key "{dest}" to set its default.')
raise NSKeyError(f'No action for key "{dest}" to set its default.')
elif isinstance(action, ActionConfigFile):
ActionConfigFile.set_default_error()
if isinstance(action, ActionTypeHint):
Expand Down Expand Up @@ -918,11 +919,11 @@ def get_default(self, dest: str) -> Any:
"""
action, _ = _find_parent_action_and_subcommand(self, dest)
if action is None or dest != action.dest:
raise KeyError(f'No action for destination key "{dest}" to get its default.')
raise NSKeyError(f'No action for key "{dest}" to get its default.')

def check_suppressed_default():
if action.default == argparse.SUPPRESS:
raise KeyError(f'Action for destination key "{dest}" does not specify a default.')
raise NSKeyError(f'Action for key "{dest}" does not specify a default.')

if not self._get_default_config_files():
check_suppressed_default()
Expand Down Expand Up @@ -1069,15 +1070,15 @@ def check_values(cfg):
elif key in self.groups and hasattr(self.groups[key], "instantiate_class"):
raise TypeError(f"Class group {key!r} got an unexpected value: {val}.")
else:
raise KeyError(f'No action for destination key "{key}" to check its value.')
raise NSKeyError(f'No action for key "{key}" to check its value.')

try:
if not skip_required and not lenient_check.get():
check_required(cfg, self)
with parser_context(load_value_mode=self.parser_mode):
check_values(cfg)
except (TypeError, KeyError) as ex:
prefix = "Configuration check failed :: "
prefix = "Validation failed: "
message = ex.args[0]
if prefix not in message:
message = prefix + message
Expand Down
13 changes: 9 additions & 4 deletions jsonargparse/_namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
meta_keys = {"__default_config__", "__path__", "__orig__"}


class NSKeyError(KeyError):
def __str__(self):
return str(self.args[0])


def split_key(key: str) -> List[str]:
return key.split(".")

Expand Down Expand Up @@ -146,10 +151,10 @@ def _parse_key(self, key: str) -> Tuple[str, Optional["Namespace"], str]:
KeyError: When given invalid key.
"""
if " " in key:
raise KeyError(f'Spaces not allowed in keys: "{key}".')
raise NSKeyError(f'Spaces not allowed in keys: "{key}".')
key_split = split_key(key)
if any(k == "" for k in key_split):
raise KeyError(f'Empty nested key: "{key}".')
raise NSKeyError(f'Empty nested key: "{key}".')
key_split = [add_clash_mark(k) for k in key_split]
leaf_key = key_split[-1]
parent_ns: Namespace = self
Expand All @@ -169,7 +174,7 @@ def _parse_required_key(self, key: str) -> Tuple[str, "Namespace", str]:
"""Same as _parse_key but raises KeyError if key not found."""
leaf_key, parent_ns, parent_key = self._parse_key(key)
if parent_ns is None or not hasattr(parent_ns, leaf_key):
raise KeyError(f'Key "{key}" not found in namespace.')
raise NSKeyError(f'Key "{key}" not found in namespace.')
return leaf_key, parent_ns, parent_key

def _create_nested_namespace(self, key: str) -> "Namespace":
Expand Down Expand Up @@ -306,7 +311,7 @@ def update(
"""
if not isinstance(value, Namespace):
if not key:
raise KeyError("Key is required if value not a Namespace.")
raise NSKeyError("Key is required if value not a Namespace.")
if not only_unset or key not in self:
self[key] = value
else:
Expand Down
6 changes: 3 additions & 3 deletions jsonargparse_tests/test_subclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ def test_subclass_class_name_then_invalid_init_args(parser):
parser.add_argument("--op", type=Union[Calendar, GzipFile])
with pytest.raises(ArgumentError) as ctx:
parser.parse_args(["--op=TextCalendar", "--op=GzipFile", "--op.firstweekday=2"])
ctx.match('No action for destination key "firstweekday"')
ctx.match('No action for key "firstweekday"')


# dict parameter tests
Expand Down Expand Up @@ -1349,7 +1349,7 @@ def test_subclass_print_config(parser):
assert obtained == {"a1": {"class_path": "calendar.Calendar", "init_args": {"firstweekday": 0}}, "a2": 7}

err = get_parse_args_stderr(parser, ["--g.a1=calendar.Calendar", "--g.a1.invalid=1", "--print_config"])
assert 'No action for destination key "invalid"' in err
assert 'No action for key "invalid"' in err


class PrintConfigRequired:
Expand Down Expand Up @@ -1427,7 +1427,7 @@ def test_subclass_error_unexpected_init_arg(parser):
init_args = '"init_args": {"unexpected_arg": True}'
with pytest.raises(ArgumentError) as ctx:
parser.parse_args(["--op={" + class_path + ", " + init_args + "}"])
ctx.match('No action for destination key "unexpected_arg"')
ctx.match('No action for key "unexpected_arg"')


def test_subclass_invalid_class_path_value(parser):
Expand Down
6 changes: 3 additions & 3 deletions jsonargparse_tests/test_subcommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ def test_subcommands_undefined_subcommand(subcommands_parser):

def test_subcommands_not_given_when_few_subcommands(subcommands_parser):
err = get_parse_args_stderr(subcommands_parser, [])
assert "error: 'expected \"subcommand\" to be one of {a,b,B}, but it was not provided.'" in err
assert 'error: expected "subcommand" to be one of {a,b,B}, but it was not provided.' in err


def test_subcommands_not_given_when_many_subcommands(parser, subparser):
subcommands = parser.add_subcommands()
for subcommand in range(ord("a"), ord("l") + 1):
subcommands.add_subcommand(chr(subcommand), subparser)
err = get_parse_args_stderr(parser, [])
assert "error: 'expected \"subcommand\" to be one of {a,b,c,d,e, ...}, but it was not provided.'" in err
assert 'error: expected "subcommand" to be one of {a,b,c,d,e, ...}, but it was not provided.' in err


def test_subcommands_missing_required_subargument(subcommands_parser):
Expand Down Expand Up @@ -121,7 +121,7 @@ def test_subcommands_parse_string_implicit_subcommand(subcommands_parser):
assert cfg["a"] == {"ap1": "ap1_cfg", "ao1": "ao1_def"}
with pytest.raises(ArgumentError) as ctx:
subcommands_parser.parse_string('{"a": {"ap1": "ap1_cfg", "unk": "unk_cfg"}}')
ctx.match('No action for destination key "unk"')
ctx.match('No action for key "unk"')


def test_subcommands_parse_string_first_implicit_subcommand(subcommands_parser):
Expand Down