Completed
Push — 5.2-unstable ( ee49a9...946858 )
by Felipe A.
01:27
created

alternative_import()   B

Complexity

Conditions 5

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
dl 0
loc 25
rs 8.0894
c 0
b 0
f 0
1
#!/usr/bin/env python
2
# -*- coding: UTF-8 -*-
3
4
import os
5
import os.path
6
import sys
7
import itertools
8
9
import warnings
10
import functools
11
12
FS_ENCODING = sys.getfilesystemencoding()
13
PY_LEGACY = sys.version_info < (3, )
14
TRUE_VALUES = frozenset(('true', 'yes', '1', 'enable', 'enabled', True, 1))
15
16
try:
17
    from scandir import scandir, walk
18
except ImportError:
19
    if not hasattr(os, 'scandir'):
20
        raise
21
    scandir = os.scandir
22
    walk = os.walk
23
24
25
def isexec(path):
26
    '''
27
    Check if given path points to an executable file.
28
29
    :param path: file path
30
    :type path: str
31
    :return: True if executable, False otherwise
32
    :rtype: bool
33
    '''
34
    return os.path.isfile(path) and os.access(path, os.X_OK)
35
36
37
def fsdecode(path, os_name=os.name, fs_encoding=FS_ENCODING, errors=None):
38
    '''
39
    Decode given path.
40
41
    :param path: path will be decoded if using bytes
42
    :type path: bytes or str
43
    :param os_name: operative system name, defaults to os.name
44
    :type os_name: str
45
    :param fs_encoding: current filesystem encoding, defaults to autodetected
46
    :type fs_encoding: str
47
    :return: decoded path
48
    :rtype: str
49
    '''
50
    if not isinstance(path, bytes):
51
        return path
52
    if not errors:
53
        use_strict = PY_LEGACY or os_name == 'nt'
54
        errors = 'strict' if use_strict else 'surrogateescape'
55
    return path.decode(fs_encoding, errors=errors)
56
57
58
def fsencode(path, os_name=os.name, fs_encoding=FS_ENCODING, errors=None):
59
    '''
60
    Encode given path.
61
62
    :param path: path will be encoded if not using bytes
63
    :type path: bytes or str
64
    :param os_name: operative system name, defaults to os.name
65
    :type os_name: str
66
    :param fs_encoding: current filesystem encoding, defaults to autodetected
67
    :type fs_encoding: str
68
    :return: encoded path
69
    :rtype: bytes
70
    '''
71
    if isinstance(path, bytes):
72
        return path
73
    if not errors:
74
        use_strict = PY_LEGACY or os_name == 'nt'
75
        errors = 'strict' if use_strict else 'surrogateescape'
76
    return path.encode(fs_encoding, errors=errors)
77
78
79
def getcwd(fs_encoding=FS_ENCODING, cwd_fnc=os.getcwd):
80
    '''
81
    Get current work directory's absolute path.
82
    Like os.getcwd but garanteed to return an unicode-str object.
83
84
    :param fs_encoding: filesystem encoding, defaults to autodetected
85
    :type fs_encoding: str
86
    :param cwd_fnc: callable used to get the path, defaults to os.getcwd
87
    :type cwd_fnc: Callable
88
    :return: path
89
    :rtype: str
90
    '''
91
    path = cwd_fnc()
92
    if isinstance(path, bytes):
93
        path = fsdecode(path, fs_encoding=fs_encoding)
94
    return os.path.abspath(path)
95
96
97
def getdebug(environ=os.environ, true_values=TRUE_VALUES):
98
    '''
99
    Get if app is expected to be ran in debug mode looking at environment
100
    variables.
101
102
    :param environ: environment dict-like object
103
    :type environ: collections.abc.Mapping
104
    :returns: True if debug contains a true-like string, False otherwise
105
    :rtype: bool
106
    '''
107
    return environ.get('DEBUG', '').lower() in true_values
