Skip to content

Commit b08a1ca

Browse files
authored
Untyped parameters with None default no longer skipped when fail_untyped=True (#697)
1 parent 2fc33fd commit b08a1ca

File tree

3 files changed

+40
-26
lines changed

3 files changed

+40
-26
lines changed

CHANGELOG.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ 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.39.0 (2025-03-??)
16+
--------------------
17+
18+
Changed
19+
^^^^^^^
20+
- Untyped parameters with ``None`` default no longer skipped when
21+
``fail_untyped=True`` (`#697
22+
<https://github.com/omni-us/jsonargparse/pull/697>`__).
23+
24+
1525
v4.38.0 (2025-03-26)
1626
--------------------
1727

jsonargparse/_signatures.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -356,11 +356,7 @@ def _add_signature_parameter(
356356
default = None
357357
is_required = False
358358
is_required_link_target = True
359-
if (
360-
kind in {kinds.VAR_POSITIONAL, kinds.VAR_KEYWORD}
361-
or (not is_required and name[0] == "_")
362-
or (annotation == inspect_empty and not is_required and default is None)
363-
):
359+
if kind in {kinds.VAR_POSITIONAL, kinds.VAR_KEYWORD} or (not is_required and name[0] == "_"):
364360
return
365361
elif skip and name in skip:
366362
self.logger.debug(skip_message + "Parameter requested to be skipped.")

jsonargparse_tests/test_signatures.py

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,15 @@ def test_add_class_without_nesting(parser):
9595
parser.add_class_arguments(Class3)
9696

9797
assert "Class3" in parser.groups
98-
for key in "c3_a0 c3_a1 c3_a2 c3_a3 c3_a4 c3_a5 c3_a6 c3_a7 c3_a8 c1_a2 c1_a4 c1_a5".split():
98+
for key in "c3_a0 c3_a1 c3_a2 c3_a3 c3_a4 c3_a5 c3_a6 c3_a7 c3_a8 c1_a2 c1_a3 c1_a4 c1_a5".split():
9999
assert _find_action(parser, key) is not None, f"{key} should be in parser but is not"
100-
for key in ["c2_a0", "c1_a1", "c1_a3", "c0_a0"]:
100+
for key in ["c2_a0", "c1_a1", "c0_a0"]:
101101
assert _find_action(parser, key) is None, f"{key} should not be in parser but is"
102102

103103
cfg = parser.parse_args(["--c3_a0=0", "--c3_a3=true", "--c3_a4=a"], with_meta=False)
104104
assert cfg.as_dict() == {
105105
"c1_a2": 2.0,
106+
"c1_a3": None,
106107
"c1_a4": 4,
107108
"c1_a5": "five",
108109
"c3_a0": 0,
@@ -136,12 +137,12 @@ def test_add_class_nested_as_group_false(parser):
136137
added_args = parser.add_class_arguments(Class3, "g", as_group=False)
137138

138139
assert "g" not in parser.groups
139-
assert 12 == len(added_args)
140+
assert 13 == len(added_args)
140141
assert all(a.startswith("g.") for a in added_args)
141142

142-
for key in "c3_a0 c3_a1 c3_a2 c3_a3 c3_a4 c3_a5 c3_a6 c3_a7 c3_a8 c1_a2 c1_a4 c1_a5".split():
143+
for key in "c3_a0 c3_a1 c3_a2 c3_a3 c3_a4 c3_a5 c3_a6 c3_a7 c3_a8 c1_a2 c1_a3 c1_a4 c1_a5".split():
143144
assert _find_action(parser, f"g.{key}") is not None, f"{key} should be in parser but is not"
144-
for key in ["c2_a0", "c1_a1", "c1_a3", "c0_a0"]:
145+
for key in ["c2_a0", "c1_a1", "c0_a0"]:
145146
assert _find_action(parser, f"g.{key}") is None, f"{key} should not be in parser but is"
146147

147148
defaults = parser.get_defaults()
@@ -218,13 +219,13 @@ def test_add_class_nested_with_and_without_parameters(parser):
218219
assert isinstance(init.group.second, NestedWithoutParams)
219220

220221

221-
class NoValidParams:
222-
def __init__(self, a0=None):
222+
class SkippedUnderscoreParam:
223+
def __init__(self, _a0=None):
223224
pass
224225

225226

226-
def test_add_class_without_valid_parameters(parser):
227-
assert [] == parser.add_class_arguments(NoValidParams)
227+
def test_add_class_skipped_underscore_parameter(parser):
228+
assert [] == parser.add_class_arguments(SkippedUnderscoreParam)
228229

229230

230231
class WithNew:
@@ -437,18 +438,17 @@ def test_add_class_unmatched_default_type(parser):
437438

438439

439440
class WithMethods:
440-
def normal_method(self, a1="1", a2: float = 2.0, a3: bool = False, a4=None):
441+
def normal_method(self, a1="1", a2: float = 2.0, a3: bool = False):
441442
"""normal_method short description
442443
443444
Args:
444445
a1: a1 description
445446
a2: a2 description
446-
a4: a4 description
447447
"""
448448
return a1
449449

450450
@staticmethod
451-
def static_method(a1: str, a2: float = 2.0, a3=None):
451+
def static_method(a1: str, a2: float = 2.0, _a3=None):
452452
return a1
453453

454454

@@ -472,8 +472,7 @@ def test_add_method_normal_and_static(parser):
472472

473473
for key in ["m.a1", "m.a2", "m.a3", "s.a1", "s.a2"]:
474474
assert _find_action(parser, key) is not None, f"{key} should be in parser but is not"
475-
for key in ["m.a4", "s.a3"]:
476-
assert _find_action(parser, key) is None, f"{key} should not be in parser but is"
475+
assert _find_action(parser, "s._a3") is None, "s._a3 should not be in parser but is"
477476

478477
cfg = parser.parse_args(["--m.a1=x", "--s.a1=y"], with_meta=False).as_dict()
479478
assert cfg == {"m": {"a1": "x", "a2": 2.0, "a3": False}, "s": {"a1": "y", "a2": 2.0}}
@@ -526,17 +525,16 @@ def test_add_function_arguments(parser):
526525

527526
assert "func" in parser.groups
528527

529-
for key in ["a1", "a2", "a3"]:
528+
for key in ["a1", "a2", "a3", "a4"]:
530529
assert _find_action(parser, key) is not None, f"{key} should be in parser but is not"
531-
assert _find_action(parser, "a4") is None, "a4 should not be in parser but is"
532530

533531
cfg = parser.parse_args(["--a1=x"], with_meta=False).as_dict()
534-
assert cfg == {"a1": "x", "a2": 2.0, "a3": False}
532+
assert cfg == {"a1": "x", "a2": 2.0, "a3": False, "a4": None}
535533
assert "x" == func(**cfg)
536534

537535
if docstring_parser_support:
538536
assert "func short description" == parser.groups["func"].title
539-
for key in ["a1", "a2"]:
537+
for key in ["a1", "a2", "a4"]:
540538
assert f"{key} description" == _find_action(parser, key).help
541539

542540

@@ -604,6 +602,16 @@ def test_add_function_fail_untyped_false(parser):
604602
assert Namespace(a1=None, a2=None) == parser.parse_args([])
605603

606604

605+
def func_untyped_optional(a1: str, a2=None):
606+
return a1
607+
608+
609+
def test_add_function_fail_untyped_true_untyped_optional(parser):
610+
added_args = parser.add_function_arguments(func_untyped_optional, fail_untyped=True)
611+
assert ["a1", "a2"] == added_args
612+
assert Namespace(a1="x", a2=None) == parser.parse_args(["--a1=x"])
613+
614+
607615
def func_config(a1="1", a2: float = 2.0, a3: bool = False):
608616
return a1
609617

@@ -616,10 +624,10 @@ def test_add_function_group_config(parser, tmp_cwd):
616624
cfg_path.write_text(json_or_yaml_dump({"a1": "one", "a3": True}))
617625

618626
cfg = parser.parse_args([f"--func={cfg_path}"])
619-
assert cfg.func == Namespace(a1="one", a2=2.0, a3=True)
627+
assert cfg.func == Namespace(a1="one", a2=2.0, a3=True, a4=None)
620628

621629
cfg = parser.parse_args(['--func={"a1": "ONE"}'])
622-
assert cfg.func == Namespace(a1="ONE", a2=2.0, a3=False)
630+
assert cfg.func == Namespace(a1="ONE", a2=2.0, a3=False, a4=None)
623631

624632
with pytest.raises(ArgumentError) as ctx:
625633
parser.parse_args(['--func="""'])
@@ -638,7 +646,7 @@ def test_add_function_group_config_within_config(parser, tmp_cwd):
638646

639647
cfg = parser.parse_args([f"--cfg={cfg_path}"])
640648
assert str(cfg.func.__path__) == str(subcfg_path)
641-
assert strip_meta(cfg.func) == Namespace(a1="one", a2=2.0, a3=True)
649+
assert strip_meta(cfg.func) == Namespace(a1="one", a2=2.0, a3=True, a4=None)
642650

643651

644652
def func_param_conflict(p1: int, cfg: dict):

0 commit comments

Comments
 (0)