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"]