From f53101e60c1ed9f1dc1287c30c90b00a80ddbe0b Mon Sep 17 00:00:00 2001 From: olen Date: Thu, 14 May 2026 16:28:24 +0200 Subject: [PATCH] fix: preserve wrapped method metadata in require_authentication decorator `require_authentication` was missing `@functools.wraps(func)` on its inner `wrapper`, so every decorated method (most of `Spond.*`) lost its `__name__`, `__doc__`, `__qualname__`, `__wrapped__`, and real signature. The effect on the published pdoc site was severe: every decorated method rendered as `async def get_xxx(self, *args, **kwargs):` with the decorator's source listing inline, instead of the method's real signature and docstring. The recently-expanded docstrings (#237/#241) existed in source but were never read by `inspect`-based tooling. Fix: - Import `functools`. - Apply `@functools.wraps(func)` to the inner `wrapper`. Regression tests in a new `TestRequireAuthenticationDecorator` class lock in the three properties that need to survive: signature, docstring, and `__name__`. Each would fail without `functools.wraps`. --- spond/base.py | 2 ++ tests/test_spond.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/spond/base.py b/spond/base.py index 872e4c3..16bc1e3 100644 --- a/spond/base.py +++ b/spond/base.py @@ -8,6 +8,7 @@ Not intended to be instantiated directly — use a subclass. """ +import functools from abc import ABC from collections.abc import Callable @@ -65,6 +66,7 @@ def require_authentication(func: Callable): client is not yet authenticated. On `AuthenticationError`, closes the underlying aiohttp session before re-raising.""" + @functools.wraps(func) async def wrapper(self, *args, **kwargs): if not self.token: try: diff --git a/tests/test_spond.py b/tests/test_spond.py index 7592c30..890b8c5 100644 --- a/tests/test_spond.py +++ b/tests/test_spond.py @@ -450,3 +450,32 @@ async def test_login__error_response_raises(self, mock_post) -> None: with pytest.raises(AuthenticationError): await s.login() assert s.token is None + + +class TestRequireAuthenticationDecorator: + """The `require_authentication` decorator must preserve the wrapped + method's metadata (signature, docstring, name) so `inspect`-based + tools — pdoc, IDE help, tab completion — see the real method + rather than the wrapper's `(*args, **kwargs)` shim. + """ + + def test_decorator_preserves_signature(self) -> None: + """Decorated methods must expose their real parameter list.""" + import inspect + + # `get_posts` is decorated and has a distinctive signature + params = list(inspect.signature(Spond.get_posts).parameters) + assert params == ["self", "group_id", "max_posts", "include_comments"] + + def test_decorator_preserves_docstring(self) -> None: + """Decorated methods must expose their own docstring, not the + wrapper's.""" + import inspect + + doc = inspect.getdoc(Spond.get_profile) or "" + # Wrapper docstring would start with 'Decorator that...' if leaked. + assert "Retrieve the authenticated user's profile." in doc + + def test_decorator_preserves_name(self) -> None: + """`__name__` must be the method's, not 'wrapper'.""" + assert Spond.get_events.__name__ == "get_events"