Skip to content

Support positional-only arguments in the strict sense#30

Merged
maread99 merged 6 commits into
mainfrom
claude/friendly-wright-WewuG
Jun 3, 2026
Merged

Support positional-only arguments in the strict sense#30
maread99 merged 6 commits into
mainfrom
claude/friendly-wright-WewuG

Conversation

@maread99

@maread99 maread99 commented Jun 2, 2026

Copy link
Copy Markdown
Owner

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.

Behaviour

@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: Got positional-only argument as keyword argument (and signature makes no provision for **kwargs that would otherwise receive it): 'a'.

@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 1 positional argument: 'a'.

Implementation

  • The check lives in validate_against_signature alongside the other signature checks. It receives the positional-only parameter names and those invalidly received as keyword arguments, reporting the latter via a dedicated "Received positional-only argument(s) as keyword argument(s): ..." error.
  • 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 (held as kwarg_name_as_posonly): it never binds to the parameter. With no **kwargs it raises; otherwise it is absorbed into **kwargs, and a required positional-only parameter not also passed positionally is still correctly reported as missing.
  • Values absorbed by **kwargs under a positional-only name are validated, coerced and parsed against the **kwargs annotation. Their errors are filed under a disambiguated key (e.g. "a (**kwargs)") so an absorbed value's error never silently overrides the positionally-received argument's error — both are reported when both are invalid.
  • The reconstruction loop's coerce/parse logic was factored into a reusable apply_coerce_and_parser helper so absorbed **kwargs values are handled consistently with other inputs.

Documentation

  • The "does not currently support positional-only arguments" note was removed from the module docstring and README.
  • The README public_function example and the tutorial's "Signature validation" section now demonstrate positional-only support (and the new keyword-rejection error), with positional-only listed under the README's "What's supported".

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 the doubly-invalid name-collision case; and a decorated method.
  • pytest: all tests pass. ruff check / ruff format: clean. mypy: no new errors.

Generated by Claude, manually fully reviewed and notably revised.
Generated by Claude Code

claude and others added 6 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.
Revisions to doc. Other changes non-functional with important exception of changes to
error messages.
Surface strict positional-only argument support within the existing
signature-verification examples of the README and tutorial, preserving
every current demonstration.

- README public_function: mark 'a' as positional-only (ahead of '/'),
  pass it positionally in the invalid-types call, and rework the
  signature-mismatch call to show the new 'positional-only argument
  passed as keyword argument' error while relocating the 'got multiple
  values' demonstration to parameter 'c'. Add a 'What's supported' bullet.
- tutorial Signature validation section: make pf's 'a' positional-only,
  add 'c' to retain the 'got multiple values' demo, and refresh the
  pasted error outputs accordingly.

https://claude.ai/code/session_015Ci5jP93WGZiN2UE3SXyoh
Doc changes and one change to an error message.
@maread99 maread99 added enhancement New feature or request documentation Improvements or additions to documentation tests All things tests labels Jun 3, 2026
@maread99 maread99 merged commit 2992fe8 into main Jun 3, 2026
8 checks passed
@maread99 maread99 deleted the claude/friendly-wright-WewuG branch June 3, 2026 21:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request tests All things tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants