Skip to content

Commit 0177681

Browse files
MilkCloudsmauvilsa
andauthored
Improve parameter kind handling for argument requirement determination (#756)
--------- Co-authored-by: Mauricio Villegas <5780272+mauvilsa@users.noreply.github.com>
1 parent 785af2c commit 0177681

File tree

3 files changed

+41
-4
lines changed

3 files changed

+41
-4
lines changed

CHANGELOG.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ Added
2020
- Support for Python 3.14 (`#753
2121
<https://github.com/omni-us/jsonargparse/pull/753>`__).
2222

23+
Fixed
24+
^^^^^
25+
- Improved parameter kind handling for argument requirement determination.
26+
``KEYWORD_ONLY`` parameters now correctly use ``--flag`` style (`#756
27+
<https://github.com/omni-us/jsonargparse/pull/756>`__).
28+
2329
Changed
2430
^^^^^^^
2531
- Removed support for python 3.8 (`#752

jsonargparse/_signatures.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,22 @@ def _add_signature_parameter(
344344
default = None
345345
elif get_typehint_origin(annotation) in not_required_types:
346346
default = SUPPRESS
347-
is_required = default == inspect_empty
347+
# Determine argument characteristics based on parameter kind and default value
348+
if kind == kinds.POSITIONAL_ONLY:
349+
is_required = True # Always required
350+
is_non_positional = False # Can be positional
351+
elif kind == kinds.POSITIONAL_OR_KEYWORD:
352+
is_required = default == inspect_empty # Required if no default
353+
is_non_positional = False # Can be positional
354+
elif kind == kinds.KEYWORD_ONLY:
355+
is_required = default == inspect_empty # Required if no default
356+
is_non_positional = True # Must use --flag style
357+
elif kind is None:
358+
# programmatically created parameters without kind
359+
is_required = default == inspect_empty # Required if no default
360+
is_non_positional = False # Can be positional (preserve old behavior)
361+
else:
362+
raise RuntimeError(f"The code should never reach here: kind={kind}") # pragma: no cover
348363
src = get_parameter_origins(param.component, param.parent)
349364
skip_message = f'Skipping parameter "{name}" from "{src}" because of: '
350365
if not fail_untyped and annotation == inspect_empty:
@@ -356,7 +371,7 @@ def _add_signature_parameter(
356371
default = None
357372
is_required = False
358373
is_required_link_target = True
359-
if kind in {kinds.VAR_POSITIONAL, kinds.VAR_KEYWORD} or (not is_required and name[0] == "_"):
374+
if not is_required and name[0] == "_":
360375
return
361376
elif skip and name in skip:
362377
self.logger.debug(skip_message + "Parameter requested to be skipped.")
@@ -371,12 +386,12 @@ def _add_signature_parameter(
371386
kwargs["default"] = default
372387
if default is None and not is_optional(annotation, object) and not is_required_link_target:
373388
annotation = Optional[annotation]
374-
elif not as_positional:
389+
elif not as_positional or is_non_positional:
375390
kwargs["required"] = True
376391
is_subclass_typehint = False
377392
is_dataclass_like_typehint = is_dataclass_like(annotation)
378393
dest = (nested_key + "." if nested_key else "") + name
379-
args = [dest if is_required and as_positional else "--" + dest]
394+
args = [dest if is_required and as_positional and not is_non_positional else "--" + dest]
380395
if param.origin:
381396
parser = container
382397
if not isinstance(container, ArgumentParser):

jsonargparse_tests/test_signatures.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,3 +697,19 @@ def test_add_function_param_conflict(parser):
697697
with pytest.raises(ValueError) as ctx:
698698
parser.add_function_arguments(func_param_conflict)
699699
ctx.match("Unable to add parameter 'cfg' from")
700+
701+
702+
def func_positional_and_keyword_only(a: int, /, b: int, *, c: int, d: int = 1):
703+
pass
704+
705+
706+
def test_add_function_positional_and_keyword_only_parameters(parser):
707+
parser.add_function_arguments(func_positional_and_keyword_only, as_positional=True)
708+
709+
# Test that we can parse with both parameters
710+
cfg = parser.parse_args(["1", "2", "--c=3", "--d=4"])
711+
assert cfg == Namespace(a=1, b=2, c=3, d=4)
712+
with pytest.raises(ArgumentError, match="Unrecognized arguments: --b=2"):
713+
parser.parse_args(["1", "--b=2", "--c=3", "--d=4"])
714+
with pytest.raises(ArgumentError, match='Key "c" is required'):
715+
parser.parse_args(["1", "2", "--d=4"])

0 commit comments

Comments
 (0)