Skip to content

Commit a530e3c

Browse files
authored
Added option to enable validation of default values (#711)
1 parent ce4b8aa commit a530e3c

File tree

4 files changed

+54
-4
lines changed

4 files changed

+54
-4
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ Added
2525
<https://github.com/omni-us/jsonargparse/pull/701>`__).
2626
- Resolve parameters completely from stubs when ``inspect.signature`` fails
2727
(`#698 <https://github.com/omni-us/jsonargparse/pull/698>`__).
28+
- Option to enable validation of default values (`#711
29+
<https://github.com/omni-us/jsonargparse/pull/711>`__).
2830

2931
Changed
3032
^^^^^^^

jsonargparse/_common.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
reconplogger_support,
3030
typing_extensions_import,
3131
)
32-
from ._type_checking import ArgumentParser
32+
from ._type_checking import ActionsContainer, ArgumentParser
3333

3434
__all__ = [
3535
"LoggerProperty",
@@ -55,7 +55,7 @@ def __call__(self, class_type: Type[ClassType], *args, **kwargs) -> ClassType:
5555
InstantiatorsDictType = Dict[Tuple[type, bool], InstantiatorCallable]
5656

5757

58-
parent_parser: ContextVar[Optional["ArgumentParser"]] = ContextVar("parent_parser", default=None)
58+
parent_parser: ContextVar[Optional[ArgumentParser]] = ContextVar("parent_parser", default=None)
5959
parser_capture: ContextVar[bool] = ContextVar("parser_capture", default=False)
6060
defaults_cache: ContextVar[Optional[Namespace]] = ContextVar("defaults_cache", default=None)
6161
lenient_check: ContextVar[Union[bool, str]] = ContextVar("lenient_check", default=False)
@@ -90,22 +90,34 @@ def parser_context(**kwargs):
9090

9191

9292
parsing_settings = dict(
93+
validate_defaults=False,
9394
parse_optionals_as_positionals=False,
9495
)
9596

9697

97-
def set_parsing_settings(*, parse_optionals_as_positionals: Optional[bool] = None) -> None:
98+
def set_parsing_settings(
99+
*,
100+
validate_defaults: Optional[bool] = None,
101+
parse_optionals_as_positionals: Optional[bool] = None,
102+
) -> None:
98103
"""
99104
Modify settings that affect the parsing behavior.
100105
101106
Args:
107+
validate_defaults: Whether default values must be valid according to the
108+
argument type. The default is False, meaning no default validation,
109+
like in argparse.
102110
parse_optionals_as_positionals: [EXPERIMENTAL] If True, the parser will
103111
take extra positional command line arguments as values for optional
104112
arguments. This means that optional arguments can be given by name
105113
--key=value as usual, but also as positional. The extra positionals
106114
are applied to optionals in the order that they were added to the
107-
parser.
115+
parser. By default, this is False.
108116
"""
117+
if isinstance(validate_defaults, bool):
118+
parsing_settings["validate_defaults"] = validate_defaults
119+
elif validate_defaults is not None:
120+
raise ValueError(f"validate_defaults must be a boolean, but got {validate_defaults}.")
109121
if isinstance(parse_optionals_as_positionals, bool):
110122
parsing_settings["parse_optionals_as_positionals"] = parse_optionals_as_positionals
111123
elif parse_optionals_as_positionals is not None:
@@ -118,6 +130,18 @@ def get_parsing_setting(name: str):
118130
return parsing_settings[name]
119131

120132

133+
def validate_default(container: ActionsContainer, action: argparse.Action):
134+
if not isinstance(action, Action) or not get_parsing_setting("validate_defaults") or action.default is None:
135+
return
136+
try:
137+
with parser_context(parent_parser=container):
138+
default = action.default
139+
action.default = None
140+
action.default = action._check_type_(default)
141+
except Exception as ex:
142+
raise ValueError(f"Default value is not valid: {ex}") from ex
143+
144+
121145
def get_optionals_as_positionals_actions(parser, include_positionals=False):
122146
from jsonargparse._actions import ActionConfigFile, _ActionConfigLoad, filter_default_actions
123147
from jsonargparse._completions import ShtabAction

jsonargparse/_core.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
lenient_check,
4747
parser_context,
4848
supports_optionals_as_positionals,
49+
validate_default,
4950
)
5051
from ._completions import (
5152
argcomplete_namespace,
@@ -151,6 +152,7 @@ def add_argument(self, *args, enable_path: bool = False, **kwargs):
151152
raise ValueError(f'Argument with destination name "{action.dest}" not allowed.')
152153
if action.option_strings == [] and "default" in kwargs:
153154
raise ValueError("Positional arguments not allowed to have a default value.")
155+
validate_default(self, action)
154156
if action.help is None:
155157
action.help = empty_help
156158
if action.required:

jsonargparse_tests/test_parsing_settings.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,28 @@ def test_get_parsing_setting_failure():
2222
get_parsing_setting("unknown_setting")
2323

2424

25+
# validate_defaults
26+
27+
28+
def test_set_validate_defaults_failure():
29+
with pytest.raises(ValueError, match="validate_defaults must be a boolean"):
30+
set_parsing_settings(validate_defaults="invalid")
31+
32+
33+
def test_validate_defaults_success(parser):
34+
set_parsing_settings(validate_defaults=True)
35+
36+
parser.add_argument("--num", type=int, default=1)
37+
parser.add_argument("--untyped", default=2)
38+
39+
40+
def test_validate_defaults_failure(parser):
41+
set_parsing_settings(validate_defaults=True)
42+
43+
with pytest.raises(ValueError, match="Default value is not valid:"):
44+
parser.add_argument("--num", type=int, default="x")
45+
46+
2547
# parse_optionals_as_positionals
2648

2749

0 commit comments

Comments
 (0)