From 9fd60025f853a9cd179a345487187a0a389ebecb Mon Sep 17 00:00:00 2001 From: strawgate Date: Tue, 12 May 2026 22:46:54 -0500 Subject: [PATCH] fix: preserve annotation metadata when cloning wrapped partials Addresses Codex P1: when reconstructing a partial to strip __wrapped__, copy over functools.WRAPPER_ASSIGNMENTS (__module__, __qualname__, etc.) so deferred annotation resolution works correctly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- fastmcp_slim/fastmcp/utilities/callable_utils.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fastmcp_slim/fastmcp/utilities/callable_utils.py b/fastmcp_slim/fastmcp/utilities/callable_utils.py index c572dc6ee..567434511 100644 --- a/fastmcp_slim/fastmcp/utilities/callable_utils.py +++ b/fastmcp_slim/fastmcp/utilities/callable_utils.py @@ -66,7 +66,21 @@ def prepare_callable(fn: Callable[..., Any]) -> Callable[..., Any]: # Strip __wrapped__ from partials so Pydantic sees the partial's own # signature with bound args removed, not the original function's signature. if isinstance(fn, functools.partial) and hasattr(fn, "__wrapped__"): + old = fn fn = functools.partial(fn.func, *fn.args, **fn.keywords) + # Preserve annotation metadata copied by functools.update_wrapper + # (e.g. __module__, __qualname__) needed for deferred annotation + # resolution when `from __future__ import annotations` is active. + for attr in functools.WRAPPER_ASSIGNMENTS: + try: + val = getattr(old, attr) + except AttributeError: + pass + else: + try: + setattr(fn, attr, val) + except AttributeError: + pass # Callable classes (not routines, not partials) → unwrap to __call__ if not inspect.isroutine(fn) and not isinstance(fn, functools.partial):