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