108
109
110
def deprecated(func_or_text, environ=os.environ):
111
    '''
112
    Decorator used to mark functions as deprecated. It will result in a
113
    warning being emmitted hen the function is called.
114
115
    Usage:
116
117
    >>> @deprecated
118
    ... def fnc():
119
    ...     pass
120
121
    Usage (custom message):
122
123
    >>> @deprecated('This is deprecated')
124
    ... def fnc():
125
    ...     pass
126
127
    :param func_or_text: message or callable to decorate
128
    :type func_or_text: callable
129
    :param environ: optional environment mapping
130
    :type environ: collections.abc.Mapping
131
    :returns: nested decorator or new decorated function (depending on params)
132
    :rtype: callable
133
    '''
134
    def inner(func):
135
        message = (
136
            'Deprecated function {}.'.format(func.__name__)
137
            if callable(func_or_text) else
138
            func_or_text
139
            )
140
141
        @functools.wraps(func)
142
        def new_func(*args, **kwargs):
143
            with warnings.catch_warnings():
144
                if getdebug(environ):
145
                    warnings.simplefilter('always', DeprecationWarning)
146
                warnings.warn(message, category=DeprecationWarning,
147
                              stacklevel=3)
148
            return func(*args, **kwargs)
149
        return new_func
150
    return inner(func_or_text) if callable(func_or_text) else inner
151
152
153
def usedoc(other):
154
    '''
155
    Decorator which copies __doc__ of given object into decorated one.
156
157
    Usage:
158
159
    >>> def fnc1():
160
    ...     """docstring"""
161
    ...     pass
162
    >>> @usedoc(fnc1)
163
    ... def fnc2():
164
    ...     pass
165
    >>> fnc2.__doc__
166
    'docstring'collections.abc.D
167
168
    :param other: anything with a __doc__ attribute
169
    :type other: any
170
    :returns: decorator function
171
    :rtype: callable
172
    '''
173
    def inner(fnc):
174
        fnc.__doc__ = fnc.__doc__ or getattr(other, '__doc__')
175
        return fnc
176
    return inner
177
178
179
def alternative_import(names):
180
    '''
181
    Import given module name with any of given namespaces.
182
183
    :param name:
184
    :type name: str
185
    :param namespaces: iterable of namespaces as str or None for absolute
186
    :type namespaces: iterable
187
    :returns: module reference
188
    :rtype: module
189
    :raises: ImportError
190
    '''
191
192
    for name in names:
193
        if name in sys.modules:
194
            return sys.modules[name]
195
196
    for name in names:
197
        try:
198
            __import__(name)
199
            return sys.modules[name]
200
        except (ImportError, KeyError):
201
            pass
202
203
    raise ImportError('No module found, tried %r' % names)
204
205
206
ENV_PATH = tuple(
207
  fsdecode(path.strip('"').replace('<SCAPED-PATHSEP>', os.pathsep))
208
  for path in os
209
    .environ['PATH']  # noqa
210
    .replace('\\%s' % os.pathsep, '<SCAPED-PATHSEP>')
211
    .split(os.pathsep)
212
  )
213
214
215
def which(name,
216
          env_path=ENV_PATH,
217
          is_executable_fnc=isexec,
218
          path_join_fnc=os.path.join):
219
    '''
220
    Get command absolute path.
221
222
    :param name: name of executable command
223
    :type name: str
224
    :param env_path: OS environment executable paths, defaults to autodetected
225
    :type env_path: list of str
226
    :param is_executable_fnc: callable will be used to detect if path is
227
                              executable, defaults to `isexec`
228
    :type is_executable_fnc: Callable
229
    :param path_join_fnc: callable will be used to join path components
230
    :type path_join_fnc: Callable
231
    :return: absolute path
232
    :rtype: str or None
233
    '''
234
    for path in env_path:
235
        exe_file = path_join_fnc(path, name)
236
        if is_executable_fnc(exe_file):
237
            return exe_file
238
    return None
239
240
241
if PY_LEGACY:
242
    FileNotFoundError = type('FileNotFoundError', (OSError,), {})  # noqa
243
    range = xrange  # noqa
244
    filter = itertools.ifilter
245
    basestring = basestring  # noqa
246
    unicode = unicode  # noqa
247
else:
248
    FileNotFoundError = FileNotFoundError
249
    range = range
250
    filter = filter
251
    basestring = str
252
    unicode = str
253