Skip to content

@prepare drops extra positional and keyword arguments although its docstring promises to forward them #454

Description

@JarryShaw

The @prepare decorator's own docstring promises the decorated function receives *args, **kwargs, but its wrapper forwards neither. Anything a caller passes beyond the four known parameters is silently discarded — no error, no warning.

The documented contract

pcapkit/utilities/decorators.py, in prepare's docstring:

Note:
    The decorated function should have following signature::

        func(cls: 'typing.Type[pcapkit.protocols.schema.schema.Schema]',
             data: 'bytes | typing.IO[bytes]',
             length: 'Optional[int],
             packet: 'Optional[dict[str, Any]',
             *args: 'typing.Any', **kwargs: 'Any') -> '...Schema'

What actually happens

The wrapper calls the decorated function with exactly four arguments:

schema = func(cls, data, length, packet)

so the *args and **kwargs the docstring tells implementors to accept can never be populated.

Reproduction, on e2d8ed6d1

@schema_final
class Probe(Schema):
    a: 'int' = UInt8Field()
    b: 'int' = UInt8Field()

Probe.unpack(b'\x07\x08', 2, {}, 'EXTRA_POSITIONAL', extra_kw='EXTRA_KW')
-> a=7 b=8

Both extras vanish. No TypeError for the unexpected positional, no warning for the unused keyword — which is the part that makes this a defect rather than a limitation: a caller passing a misspelled or unsupported argument gets a successful parse and no signal that their argument did nothing.

There is a second, narrower case of the same shape: passing length both positionally and by keyword silently keeps the positional and drops the keyword.

Pre-existing, and not introduced by the @prepare fix

main's wrapper reads args[2]/args[3] unconditionally and also calls func(cls, data, length, packet), so the drop is identical before and after PR #450, which fixes a different fault in the same lines (issue #444 — the optional parameters being mandatory and positional). Confirmed by diffing: #450 changes only how length and packet are obtained, not how func is called.

Fix

Either forward what the docstring promises:

schema = func(cls, data, length, packet, *args[4:], **kwargs)

or, if extras are genuinely not wanted, delete the *args, **kwargs from the documented signature and let Python raise TypeError on an unexpected argument, which is the behaviour a caller would expect. The second is arguably better: nothing in the tree passes extras today, and silently accepting them is what hides a typo.

Whichever way, note that kwargs is currently consumed by kwargs.pop('length'/'packet', …) on #450's branch, so a forwarding fix needs to be careful not to re-forward those two.

Provenance

Found by the reviewer of PR #450 while checking that PR's argument binding, and flagged as out of scope for it. Verified and reproduced independently before filing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugIssues reporting a defect (set by the bug report template; a default, not an assessment)

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions