Completed
Push — master ( 1c1494...590d9e )
by Ramon
01:54
created

jsons.decorators._map_args()   A

Complexity

Conditions 5

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 11
nop 5
dl 0
loc 14
rs 9.3333
c 0
b 0
f 0
1
"""
2
This module contains decorators that facilitate the `jsons` functions in an
3
alternative fashion.
4
"""
5
import warnings
6
from inspect import signature, Parameter, isawaitable, iscoroutinefunction
7
from jsons import JsonSerializable, dump, load, loads, loadb, dumps, dumpb
8
9
10
def loaded(parameters=True, returnvalue=True, fork_inst=JsonSerializable,
11
           loader=load, **kwargs):
12
    """
13
    Return a decorator that can call `jsons.load` upon all parameters and the
14
    return value of the decorated function.
15
16
17
    **Example**:
18
19
    >>> from datetime import datetime
20
    >>> @loaded()
21
    ... def func(arg: datetime) -> datetime:
22
    ...     # arg is now of type datetime.
23
    ...     return '2018-10-04T21:57:00Z'  # This will become a datetime.
24
    >>> res = func('2018-10-04T21:57:00Z')
25
    >>> type(res).__name__
26
    'datetime'
27
28
    :param parameters: determines whether parameters should be taken into
29
    account.
30
    :param returnvalue: determines whether the return value should be taken
31
    into account.
32
    :param fork_inst: if given, it uses this fork of ``JsonSerializable``.
33
    :param kwargs: any keyword arguments that should be passed on to
34
    `jsons.load`
35
    :param loader: the load function which must be one of (``load``,
36
    ``loads``, ``loadb``)
37
    :return: a decorator that can be placed on a function.
38
    """
39
    assert loader in (load, loads, loadb)
40
    return _get_decorator(parameters, returnvalue, fork_inst, loader, kwargs)
41
42
43
def dumped(parameters=True, returnvalue=True, fork_inst=JsonSerializable,
44
           dumper=dump, **kwargs):
45
    """
46
    Return a decorator that can call `jsons.dump` upon all parameters and the
47
    return value of the decorated function.
48
49
50
    **Example**:
51
52
    >>> from datetime import datetime
53
    >>> @dumped()
54
    ... def func(arg):
55
    ...     # arg is now of type str.
56
    ...     return datetime.now()
57
    >>> res = func(datetime.now())
58
    >>> type(res).__name__
59
    'str'
60
61
    :param parameters: determines whether parameters should be taken into
62
    account.
63
    :param returnvalue: determines whether the return value should be taken
64
    into account.
65
    :param fork_inst: if given, it uses this fork of ``JsonSerializable``.
66
    :param kwargs: any keyword arguments that should be passed on to
67
    `jsons.dump`
68
    :param dumper: the dump function which must be one of (``dump``,
69
    ``dumps``, ``dumpb``)
70
    :return: a decorator that can be placed on a function.
71
    """
72
    assert dumper in (dump, dumps, dumpb)
73
    return _get_decorator(parameters, returnvalue, fork_inst, dumper, kwargs)
74
75
76
def _get_params_sig(args, func):
77
    sig = signature(func)
78
    params = sig.parameters
79
    param_names = [param_name for param_name in params]
80
    result = [(args[i], params[param_names[i]]) for i in range(len(args))]
81
    return result
82
83
84
def _map_args(args, decorated, fork_inst, mapper, mapper_kwargs):
85
    params_sig = _get_params_sig(args, decorated)
86
    new_args = []
87
    for arg, sig in params_sig:
88
        if sig.name in ('self', 'cls') and hasattr(arg, decorated.__name__):
89
            # `decorated` is a method and arg is either `self` or `cls`.
90
            new_arg = arg
91
        else:
92
            cls = sig.annotation if sig.annotation != Parameter.empty else None
93
            new_arg = mapper(arg, cls=cls, fork_inst=fork_inst,
94
                             **mapper_kwargs)
95
96
        new_args.append(new_arg)
97
    return new_args
98
99
100
def _map_returnvalue(returnvalue, decorated, fork_inst, mapper, mapper_kwargs):
101
    return_annotation = signature(decorated).return_annotation
102
    cls = return_annotation if return_annotation != Parameter.empty else None
103
    result = mapper(returnvalue, cls=cls, fork_inst=fork_inst, **mapper_kwargs)
104
    return result
105
106
107
def _run_decorated(decorated, mapper, fork_inst, args, kwargs, mapper_kwargs):
0 ignored issues
show
best-practice introduced by
Too many arguments (6/5)
Loading history...
108
    new_args = args
109
    if mapper:
110
        new_args = _map_args(args, decorated, fork_inst, mapper, mapper_kwargs)
111
    result = decorated(*new_args, **kwargs)
112
    return result
113
114
115
def _get_decorator(parameters, returnvalue, fork_inst, mapper, mapper_kwargs):
116
    def _decorator(decorated):
117
        def _wrapper(*args, **kwargs):
118
            result = _run_decorated(decorated, mapper if parameters else None,
119
                                    fork_inst, args, kwargs, mapper_kwargs)
120
            if returnvalue:
121
                result = _map_returnvalue(result, decorated, fork_inst, mapper,
122
                                          mapper_kwargs)
123
            return result
124
125
        async def _async_wrapper(*args, **kwargs):
126
            result = _run_decorated(decorated, mapper if parameters else None,
127
                                    fork_inst, args, kwargs, mapper_kwargs)
128
            if isawaitable(result):
129
                result = await result
130
            if returnvalue:
131
                result = _map_returnvalue(result, decorated, fork_inst, mapper,
132
                                          mapper_kwargs)
133
            return result
134
        if isinstance(decorated, staticmethod):
135
            warnings.warn('You cannot decorate a static- or classmethod. '
136
                          'You can still obtain the desired behavior by '
137
                          'decorating your method first and then place '
138
                          '@staticmethod/@classmethod on top (switching the '
139
                          'order).')
140
            raise Exception('Cannot decorate a static- or classmethod.')
141
        if isinstance(decorated, type):
142
            raise Exception('Cannot decorate a class.')
143
        return _async_wrapper if iscoroutinefunction(decorated) else _wrapper
144
    return _decorator
145