def _delegate_method_dispatch(fn: Callable, attr: str, prop_name: str, skip: int = 2): adapter = wrapt.adapter_factory(partial(argspec_factory, fn=fn, skip=skip)) @wrapt.decorator(adapter=adapter) def delegate(wrapped, instance, args, kwargs): # first, get the correct method based on the dispatcher (can't use wrapped.dispatch..., since it's a function) # then, call the actual function return getattr(instance, attr)( getattr(instance, prop_name), prop_name, *args, **kwargs ) return delegate(fn)
def test_adapter_factory(self): def factory(wrapped): argspec = inspect.getargspec(wrapped) argspec.args.insert(0, 'arg0') return argspec @wrapt.decorator(adapter=wrapt.adapter_factory(factory)) def _wrapper_1(wrapped, instance, args, kwargs): return wrapped(*args, **kwargs) @_wrapper_1 def _function_1(arg1, arg2): pass argspec = inspect.getargspec(_function_1) self.assertEqual(argspec.args, ['arg0', 'arg1', 'arg2'])
def test_adapter_factory(self): def factory(wrapped): argspec = inspect.getargspec(wrapped) argspec.args.insert(0, 'arg0') return argspec @wrapt.decorator(adapter=wrapt.adapter_factory(factory)) def _wrapper_1(wrapped, instance, args, kwargs): return wrapped(*args, **kwargs) @_wrapper_1 def _function_1(arg1, arg2): pass argspec = inspect.getargspec(_function_1) self.assertEqual(argspec.args, ['arg0', 'arg1', 'arg2'])
def change_signature( force_annotation: Optional[Dict[str, Type]] = None, change_annotation: Optional[Dict[str, Type]] = None, namespace: Optional[Dict[str, Any]] = None, ) -> Callable[[Callable], Callable]: """Change function signature. Parameters ---------- force_annotation : dict, optional mapping of {argument_name: type_hint}. Will force any arguments named ``argument_name`` to be annotated as ``type_hint``. change_annotation : dict mapping of {current_hint: new_hint}. Will force any arguments currently annotated with a type of ``curent_hint`` to ``new_hint`. namespace : dict mapping of {name: object}. Namespaces required for importing any types declared as type annotations in the other arguments. Returns ------- adapter : callable The returned function may be provided to the `adapter` argument of the ``wrapt.decorator`` function. """ force_annotation = force_annotation or dict() change_annotation = change_annotation or dict() namespace = namespace or dict() if not namespace: for val in chain(change_annotation.values(), force_annotation.values()): mod = val.__module__.split(".")[0] namespace[mod] = import_module(mod) def argspec_factory(wrapped): sig = inspect.signature(wrapped) new_params = OrderedDict(sig.parameters.items()) for name, param in new_params.items(): if name in force_annotation: new_params[name] = param.replace(annotation=force_annotation[name]) new_sig = sig.replace(parameters=list(new_params.values())) _globals = {} exec(f"def _func{new_sig}: pass", namespace, _globals) return _globals["_func"] return wrapt.adapter_factory(argspec_factory)
def _delegate( *, prop_name: Optional[str] = None, return_type: Optional[Type] = None, skip: int = 2, ) -> Callable: @wrapt.decorator() def pass_through(wrapped, _instance, args, kwargs): return wrapped(*args, **kwargs) if prop_name is None: return pass_through adapter = wrapt.adapter_factory( partial(argspec_factory, skip=skip, return_type=return_type)) @wrapt.decorator(adapter=adapter) def wrapper(wrapped, instance, args, kwargs): return wrapped(getattr(instance, prop_name), prop_name, *args, **kwargs) return wrapper
def _inject_api_method( clazz: type, ) -> None: """ Create a decorator which does nothing except for modifying the function signature in the docstring. The function to be decorated must be a class method and is allowed only to have positional arguments, and variable keyword arguments (**kwargs). The resulting decorated function will containing only the positional arguments (including original type annotations) and possibly keyword only arguments. In this example signature might def fn(foo, bar, *, baz, quux), `baz` and `quux` are the keyword only arguments. Parameters ---------- clazz The class for which to create the query. Must not be abstract. Returns ------- :class:`callable` The decorator as described above. """ def argspec_factory(orig_fn: Callable) -> Callable: orig_params = inspect.signature(orig_fn).parameters # maintain the original signature if the subclass has overriden the method # this will lose the docstring of the original function parameters = { k: v for k, v in orig_params.items() if k != "cls" and v.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD) } annotations = { k: v for k, v in clazz._annotations().items() if k not in parameters } sig = inspect.signature(lambda _: _) sig = sig.replace( parameters=[Parameter("cls", kind=Parameter.POSITIONAL_ONLY)] + list(parameters.values()) + [ Parameter(k, kind=Parameter.KEYWORD_ONLY, annotation=a) for k, a in sorted(annotations.items()) ] + [Parameter("kwargs", kind=Parameter.VAR_KEYWORD)] ) # modify locals() for argspec factory import omnipath # noqa: F401 NoneType, pandas = type(None), pd exec( f"def adapter{sig}: pass".replace(" /,", ""), globals(), locals(), ) return locals()["adapter"] if not isinstance(clazz, type): raise TypeError( f"Expected `clazz` to be a type, found `{type(clazz).__name__}`." ) if isabstract(clazz): return @wrapt.decorator(adapter=wrapt.adapter_factory(argspec_factory)) def wrapper(wrapped, _instance, args, kwargs): return wrapped(*args, **kwargs) if hasattr(clazz, "get") and not hasattr(clazz.get, "__wrapped__"): # overriding in subclass clazz.get = wrapper(unwrap(clazz.get)) return clazz.get = wrapper(MethodType(_get_helper, clazz))