Completed
Pull Request — master (#2345)
by Mischa
01:50
created

object_defined_in()   B

Complexity

Conditions 5

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
dl 0
loc 28
rs 8.0894
c 0
b 0
f 0
1
from contextlib import ExitStack
2
import inspect
3
import os
4
import platform
5
import sys
6
7
from coalib.misc.ContextManagers import suppress_stdout
8
from coala_decorators.decorators import arguments_to_lists, yield_once
9
10
11
def _import_module(file_path):
12
    if not os.path.exists(file_path):
13
        raise ImportError
14
15
    module_name = os.path.splitext(os.path.basename(file_path))[0]
16
    module_dir = os.path.dirname(file_path)
17
18
    if module_dir not in sys.path:
19
        sys.path.insert(0, module_dir)
20
21
    # Ugly inconsistency: Python will insist on correctly cased module names
22
    # independent of whether the OS is case-sensitive or not.
23
    # We want all cases to match though.
24
    if platform.system() == 'Windows':  # pragma: nocover
25
        for cased_file_path in os.listdir(module_dir):
26
            cased_module_name = os.path.splitext(cased_file_path)[0]
27
            if cased_module_name.lower() == module_name.lower():
28
                module_name = cased_module_name
29
                break
30
31
    return __import__(module_name)
32
33
34
def _is_subclass(test_class, superclasses):
35
    for superclass in superclasses:
36
        try:
37
            if issubclass(test_class, superclass):
38
                return True
39
        except TypeError:
40
            pass
41
    return False
42
43
44
def _has_all(obj, attribute_names):
45
    for attribute_name in attribute_names:
46
        if not hasattr(obj, attribute_name):
47
            return False
48
    return True
49
50
51
def object_defined_in(obj, file_path):
52
    """
53
    Check if the object is defined in the given file.
54
55
    >>> object_defined_in(object_defined_in, __file__)
56
    True
57
    >>> object_defined_in(object_defined_in, "somewhere else")
58
    False
59
60
    Builtins are always defined outside any given file:
61
62
    >>> object_defined_in(False, __file__)
63
    False
64
65
    :param obj:       The object to check.
66
    :param file_path: The path it might be defined in.
67
    :return:          True if the object is defined in the file.
68
    """
69
    try:
70
        source = inspect.getfile(obj)
71
        if (platform.system() == 'Windows' and
72
                source.lower() == file_path.lower() or
73
                source == file_path):
74
            return True
75
    except TypeError:  # Builtin values don't have a source location
76
        pass
77
78
    return False
79
80
81
def _is_defined_in(obj, file_path):
82
    """
83
    Check if a class is defined in the given file.
84
85
    Any class is considered to be defined in the given file if any of it's
86
    parent classes or the class itself is defined in it.
87
    """
88
    if not inspect.isclass(obj):
89
        return object_defined_in(obj, file_path)
90
91
    for base in inspect.getmro(obj):
92
        if object_defined_in(base, file_path):
93
            return True
94
95
    return False
96
97
98
@arguments_to_lists
99
@yield_once
100
def _iimport_objects(file_paths, names, types, supers, attributes, local):
101
    """
102
    Import all objects from the given modules that fulfill the requirements
103
104
    :param file_paths: File path(s) from which objects will be imported
105
    :param names:      Name(s) an objects need to have one of
106
    :param types:      Type(s) an objects need to be out of
107
    :param supers:     Class(es) objects need to be a subclass of
108
    :param attributes: Attribute(s) an object needs to (all) have
109
    :param local:      if True: Objects need to be defined in the file they
110
                       appear in to be collected
111
    :return:           iterator that yields all matching python objects
112
    :raises Exception: Any exception that is thrown in module code or an
113
                       ImportError if paths are erroneous.
114
    """
115
    if not file_paths or (not names and
116
                          not types and
117
                          not supers and
118
                          not attributes):
119
        return
120
121
    for file_path in file_paths:
122
        module = _import_module(file_path)
123
        for obj_name, obj in inspect.getmembers(module):
124
            if ((not names or obj_name in names) and
125
                    (not types or isinstance(obj, tuple(types))) and
126
                    (not supers or _is_subclass(obj, supers)) and
127
                    (not attributes or _has_all(obj, attributes)) and
128
                    (local[0] is False or _is_defined_in(obj, file_path))):
129
                yield obj
130
131
132
def iimport_objects(file_paths, names=None, types=None, supers=None,
133
                    attributes=None, local=False, suppress_output=False):
134
    """
135
    Import all objects from the given modules that fulfill the requirements
136
137
    :param file_paths:
138
        File path(s) from which objects will be imported.
139
    :param names:
140
        Name(s) an objects need to have one of.
141
    :param types:
142
        Type(s) an objects need to be out of.
143
    :param supers:
144
        Class(es) objects need to be a subclass of.
145
    :param attributes:
146
        Attribute(s) an object needs to (all) have.
147
    :param local:
148
        If True: Objects need to be defined in the file they appear in to be
149
        collected.
150
    :param suppress_output:
151
        Whether console output from stdout shall be suppressed or not.
152
    :return:
153
        An iterator that yields all matching python objects.
154
    :raises Exception:
155
        Any exception that is thrown in module code or an ImportError if paths
156
        are erroneous.
157
    """
158
    with ExitStack() as stack:
159
        if not suppress_output:
160
            stack.enter_context(suppress_stdout())
161
162
        yield from _iimport_objects(file_paths, names, types, supers,
163
                                    attributes, local)
164
165
166
def import_objects(file_paths, names=None, types=None, supers=None,
167
                   attributes=None, local=False, verbose=False):
168
    """
169
    Import all objects from the given modules that fulfill the requirements
170
171
    :param file_paths: File path(s) from which objects will be imported
172
    :param names:      Name(s) an objects need to have one of
173
    :param types:      Type(s) an objects need to be out of
174
    :param supers:     Class(es) objects need to be a subclass of
175
    :param attributes: Attribute(s) an object needs to (all) have
176
    :param local:      if True: Objects need to be defined in the file they
177
                       appear in to be collected
178
    :return:           list of all matching python objects
179
    :raises Exception: Any exception that is thrown in module code or an
180
                       ImportError if paths are erroneous.
181
    """
182
    return list(iimport_objects(file_paths, names, types, supers, attributes,
183
                                local, verbose))
184