Completed
Pull Request — master (#2057)
by Lasse
02:06
created

_is_defined_in()   A

Complexity

Conditions 4

Size

Total Lines 15

Duplication

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