Skip to content

Support positional-only arguments in the strict sense#30

Open
maread99 wants to merge 3 commits into
mainfrom
claude/friendly-wright-WewuG
Open

Support positional-only arguments in the strict sense#30
maread99 wants to merge 3 commits into
mainfrom
claude/friendly-wright-WewuG

Conversation

@maread99
Copy link
Copy Markdown
Owner

@maread99 maread99 commented Jun 2, 2026

Summary

Previously valimp ignored any / in a decorated function's signature, allowing arguments intended to be positional-only to be passed as keyword arguments.

valimp now enforces positional-only arguments in the strict sense: a positional-only argument can only be passed positionally. Passing it by keyword raises an InputsError, unless the signature provides for **kwargs, in which case the keyword input is absorbed by **kwargs — consistent with standard Python behaviour.

Implementation

  • The new verification is implemented within validate_against_signature, which covers the other signature checks. It receives the list of positional-only parameter names and the list of those invalidly received as keyword arguments.
  • inspect.signature is interrogated to identify positional-only parameters, since inspect.getfullargspec does not distinguish them (they are lumped into spec.args).
  • In wrapped_f, any keyword input matching a positional-only parameter name is removed from kwargs up front: it never binds to the parameter. If the signature has no **kwargs it is reported via a dedicated "Got positional-only argument(s) passed as keyword argument(s): ..." error; otherwise it is absorbed into **kwargs (and a required positional-only parameter not also passed positionally is correctly reported as missing).
  • The coerce/parse logic of the reconstruction loop has been factored into a new apply_metadata helper so that values absorbed by **kwargs under a positional-only name are validated, coerced and parsed consistently with other **kwargs inputs.

Behaviour examples

@parse
def f(a: int, b: str, /, c: int): ...

f(1, "x", 3)        # ok
f(1, "x", c=3)      # ok (c is positional-or-keyword)
f(a=1, b="x", c=3)  # InputsError: positional-only args passed as keyword

@parse
def g(a: int, /, **kwargs: int): ...

g(1, a=2, b=3)      # ok -> a=1, kwargs={'a': 2, 'b': 3}  (matches plain Python)
g(a=2)              # InputsError: missing positional argument 'a'

Documentation

The "does not currently support positional-only arguments" note has been removed from both the module docstring and the README.

Testing

  • Added tests covering: passing positional-only args positionally (incl. coerce/parse), the single- and multiple-argument keyword-rejection errors, interaction with other signature errors, **kwargs absorption (with and without a default), the required-but-missing case, validation/coercion of absorbed values, and a decorated method.
  • pytest: all 35 tests pass.
  • ruff check / ruff format: clean.
  • mypy: no new errors introduced (count is one lower than the pre-existing baseline).

https://claude.ai/code/session_015Ci5jP93WGZiN2UE3SXyoh


Generated by Claude Code

claude and others added 3 commits June 2, 2026 22:32
Previously valimp ignored any '/' in a decorated function's signature,
allowing intended positional-only arguments to be passed as keyword
arguments.

valimp now enforces positional-only arguments: a positional-only argument
can only be passed positionally. Passing it by keyword raises an
`InputsError`, unless the signature provides for **kwargs, in which case
the keyword input is absorbed by **kwargs (consistent with standard Python
behaviour).

The verification is implemented within `validate_against_signature`, which
covers the other signature checks. `inspect.signature` is interrogated to
identify positional-only parameters (these are not distinguished by
`inspect.getfullargspec`). The coerce/parse logic of the reconstruction
loop has been factored into a new `apply_metadata` helper so that values
absorbed by **kwargs under a positional-only name are validated, coerced
and parsed consistently with other **kwargs inputs.

Tests added to cover the new verification and the **kwargs absorption
behaviour. The corresponding 'does not currently support' note has been
removed from the module docstring and the README.
Doc and other non-functional revisions.
When a positional-only argument is received both positionally and as a
keyword absorbed by **kwargs, and both values are invalid against their
respective type annotations, both errors are now reported. Previously the
absorbed value's error overrode the positional argument's error as both
were keyed by the same parameter name in the consolidated errors mapping.

The absorbed value's error is filed under a disambiguated key (e.g.
"a (**kwargs)") only when the same name already carries an error from a
positionally-received value, so ordinary cases are unaffected.

Test added for the doubly-invalid collision case.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants