This repository has been archived by the owner on Feb 25, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
/
modutil.py
145 lines (110 loc) · 4.88 KB
/
modutil.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""Help for working with modules."""
__version__ = "2.0.0"
import importlib
import importlib.machinery
import importlib.util
import types
STANDARD_MODULE_ATTRS = frozenset(['__all__', '__builtins__', '__cached__',
'__doc__', '__file__', '__loader__',
'__name__', '__package__', '__spec__',
'__getattr__'])
class ModuleAttributeError(AttributeError):
"""An AttributeError specifically for modules.
The module_name and 'attribute' attributes are set to strings representing
the module the attribute was searched on and the missing attribute,
respectively.
"""
def __init__(self, module_name, attribute):
self.module_name = module_name
self.attribute = attribute
super().__init__(f"module {module_name!r} has no attribute {attribute!r}")
def lazy_import(module_name, to_import):
"""Return the importing module and a callable for lazy importing.
The module named by module_name represents the module performing the
import to help facilitate resolving relative imports.
to_import is an iterable of the modules to be potentially imported (absolute
or relative). The `as` form of importing is also supported,
e.g. `pkg.mod as spam`.
This function returns a tuple of two items. The first is the importer
module for easy reference within itself. The second item is a callable to be
set to `__getattr__`.
"""
module = importlib.import_module(module_name)
import_mapping = {}
for name in to_import:
importing, _, binding = name.partition(' as ')
if not binding:
_, _, binding = importing.rpartition('.')
import_mapping[binding] = importing
def __getattr__(name):
if name not in import_mapping:
raise ModuleAttributeError(module_name, name)
importing = import_mapping[name]
# imortlib.import_module() implicitly sets submodules on this module as
# appropriate for direct imports.
imported = importlib.import_module(importing,
module.__spec__.parent)
setattr(module, name, imported)
return imported
return module, __getattr__
def filtered_attrs(module, *, modules=False, private=False, dunder=False,
common=False):
"""Return a collection of attributes on 'module'.
If 'modules' is false then module instances are excluded. If 'private' is
false then attributes starting with, but not ending in, '_' will be
excluded. With 'dunder' set to false then attributes starting and ending
with '_' are left out. The 'common' argument controls whether attributes
found in STANDARD_MODULE_ATTRS are returned.
"""
attr_names = set()
for name, value in module.__dict__.items():
if not common and name in STANDARD_MODULE_ATTRS:
continue
if name.startswith('_'):
if name.endswith('_'):
if not dunder:
continue
elif not private:
continue
if not modules and isinstance(value, types.ModuleType):
continue
attr_names.add(name)
return frozenset(attr_names)
def calc___all__(module_name, **kwargs):
"""Return a sorted list of defined attributes on 'module_name'.
All values specified in **kwargs are directly passed to filtered_attrs().
"""
module = importlib.import_module(module_name)
return sorted(filtered_attrs(module, **kwargs))
def filtered_dir(module_name, *, additions={}, **kwargs):
"""Return a callable appropriate for __dir__().
All values specified in **kwargs get passed directly to filtered_attrs().
The 'additions' argument should be an iterable which is added to the final
results.
"""
module = importlib.import_module(module_name)
def __dir__():
attr_names = set(filtered_attrs(module, **kwargs))
attr_names.update(additions)
return sorted(attr_names)
return __dir__
def chained___getattr__(module_name, *getattrs):
"""Create a callable which calls each __getattr__ in sequence.
Any raised ModuleAttributeError which matches module_name and the
attribute being searched for will be caught and the search will continue.
All other exceptions will be allowed to propagate. If no callable
successfully returns a value, ModuleAttributeError will be raised.
"""
def __getattr__(name):
"""Call each __getattr__ function in sequence."""
for getattr_ in getattrs:
try:
return getattr_(name)
except ModuleAttributeError as exc:
if exc.module_name == module_name and exc.attribute == name:
continue
else:
raise
else:
raise ModuleAttributeError(module_name, name)
return __getattr__