Source code for takes.takes

"""Main module."""
from functools import wraps
from typing import Any, Dict, Tuple
from inspect import getfullargspec, signature


[docs]class ObjectConversionError(Exception): pass
[docs]class takes: def __init__(self, klass, name=None): self.klass = klass self.name = name def __call__(self, func): spec = getfullargspec(func) if self.name is not None and self.name not in spec.args + spec.kwonlyargs: raise ValueError( f"Parameter name '{self.name}' not a valid argument " "to decorated function." ) @wraps(func) def wrapper(*args, **kwargs): args, kwargs = self._replace_object(func, args, kwargs) return func(*args, **kwargs) # This lets us stack @takes decorators, # because @wraps does not entirely preserve the signature. wrapper.__signature__ = signature(func) return wrapper def _replace_object(self, func, args, kwargs) -> Tuple[Tuple, Dict]: # First positional argument if we don't have a name if self.name is None: return (self._convert_object(args[0]),) + args[1:], kwargs if self.name in kwargs: kwargs[self.name] = self._convert_object(kwargs[self.name]) return args, kwargs spec = getfullargspec(func) idx = spec.args.index(self.name) try: obj = self._convert_object(args[idx]) args = self._replace_obj_at_index(args, obj, index=idx) except IndexError: # We get here if the decorated function was called # where `name` is set to a position-or-kwarg, or kwarg-only, # but the function was called with an incorrect kwarg. # Python will do the right thing if we just let it call the # decorated function without replacing anything. pass return args, kwargs def _replace_obj_at_index(self, items: Tuple, obj: Any, index: int): return items[:index] + (obj,) + items[index + 1 :] def _convert_object(self, obj): if isinstance(obj, self.klass): return obj try: return self.klass(**obj) except Exception as exc: raise ObjectConversionError( f"Error converting {type(obj)} to {self.klass}: {obj}" ) from exc