137 lines
4.2 KiB
Python
137 lines
4.2 KiB
Python
import inspect
|
|
from functools import wraps
|
|
from inspect import signature
|
|
from types import FunctionType
|
|
from typing import Callable, Generic, Optional, Type, Union, cast
|
|
|
|
from apischema.typing import get_type_hints
|
|
from apischema.utils import PREFIX, T, get_origin_or_type2
|
|
|
|
MethodOrProperty = Union[Callable, property]
|
|
|
|
|
|
def _method_location(method: MethodOrProperty) -> Optional[Type]:
|
|
if isinstance(method, property):
|
|
assert method.fget is not None
|
|
method = method.fget
|
|
while hasattr(method, "__wrapped__"):
|
|
method = method.__wrapped__ # type: ignore
|
|
assert isinstance(method, FunctionType)
|
|
global_name, *class_path = method.__qualname__.split(".")[:-1]
|
|
if global_name not in method.__globals__:
|
|
return None
|
|
location = method.__globals__[global_name]
|
|
for attr in class_path:
|
|
if hasattr(location, attr):
|
|
location = getattr(location, attr)
|
|
else:
|
|
break
|
|
return location
|
|
|
|
|
|
def is_method(method: MethodOrProperty) -> bool:
|
|
"""Return if the function is method/property declared in a class"""
|
|
return (
|
|
isinstance(method, property)
|
|
and method.fget is not None
|
|
and is_method(method.fget)
|
|
) or (
|
|
isinstance(method, FunctionType)
|
|
and method.__name__ != method.__qualname__
|
|
and isinstance(_method_location(method), (type, type(None)))
|
|
and next(iter(inspect.signature(method).parameters), None) == "self"
|
|
)
|
|
|
|
|
|
def method_class(method: MethodOrProperty) -> Optional[Type]:
|
|
cls = _method_location(method)
|
|
return cls if isinstance(cls, type) else None
|
|
|
|
|
|
METHOD_WRAPPER_ATTR = f"{PREFIX}method_wrapper"
|
|
|
|
|
|
def method_wrapper(method: MethodOrProperty, name: str = None) -> Callable:
|
|
if isinstance(method, property):
|
|
assert method.fget is not None
|
|
name = name or method.fget.__name__
|
|
|
|
@wraps(method.fget)
|
|
def wrapper(self):
|
|
return getattr(self, name)
|
|
|
|
else:
|
|
if hasattr(method, METHOD_WRAPPER_ATTR):
|
|
return method
|
|
name = name or method.__name__
|
|
|
|
if list(signature(method).parameters) == ["self"]:
|
|
|
|
@wraps(method)
|
|
def wrapper(self):
|
|
return getattr(self, name)()
|
|
|
|
else:
|
|
|
|
@wraps(method)
|
|
def wrapper(self, *args, **kwargs):
|
|
return getattr(self, name)(*args, **kwargs)
|
|
|
|
setattr(wrapper, METHOD_WRAPPER_ATTR, True)
|
|
return wrapper
|
|
|
|
|
|
class MethodWrapper(Generic[T]):
|
|
def __init__(self, method: T):
|
|
self._method = method
|
|
|
|
def getter(self, func):
|
|
self._method = self._method.getter(func)
|
|
return self
|
|
|
|
def setter(self, func):
|
|
self._method = self._method.setter(func)
|
|
return self
|
|
|
|
def deleter(self, func):
|
|
self._method = self._method.deleter(func)
|
|
return self
|
|
|
|
def __set_name__(self, owner, name):
|
|
setattr(owner, name, self._method)
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
raise RuntimeError("Method __set_name__ has not been called")
|
|
|
|
|
|
def method_registerer(
|
|
arg: Optional[Callable],
|
|
owner: Optional[Type],
|
|
register: Callable[[Callable, Type, str], None],
|
|
):
|
|
def decorator(method: MethodOrProperty):
|
|
if owner is None and is_method(method) and method_class(method) is None:
|
|
|
|
class Descriptor(MethodWrapper[MethodOrProperty]):
|
|
def __set_name__(self, owner, name):
|
|
super().__set_name__(owner, name)
|
|
register(method_wrapper(method), owner, name)
|
|
|
|
return Descriptor(method)
|
|
else:
|
|
owner2 = owner
|
|
if is_method(method):
|
|
if owner2 is None:
|
|
owner2 = method_class(method)
|
|
method = method_wrapper(method)
|
|
if owner2 is None:
|
|
try:
|
|
hints = get_type_hints(method)
|
|
owner2 = get_origin_or_type2(hints[next(iter(hints))])
|
|
except (KeyError, StopIteration):
|
|
raise TypeError("First parameter of method must be typed") from None
|
|
assert not isinstance(method, property)
|
|
register(cast(Callable, method), owner2, method.__name__)
|
|
return method
|
|
|
|
return decorator if arg is None else decorator(arg)
|