From 4f7916cce6c2d1dfecb1050effa9dda79b34cbaf Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:31:04 +0000 Subject: [PATCH] ParamsSpecifier: stop leaking instance annotations into the class __init__ updated self.__annotations__ in place, which is the class's dict once it exists (always, on 3.10), so every instance's annotations leaked into later ones. Master CI (3.10) failed on the ParamsSpecifier doctest after #50's test ran from_func on another function first. Co-Authored-By: Claude Opus 5.5 --- py2http/decorators.py | 7 +++---- py2http/tests/test_not_set_defaults.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/py2http/decorators.py b/py2http/decorators.py index dbfad1f..5bf3200 100644 --- a/py2http/decorators.py +++ b/py2http/decorators.py @@ -357,10 +357,9 @@ def __init__( self._dflt_default = _dflt_default self._kind = _kind - if hasattr(self, "__annotations__"): - self.__annotations__.update(_annotations) - else: - self.__annotations__ = _annotations + # A new dict on the instance: updating ``self.__annotations__`` in place would + # mutate the class's dict, leaking these annotations into every later instance. + self.__annotations__ = {**getattr(self, "__annotations__", {}), **_annotations} reserved = { "_annotations", diff --git a/py2http/tests/test_not_set_defaults.py b/py2http/tests/test_not_set_defaults.py index 5faab49..6c17a28 100644 --- a/py2http/tests/test_not_set_defaults.py +++ b/py2http/tests/test_not_set_defaults.py @@ -110,3 +110,15 @@ class K: specifier = ParamsSpecifier.from_func(method, _dflt_default="dflt") assert specifier._name_and_dflts == {"y": "dflt", "z": "dflt", "d": 1.5} + + +def test_params_specifier_annotations_do_not_leak_between_instances(): + """An instance's annotations used to be written into the class's own dict.""" + + def g(y: int, z: float = 0.0): ... + + def f(a, b: int): ... + + ParamsSpecifier.__annotations__ # as on 3.10, where the class dict then exists + ParamsSpecifier.from_func(g) + assert [p["name"] for p in ParamsSpecifier.from_func(f)()] == ["a", "b"]