55 lines
2.4 KiB
Python
55 lines
2.4 KiB
Python
|
|
import inspect
|
||
|
|
from collections.abc import Callable
|
||
|
|
from typing import Any, TypeVar, get_type_hints
|
||
|
|
|
||
|
|
T = TypeVar("T")
|
||
|
|
R = TypeVar("R")
|
||
|
|
|
||
|
|
|
||
|
|
def create_call_wrapper(func: Callable[..., R], request_type: type[T]) -> Callable[[T], R]:
|
||
|
|
"""
|
||
|
|
Create a wrapper function that knows how to call func with the request object.
|
||
|
|
|
||
|
|
Returns a wrapper function that takes the request and calls func appropriately.
|
||
|
|
|
||
|
|
The wrapper handles three calling patterns:
|
||
|
|
1. Positional-only parameter typed as request_type (no default): func(req)
|
||
|
|
2. Positional/keyword parameter typed as request_type (no default): func(**{param_name: req})
|
||
|
|
3. No request parameter or parameter with default: func()
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
sig = inspect.signature(func)
|
||
|
|
type_hints = get_type_hints(func)
|
||
|
|
except (ValueError, TypeError, NameError): # pragma: no cover
|
||
|
|
return lambda _: func()
|
||
|
|
|
||
|
|
# Check for positional-only parameter typed as request_type
|
||
|
|
for param_name, param in sig.parameters.items():
|
||
|
|
if param.kind == inspect.Parameter.POSITIONAL_ONLY:
|
||
|
|
param_type = type_hints.get(param_name)
|
||
|
|
if param_type == request_type: # pragma: no branch
|
||
|
|
# Check if it has a default - if so, treat as old style
|
||
|
|
if param.default is not inspect.Parameter.empty: # pragma: no cover
|
||
|
|
return lambda _: func()
|
||
|
|
# Found positional-only parameter with correct type and no default
|
||
|
|
return lambda req: func(req)
|
||
|
|
|
||
|
|
# Check for any positional/keyword parameter typed as request_type
|
||
|
|
for param_name, param in sig.parameters.items():
|
||
|
|
if param.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY): # pragma: no branch
|
||
|
|
param_type = type_hints.get(param_name)
|
||
|
|
if param_type == request_type:
|
||
|
|
# Check if it has a default - if so, treat as old style
|
||
|
|
if param.default is not inspect.Parameter.empty: # pragma: no cover
|
||
|
|
return lambda _: func()
|
||
|
|
|
||
|
|
# Found keyword parameter with correct type and no default
|
||
|
|
# Need to capture param_name in closure properly
|
||
|
|
def make_keyword_wrapper(name: str) -> Callable[[Any], Any]:
|
||
|
|
return lambda req: func(**{name: req})
|
||
|
|
|
||
|
|
return make_keyword_wrapper(param_name)
|
||
|
|
|
||
|
|
# No request parameter found - use old style
|
||
|
|
return lambda _: func()
|