Advertisement
EditorRUS

Python transforms

May 6th, 2018
169
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.40 KB | None | 0 0
  1. def transform_args(*arg_lambdas, **kwarg_lambdas):
  2.     """Construct a decorator that attempts to transform arguments before calling the function.
  3.  
  4.    Arguments:
  5.        *arg_lambdas (Union[Ellipsis, Callable, List[Callable]]):
  6.            Transformers for positional arguments.
  7.  
  8.            type: Ellipsis:
  9.                Value remains unchanged (identity function).
  10.  
  11.            type: Callable -> List[Callable]
  12.  
  13.            type: List[Callable]
  14.                Reduce is used on the chain with this value being initial.
  15.  
  16.        **kwarg_lambdas (Union[Callable, List[Callable]]):
  17.            For keyword arguments.
  18.  
  19.            Same rules apply as for arg_lambdas, but Ellipsis is not allowed for type.
  20.  
  21.            If key is str like @1, @2, @kwarg etc. and value is str, then if ValueError is raised by function,
  22.            it will be reraised with this message.
  23.  
  24.    Notes:
  25.        kwarg lambda is only applied when the function is explicitly called with this keyword argument
  26.        Initial kwarg values are NOT transformed.
  27.  
  28.    Raises:
  29.        ValueError:
  30.            If ValueError is raised during transform by one of the functions.
  31.    """
  32.     POSITIONAL_TRANSFORM_ERROR = """Positional transform failed
  33.    Func: {func}
  34.    Arg#: {arg_pos}
  35.    Value: {arg_value}
  36.    Value type: {value_type}
  37.    Chain#: {chain_pos}
  38.    Message: {message}"""
  39.     KEYWORD_TRANSFORM_ERROR = """Keyword transform failed
  40.    Func: {func}
  41.    Kwarg: {kwarg}
  42.    Value: {kwarg_value}
  43.    Value type: {value_type}
  44.    Chain#: {chain_pos}
  45.    Message: {message}"""
  46.  
  47.     def decorator(func):
  48.         from functools import wraps
  49.         from itertools import count
  50.         @wraps(func)
  51.         def transformer(*args, **kwargs):
  52.             nonlocal arg_lambdas
  53.  
  54.             modified_args = []
  55.             modified_kwargs = {}
  56.  
  57.             if len(args) > len(arg_lambdas):
  58.                 arg_lambdas = arg_lambdas.copy()
  59.                 arg_lambdas += [Ellipsis] * (len(args) - len(arg_lambdas))
  60.  
  61.             for arg_pos, arg_value, transform_source in zip(count(), args, arg_lambdas):
  62.                 if transform_source is Ellipsis:
  63.                     modified_args.append(arg_value)
  64.                 elif callable(transform_source):
  65.                     transform_source = (transform_source,)
  66.                 for chain_pos, transform_func in enumerate(transform_source):
  67.                     try:
  68.                         arg_value = transform_func(arg_value)
  69.                     except ValueError as E:
  70.                         possible_name = '@' + str(arg_pos)
  71.                         message = kwarg_lambdas.get(possible_name, E.args[0] if len(E.args) else "")
  72.                         message = POSITIONAL_TRANSFORM_ERROR.format(func=func.__name__,
  73.                                                                     arg_pos=arg_pos,
  74.                                                                     arg_value=arg_value,
  75.                                                                     value_type=type(arg_value),
  76.                                                                     chain_pos=chain_pos,
  77.                                                                     message=message)
  78.                         raise ValueError(message)
  79.                 modified_args.append(arg_value)
  80.  
  81.             for kwarg_name, kwarg_value in kwargs.items():
  82.                 transform_source = kwarg_lambdas.get(kwarg_name, Ellipsis)
  83.                 if transform_source is Ellipsis:
  84.                     modified_kwargs[kwarg_name] = kwarg_value
  85.                 elif callable(transform_source):
  86.                     transform_source = (transform_source,)
  87.                 for chain_pos, transform_func in zip(count(0), transform_source):
  88.                     try:
  89.                         kwarg_value = transform_func(kwarg_value)
  90.                     except ValueError as E:
  91.                         possible_name = '@' + kwarg_name
  92.                         message = E.args[0] if len(E.args) else ""
  93.                         if possible_name in kwarg_lambdas:
  94.                             message = kwarg_lambdas.get(possible_name)
  95.                         message = KEYWORD_TRANSFORM_ERROR.format(func=func.__name__,
  96.                                                                  kwarg=kwarg_name,
  97.                                                                  kwarg_value=kwarg_value,
  98.                                                                  value_type=type(kwarg_value),
  99.                                                                  chain_pos=chain_pos,
  100.                                                                  message=message)
  101.                         raise ValueError(message)
  102.                 modified_kwargs[kwarg_name] = kwarg_value
  103.             return func(*modified_args, **modified_kwargs)
  104.  
  105.         return transformer
  106.  
  107.     return decorator
  108.  
  109.  
  110. def transform_return(return_transform, return_message=Ellipsis):
  111.     """A limited version of transform_args that only accepts one transform func and applies it
  112.    to return value of the function
  113.  
  114.    Arguments:
  115.        transform (Union[Callable, List[Callable]):
  116.            type: Callable -> List[Callable]
  117.  
  118.            type: List[Callable]
  119.                Reduce is used on the chain with return value being initial
  120.  
  121.        return_message (Ellipsis, str):
  122.            If not Ellipsis, then if ValueError is raised by the function, it will be reraised with this message
  123.  
  124.    Raises:
  125.        ValueError:
  126.            If ValueError is raised during transform.
  127. """
  128.  
  129.     RETURN_TRANSFORM_ERROR = """Return transform failed
  130.    Func: {func}
  131.    Value: {return_value}
  132.    Message: {message}"""
  133.  
  134.     def decorator(func):
  135.         from functools import wraps
  136.         @wraps(func)
  137.         def transformer(*args, **kwargs):
  138.             value = func(*args, **kwargs)
  139.             try:
  140.                 value = return_transform(value)
  141.             except ValueError as E:
  142.                 message = E.args[0] if len(E.args) else ''
  143.                 if return_message is not Ellipsis:
  144.                     message = return_message
  145.                 message = RETURN_TRANSFORM_ERROR.format(func=func.__name__,
  146.                                                         return_value=value,
  147.                                                         message=message)
  148.                 raise ValueError(message)
  149.             return value
  150.  
  151.         return transformer
  152.  
  153.     return decorator
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement