1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3"""
4Various utilities functions
5"""
6
7import sys
8
9from inspect import isclass
10try:
11    from inspect import getfullargspec as getargspec
12
13    _fullargspec_supported = True
14except ImportError:
15    _fullargspec_supported = False
16    from inspect import getargspec
17
18from .utils import is_iterable
19
20if sys.version_info < (3, 4, 0):  # pragma: no cover
21    def _constructor(class_):
22        """
23        Retrieves constructor from given class
24
25        :param class_:
26        :type class_: class
27        :return: constructor from given class
28        :rtype: callable
29        """
30        return class_.__init__
31else:  # pragma: no cover
32    def _constructor(class_):
33        """
34        Retrieves constructor from given class
35
36        :param class_:
37        :type class_: class
38        :return: constructor from given class
39        :rtype: callable
40        """
41        return class_
42
43
44def call(function, *args, **kwargs):
45    """
46    Call a function or constructor with given args and kwargs after removing args and kwargs that doesn't match
47    function or constructor signature
48
49    :param function: Function or constructor to call
50    :type function: callable
51    :param args:
52    :type args:
53    :param kwargs:
54    :type kwargs:
55    :return: sale vakye as default function call
56    :rtype: object
57    """
58    func = constructor_args if isclass(function) else function_args
59    call_args, call_kwargs = func(function, *args, ignore_unused=True, **kwargs)  # @see #20
60    return function(*call_args, **call_kwargs)
61
62
63def function_args(callable_, *args, **kwargs):
64    """
65    Return (args, kwargs) matching the function signature
66
67    :param callable: callable to inspect
68    :type callable: callable
69    :param args:
70    :type args:
71    :param kwargs:
72    :type kwargs:
73    :return: (args, kwargs) matching the function signature
74    :rtype: tuple
75    """
76    argspec = getargspec(callable_)  # pylint:disable=deprecated-method
77    return argspec_args(argspec, False, *args, **kwargs)
78
79
80def constructor_args(class_, *args, **kwargs):
81    """
82    Return (args, kwargs) matching the function signature
83
84    :param callable: callable to inspect
85    :type callable: Callable
86    :param args:
87    :type args:
88    :param kwargs:
89    :type kwargs:
90    :return: (args, kwargs) matching the function signature
91    :rtype: tuple
92    """
93    argspec = getargspec(_constructor(class_))  # pylint:disable=deprecated-method
94    return argspec_args(argspec, True, *args, **kwargs)
95
96
97def argspec_args(argspec, constructor, *args, **kwargs):
98    """
99    Return (args, kwargs) matching the argspec object
100
101    :param argspec: argspec to use
102    :type argspec: argspec
103    :param constructor: is it a constructor ?
104    :type constructor: bool
105    :param args:
106    :type args:
107    :param kwargs:
108    :type kwargs:
109    :return: (args, kwargs) matching the function signature
110    :rtype: tuple
111    """
112    if argspec.varkw:
113        call_kwarg = kwargs
114    else:
115        call_kwarg = dict((k, kwargs[k]) for k in kwargs if k in argspec.args)  # Python 2.6 dict comprehension
116    if argspec.varargs:
117        call_args = args
118    else:
119        call_args = args[:len(argspec.args) - (1 if constructor else 0)]
120    return call_args, call_kwarg
121
122
123if not _fullargspec_supported:
124    def argspec_args_legacy(argspec, constructor, *args, **kwargs):
125        """
126        Return (args, kwargs) matching the argspec object
127
128        :param argspec: argspec to use
129        :type argspec: argspec
130        :param constructor: is it a constructor ?
131        :type constructor: bool
132        :param args:
133        :type args:
134        :param kwargs:
135        :type kwargs:
136        :return: (args, kwargs) matching the function signature
137        :rtype: tuple
138        """
139        if argspec.keywords:
140            call_kwarg = kwargs
141        else:
142            call_kwarg = dict((k, kwargs[k]) for k in kwargs if k in argspec.args)  # Python 2.6 dict comprehension
143        if argspec.varargs:
144            call_args = args
145        else:
146            call_args = args[:len(argspec.args) - (1 if constructor else 0)]
147        return call_args, call_kwarg
148
149
150    argspec_args = argspec_args_legacy
151
152
153def ensure_list(param):
154    """
155    Retrieves a list from given parameter.
156
157    :param param:
158    :type param:
159    :return:
160    :rtype:
161    """
162    if not param:
163        param = []
164    elif not is_iterable(param):
165        param = [param]
166    return param
167
168
169def ensure_dict(param, default_value, default_key=None):
170    """
171    Retrieves a dict and a default value from given parameter.
172
173    if parameter is not a dict, it will be promoted as the default value.
174
175    :param param:
176    :type param:
177    :param default_value:
178    :type default_value:
179    :param default_key:
180    :type default_key:
181    :return:
182    :rtype:
183    """
184    if not param:
185        param = default_value
186    if not isinstance(param, dict):
187        if param:
188            default_value = param
189        return {default_key: param}, default_value
190    return param, default_value
191
192
193def filter_index(collection, predicate=None, index=None):
194    """
195    Filter collection with predicate function and index.
196
197    If index is not found, returns None.
198    :param collection:
199    :type collection: collection supporting iteration and slicing
200    :param predicate: function to filter the collection with
201    :type predicate: function
202    :param index: position of a single element to retrieve
203    :type index: int
204    :return: filtered list, or single element of filtered list if index is defined
205    :rtype: list or object
206    """
207    if index is None and isinstance(predicate, int):
208        index = predicate
209        predicate = None
210    if predicate:
211        collection = collection.__class__(filter(predicate, collection))
212    if index is not None:
213        try:
214            collection = collection[index]
215        except IndexError:
216            collection = None
217    return collection
218
219
220def set_defaults(defaults, kwargs, override=False):
221    """
222    Set defaults from defaults dict to kwargs dict
223
224    :param override:
225    :type override:
226    :param defaults:
227    :type defaults:
228    :param kwargs:
229    :type kwargs:
230    :return:
231    :rtype:
232    """
233    if 'clear' in defaults.keys() and defaults.pop('clear'):
234        kwargs.clear()
235    for key, value in defaults.items():
236        if key in kwargs:
237            if isinstance(value, list) and isinstance(kwargs[key], list):
238                kwargs[key] = list(value) + kwargs[key]
239            elif isinstance(value, dict) and isinstance(kwargs[key], dict):
240                set_defaults(value, kwargs[key])
241        if key not in kwargs or override:
242            kwargs[key] = value
243