You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(wrapper): stop Wrap mutating the ingress it is given (#84)
`preserve_signature` wrote `__signature__`/`__annotations__` onto the ingress object.
A decorator normally defines one ingress and reuses it for every function it wraps, so
that write corrupted the caller's function and made every later Wrap built from the same
ingress advertise the FIRST wrapped function's signature -- while still executing
correctly, so nothing surfaced the lie:
def shared_ingress(*args, **kwargs): return args, kwargs
def f(a: int) -> int: ...
def g(x: str, y: str) -> str: ...
Wrap(f, ingress=shared_ingress)
signature(shared_ingress) # (a: int) -> int <- caller's function mutated
Sig(Wrap(g, ingress=shared_ingress)) # (a: int) -> str <- wrong, but g still works
That is the worst shape of bug for this package: silent, and wrong in exactly the
introspection i2 exists to provide (meshed builds DAGs from these signatures).
Preserving means *the wrapper* presents func's interface, so read the signature from
func and never write it onto the ingress. Defaults now come from the same source as the
signature, so the two cannot disagree.
Also, while in here:
- `_get_return_annotation` had `x if x is not Parameter.empty else empty` three times.
`empty IS Parameter.empty`, so each was a no-op wrapped around a duplicated fallback.
Reduced to one "egress wins if annotated, else func" rule with doctests, including the
case that made the dead branch look meaningful (an unannotated func gives `empty`,
not `None` -- `None` is a real annotation meaning "returns None").
- Extracted `_is_generic_signature`, the actual predicate `'auto'` turns on, with
doctests for the near-misses ((*a) alone, (x, *a, **kw)).
- Dropped the unused `func` parameter from `_should_preserve_signature`.
- Named the `'auto'` sentinel `AUTO_PRESERVE_SIGNATURE` so it has one definition.
- Converted the numpydoc docstrings to the `:param:` style used everywhere else here.
Public API unchanged. Regression tests fail on the old code and pass on the new.
i2: 470 passed. meshed (biggest dependent): 60 passed. crude/o failures are pre-existing
and byte-identical with and without this change.
Claude-Session: https://claude.ai/code/session_01GsUw8ey8KikzNFQ1UaWWia
0 commit comments