Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Added

Changed
^^^^^^^
- Improved parameter kind handling for argument requirement determination.
``KEYWORD_ONLY`` parameters now correctly use ``--flag`` style (`#756
<https://github.com/omni-us/jsonargparse/pull/756>`__).
- Removed support for python 3.8 (`#752
<https://github.com/omni-us/jsonargparse/pull/752>`__).
- ``YAML`` comments feature is now implemented in a separate class to allow
Expand Down
26 changes: 22 additions & 4 deletions jsonargparse/_signatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,25 @@ def _add_signature_parameter(
default = None
elif get_typehint_origin(annotation) in not_required_types:
default = SUPPRESS
is_required = default == inspect_empty
# Determine argument characteristics based on parameter kind and default value
if kind == kinds.POSITIONAL_ONLY:
is_required = True # Always required
is_option = False # Can be positional
elif kind == kinds.POSITIONAL_OR_KEYWORD:
is_required = default == inspect_empty # Required if no default
is_option = False # Can be positional
elif kind == kinds.KEYWORD_ONLY:
is_required = default == inspect_empty # Required if no default
is_option = True # Must use --flag style
elif kind in {kinds.VAR_POSITIONAL, kinds.VAR_KEYWORD}:
# These parameter types don't translate well to CLI arguments
return # Skip entirely
elif kind is None:
# Fallback for programmatically created parameters without kind
is_required = default == inspect_empty # Required if no default
is_option = False # Can be positional (preserve old behavior)
else:
raise ValueError(f"Unknown parameter kind: {kind}")
src = get_parameter_origins(param.component, param.parent)
skip_message = f'Skipping parameter "{name}" from "{src}" because of: '
if not fail_untyped and annotation == inspect_empty:
Expand All @@ -356,7 +374,7 @@ def _add_signature_parameter(
default = None
is_required = False
is_required_link_target = True
if kind in {kinds.VAR_POSITIONAL, kinds.VAR_KEYWORD} or (not is_required and name[0] == "_"):
if not is_required and name[0] == "_":
return
elif skip and name in skip:
self.logger.debug(skip_message + "Parameter requested to be skipped.")
Expand All @@ -371,12 +389,12 @@ def _add_signature_parameter(
kwargs["default"] = default
if default is None and not is_optional(annotation, object) and not is_required_link_target:
annotation = Optional[annotation]
elif not as_positional:
elif not as_positional or is_option:
kwargs["required"] = True
is_subclass_typehint = False
is_dataclass_like_typehint = is_dataclass_like(annotation)
dest = (nested_key + "." if nested_key else "") + name
args = [dest if is_required and as_positional else "--" + dest]
args = [dest if is_required and as_positional and not is_option else "--" + dest]
if param.origin:
parser = container
if not isinstance(container, ArgumentParser):
Expand Down
19 changes: 19 additions & 0 deletions jsonargparse_tests/test_signatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,3 +697,22 @@ def test_add_function_param_conflict(parser):
with pytest.raises(ValueError) as ctx:
parser.add_function_arguments(func_param_conflict)
ctx.match("Unable to add parameter 'cfg' from")


def func_positional_only(a: int, /, b: int = 1):
"""Function with positional-only parameters."""
return a, b


def test_add_function_positional_only_parameters(parser):
"""Test coverage for POSITIONAL_ONLY parameter handling (lines 349-350)."""
added_args = parser.add_function_arguments(func_positional_only, fail_untyped=False)

# Both 'a' and 'b' should be added
assert "a" in added_args
assert "b" in added_args

# Test that we can parse with both parameters
cfg = parser.parse_args(["--a=1", "--b=2"])
assert cfg.a == 1
assert cfg.b == 2