tokencrawler/.venv/lib/python3.9/site-packages/apischema/methods.py
2022-03-17 22:16:30 +01:00

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)