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

_is_subclass()   A

Complexity

Conditions 4

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
dl 0
loc 8
rs 9.2
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 and (names or types or supers or attributes)):
116
        return
117
118
    for file_path in file_paths:
119
        module = _import_module(file_path)
120
        for obj_name, obj in inspect.getmembers(module):
121
            if ((not names or obj_name in names) and
122
                    (not types or isinstance(obj, tuple(types))) and
123
                    (not supers or _is_subclass(obj, supers)) and
124
                    (not attributes or _has_all(obj, attributes)) and
125
                    (local[0] is False or _is_defined_in(obj, file_path))):
126
                yield obj
127
128
129
def iimport_objects(file_paths, names=None, types=None, supers=None,
130
                    attributes=None, local=False, verbose=False):
131
    """
132
    Import all objects from the given modules that fulfill the requirements
133
134
    :param file_paths: File path(s) from which objects will be imported
135
    :param names:      Name(s) an objects need to have one of
136
    :param types:      Type(s) an objects need to be out of
137
    :param supers:     Class(es) objects need to be a subclass of
138
    :param attributes: Attribute(s) an object needs to (all) have
139
    :param local:      if True: Objects need to be defined in the file they
140
                       appear in to be collected
141
    :param verbose:    Whether verbose logs shall be displayed on console or
142
                       not.
143
    :return:           iterator that yields all matching python objects
144
    :raises Exception: Any exception that is thrown in module code or an
145
                       ImportError if paths are erroneous.
146
    """
147
    with ExitStack() as stack:
148
        if not verbose:
149
            stack.enter_context(suppress_stdout())
150
151
        yield from _iimport_objects(file_paths, names, types, supers,
152
                                    attributes, local)
153
154
155
def import_objects(file_paths, names=None, types=None, supers=None,
156
                   attributes=None, local=False, verbose=False):
157
    """
158
    Import all objects from the given modules that fulfill the requirements
159
160
    :param file_paths: File path(s) from which objects will be imported
161
    :param names:      Name(s) an objects need to have one of
162
    :param types:      Type(s) an objects need to be out of
163
    :param supers:     Class(es) objects need to be a subclass of
164
    :param attributes: Attribute(s) an object needs to (all) have
165
    :param local:      if True: Objects need to be defined in the file they
166
                       appear in to be collected
167
    :return:           list of all matching python objects
168
    :raises Exception: Any exception that is thrown in module code or an
169
                       ImportError if paths are erroneous.
170
    """
171
    return list(iimport_objects(file_paths, names, types, supers, attributes,
172
                                local, verbose))
173