|
1
|
|
|
""" |
|
2
|
|
|
Widget Discovery |
|
3
|
|
|
================ |
|
4
|
|
|
|
|
5
|
|
|
Discover which widgets are installed/available. |
|
6
|
|
|
|
|
7
|
|
|
This module implements a discovery process |
|
8
|
|
|
|
|
9
|
|
|
""" |
|
10
|
|
|
|
|
11
|
|
|
import os |
|
12
|
|
|
import sys |
|
|
|
|
|
|
13
|
|
|
import stat |
|
14
|
|
|
import glob |
|
15
|
|
|
import logging |
|
16
|
|
|
import types |
|
17
|
|
|
import pkgutil |
|
18
|
|
|
from collections import namedtuple |
|
19
|
|
|
import pkg_resources |
|
20
|
|
|
|
|
21
|
|
|
from .description import ( |
|
22
|
|
|
WidgetDescription, CategoryDescription, |
|
23
|
|
|
WidgetSpecificationError, CategorySpecificationError |
|
24
|
|
|
) |
|
25
|
|
|
|
|
26
|
|
|
from . import VERSION_HEX |
|
27
|
|
|
from . import cache, WidgetRegistry |
|
28
|
|
|
import collections |
|
|
|
|
|
|
29
|
|
|
|
|
30
|
|
|
log = logging.getLogger(__name__) |
|
31
|
|
|
|
|
32
|
|
|
|
|
33
|
|
|
_CacheEntry = \ |
|
34
|
|
|
namedtuple( |
|
35
|
|
|
"_CacheEntry", |
|
36
|
|
|
["mod_path", # Module path (filename) |
|
37
|
|
|
"name", # Module qualified import name |
|
38
|
|
|
"mtime", # Modified time |
|
39
|
|
|
"project_name", # distribution name (if available) |
|
40
|
|
|
"project_version", # distribution version (if available) |
|
41
|
|
|
"exc_type", # exception type when last trying to import |
|
42
|
|
|
"exc_val", # exception value (str of value) |
|
43
|
|
|
"description" # WidgetDescription instance |
|
44
|
|
|
] |
|
45
|
|
|
) |
|
46
|
|
|
|
|
47
|
|
|
|
|
48
|
|
|
def default_category_for_module(module): |
|
49
|
|
|
""" |
|
50
|
|
|
Return a default constructed :class:`CategoryDescription` |
|
51
|
|
|
for a `module`. |
|
52
|
|
|
|
|
53
|
|
|
""" |
|
54
|
|
|
if isinstance(module, str): |
|
55
|
|
|
module = __import__(module, fromlist=[""]) |
|
56
|
|
|
name = module.__name__.rsplit(".", 1)[-1] |
|
57
|
|
|
qualified_name = module.__name__ |
|
58
|
|
|
return CategoryDescription(name=name, qualified_name=qualified_name) |
|
59
|
|
|
|
|
60
|
|
|
|
|
61
|
|
|
class WidgetDiscovery(object): |
|
62
|
|
|
""" |
|
63
|
|
|
Base widget discovery runner. |
|
64
|
|
|
""" |
|
65
|
|
|
|
|
66
|
|
|
def __init__(self, registry=None, cached_descriptions=None): |
|
67
|
|
|
self.registry = registry |
|
68
|
|
|
self.cached_descriptions = cached_descriptions or {} |
|
69
|
|
|
version = (VERSION_HEX, ) |
|
70
|
|
|
if self.cached_descriptions.get("!VERSION") != version: |
|
71
|
|
|
self.cached_descriptions.clear() |
|
72
|
|
|
self.cached_descriptions["!VERSION"] = version |
|
73
|
|
|
|
|
74
|
|
|
def run(self, entry_points_iter): |
|
75
|
|
|
""" |
|
76
|
|
|
Run the widget discovery process from an entry point iterator |
|
77
|
|
|
(yielding :class:`pkg_resources.EntryPoint` instances). |
|
78
|
|
|
|
|
79
|
|
|
As a convenience, if `entry_points_iter` is a string it will be used |
|
80
|
|
|
to retrieve the iterator using `pkg_resources.iter_entry_points`. |
|
81
|
|
|
|
|
82
|
|
|
""" |
|
83
|
|
|
if isinstance(entry_points_iter, str): |
|
84
|
|
|
entry_points_iter = \ |
|
85
|
|
|
pkg_resources.iter_entry_points(entry_points_iter) |
|
86
|
|
|
|
|
87
|
|
|
for entry_point in entry_points_iter: |
|
88
|
|
|
try: |
|
89
|
|
|
point = entry_point.load() |
|
90
|
|
|
except pkg_resources.DistributionNotFound: |
|
91
|
|
|
log.error("Could not load '%s' (unsatisfied dependencies).", |
|
92
|
|
|
entry_point, exc_info=True) |
|
93
|
|
|
continue |
|
94
|
|
|
except ImportError: |
|
95
|
|
|
log.error("An ImportError occurred while loading " |
|
96
|
|
|
"entry point '%s'", entry_point, exc_info=True) |
|
97
|
|
|
continue |
|
98
|
|
|
except Exception: |
|
|
|
|
|
|
99
|
|
|
log.error("An exception occurred while loading " |
|
100
|
|
|
"entry point '%s'", entry_point, exc_info=True) |
|
101
|
|
|
continue |
|
102
|
|
|
|
|
103
|
|
|
try: |
|
104
|
|
|
if isinstance(point, types.ModuleType): |
|
105
|
|
|
if hasattr(point, "__path__"): |
|
106
|
|
|
# Entry point is a package (a widget category) |
|
107
|
|
|
self.process_category_package( |
|
108
|
|
|
point, |
|
109
|
|
|
name=entry_point.name, |
|
110
|
|
|
distribution=entry_point.dist |
|
111
|
|
|
) |
|
112
|
|
|
else: |
|
113
|
|
|
# Entry point is a module (a single widget) |
|
114
|
|
|
self.process_widget_module( |
|
115
|
|
|
point, |
|
116
|
|
|
name=entry_point.name, |
|
117
|
|
|
distribution=entry_point.dist |
|
118
|
|
|
) |
|
119
|
|
|
elif isinstance(point, (types.FunctionType, types.MethodType)): |
|
120
|
|
|
# Entry point is a callable loader function |
|
121
|
|
|
self.process_loader(point) |
|
122
|
|
|
elif isinstance(point, (list, tuple)): |
|
123
|
|
|
# An iterator yielding Category/WidgetDescriptor instances. |
|
124
|
|
|
self.process_iter(point) |
|
125
|
|
|
else: |
|
126
|
|
|
log.error("Cannot handle entry point %r", point) |
|
127
|
|
|
except Exception: |
|
|
|
|
|
|
128
|
|
|
log.error("An exception occurred while processing %r.", |
|
129
|
|
|
entry_point, exc_info=True) |
|
130
|
|
|
|
|
131
|
|
|
def process_directory(self, directory): |
|
132
|
|
|
""" |
|
133
|
|
|
Process and locate any widgets in directory on a file system, |
|
134
|
|
|
i.e. examines all .py files in the directory, delegating the |
|
135
|
|
|
to `process_file` (deprecated) |
|
136
|
|
|
|
|
137
|
|
|
""" |
|
138
|
|
|
for filename in glob.glob(os.path.join(directory, "*.py")): |
|
139
|
|
|
self.process_file(filename) |
|
140
|
|
|
|
|
141
|
|
|
def process_file(self, filename): |
|
142
|
|
|
""" |
|
143
|
|
|
Process .py file containing the widget code (deprecated). |
|
144
|
|
|
""" |
|
145
|
|
|
filename = fix_pyext(filename) |
|
146
|
|
|
# TODO: zipped modules |
|
|
|
|
|
|
147
|
|
|
if self.cache_has_valid_entry(filename): |
|
148
|
|
|
desc = self.cache_get(filename).description |
|
149
|
|
|
return |
|
150
|
|
|
|
|
151
|
|
|
if self.cache_can_ignore(filename): |
|
152
|
|
|
log.info("Ignoring %r.", filename) |
|
153
|
|
|
return |
|
154
|
|
|
|
|
155
|
|
|
try: |
|
156
|
|
|
desc = WidgetDescription.from_file(filename) |
|
157
|
|
|
except WidgetSpecificationError: |
|
158
|
|
|
self.cache_insert(filename, 0, None, None, |
|
159
|
|
|
WidgetSpecificationError) |
|
160
|
|
|
return |
|
161
|
|
|
except Exception: |
|
|
|
|
|
|
162
|
|
|
log.info("Error processing a file.", exc_info=True) |
|
163
|
|
|
return |
|
164
|
|
|
|
|
165
|
|
|
self.handle_widget(desc) |
|
166
|
|
|
|
|
167
|
|
|
def process_widget_module(self, module, name=None, category_name=None, |
|
|
|
|
|
|
168
|
|
|
distribution=None): |
|
169
|
|
|
""" |
|
170
|
|
|
Process a widget module. |
|
171
|
|
|
""" |
|
172
|
|
|
try: |
|
173
|
|
|
desc = self.widget_description(module, widget_name=name, |
|
174
|
|
|
distribution=distribution) |
|
175
|
|
|
except (WidgetSpecificationError, Exception) as ex: |
|
|
|
|
|
|
176
|
|
|
log.info("Invalid widget specification.", exc_info=True) |
|
177
|
|
|
return |
|
178
|
|
|
|
|
179
|
|
|
self.handle_widget(desc) |
|
180
|
|
|
|
|
181
|
|
|
def process_category_package(self, category, name=None, distribution=None): |
|
182
|
|
|
""" |
|
183
|
|
|
Process a category package. |
|
184
|
|
|
""" |
|
185
|
|
|
cat_desc = None |
|
186
|
|
|
category = asmodule(category) |
|
187
|
|
|
|
|
188
|
|
|
if hasattr(category, "widget_discovery"): |
|
189
|
|
|
widget_discovery = getattr(category, "widget_discovery") |
|
190
|
|
|
self.process_loader(widget_discovery) |
|
191
|
|
|
return # The widget_discovery function handles all |
|
192
|
|
|
elif hasattr(category, "category_description"): |
|
193
|
|
|
category_description = getattr(category, "category_description") |
|
194
|
|
|
try: |
|
195
|
|
|
cat_desc = category_description() |
|
196
|
|
|
except Exception: |
|
|
|
|
|
|
197
|
|
|
log.error("Error calling 'category_description' in %r.", |
|
198
|
|
|
category, exc_info=True) |
|
199
|
|
|
cat_desc = default_category_for_module(category) |
|
200
|
|
|
else: |
|
201
|
|
|
try: |
|
202
|
|
|
cat_desc = CategoryDescription.from_package(category) |
|
203
|
|
|
except (CategorySpecificationError, Exception) as ex: |
|
|
|
|
|
|
204
|
|
|
log.info("Package %r does not describe a category.", category, |
|
205
|
|
|
exc_info=True) |
|
206
|
|
|
cat_desc = default_category_for_module(category) |
|
207
|
|
|
|
|
208
|
|
|
if name is not None: |
|
209
|
|
|
cat_desc.name = name |
|
210
|
|
|
|
|
211
|
|
|
if distribution is not None: |
|
212
|
|
|
cat_desc.project_name = distribution.project_name |
|
213
|
|
|
|
|
214
|
|
|
self.handle_category(cat_desc) |
|
215
|
|
|
|
|
216
|
|
|
desc_iter = self.iter_widget_descriptions( |
|
217
|
|
|
category, |
|
218
|
|
|
category_name=cat_desc.name, |
|
219
|
|
|
distribution=distribution |
|
220
|
|
|
) |
|
221
|
|
|
|
|
222
|
|
|
for desc in desc_iter: |
|
223
|
|
|
self.handle_widget(desc) |
|
224
|
|
|
|
|
225
|
|
|
def process_loader(self, callable): |
|
|
|
|
|
|
226
|
|
|
""" |
|
227
|
|
|
Process a callable loader function. |
|
228
|
|
|
""" |
|
229
|
|
|
try: |
|
230
|
|
|
callable(self) |
|
231
|
|
|
except Exception: |
|
|
|
|
|
|
232
|
|
|
log.error("Error calling %r", callable, exc_info=True) |
|
233
|
|
|
|
|
234
|
|
|
def process_iter(self, iter): |
|
|
|
|
|
|
235
|
|
|
""" |
|
236
|
|
|
""" |
|
237
|
|
|
for desc in iter: |
|
238
|
|
|
if isinstance(desc, CategoryDescription): |
|
239
|
|
|
self.handle_category(desc) |
|
240
|
|
|
elif isinstance(desc, WidgetDescription): |
|
241
|
|
|
self.handle_widget(desc) |
|
242
|
|
|
else: |
|
243
|
|
|
log.error("Category or Widget Description instance " |
|
244
|
|
|
"expected. Got %r.", desc) |
|
245
|
|
|
|
|
246
|
|
|
def handle_widget(self, desc): |
|
247
|
|
|
""" |
|
248
|
|
|
Handle a found widget description. |
|
249
|
|
|
|
|
250
|
|
|
Base implementation adds it to the registry supplied in the |
|
251
|
|
|
constructor. |
|
252
|
|
|
|
|
253
|
|
|
""" |
|
254
|
|
|
if self.registry: |
|
255
|
|
|
self.registry.register_widget(desc) |
|
256
|
|
|
|
|
257
|
|
|
def handle_category(self, desc): |
|
258
|
|
|
""" |
|
259
|
|
|
Handle a found category description. |
|
260
|
|
|
|
|
261
|
|
|
Base implementation adds it to the registry supplied in the |
|
262
|
|
|
constructor. |
|
263
|
|
|
|
|
264
|
|
|
""" |
|
265
|
|
|
if self.registry: |
|
266
|
|
|
self.registry.register_category(desc) |
|
267
|
|
|
|
|
268
|
|
|
def iter_widget_descriptions(self, package, category_name=None, |
|
269
|
|
|
distribution=None): |
|
270
|
|
|
""" |
|
271
|
|
|
Return an iterator over widget descriptions accessible from |
|
272
|
|
|
`package`. |
|
273
|
|
|
|
|
274
|
|
|
""" |
|
275
|
|
|
package = asmodule(package) |
|
276
|
|
|
|
|
277
|
|
|
for path in package.__path__: |
|
278
|
|
|
for _, mod_name, ispkg in pkgutil.iter_modules([path]): |
|
279
|
|
|
if ispkg: |
|
280
|
|
|
continue |
|
281
|
|
|
name = package.__name__ + "." + mod_name |
|
282
|
|
|
source_path = os.path.join(path, mod_name + ".py") |
|
283
|
|
|
desc = None |
|
284
|
|
|
|
|
285
|
|
|
# Check if the path can be ignored. |
|
286
|
|
|
if self.cache_can_ignore(source_path, distribution): |
|
287
|
|
|
log.info("Ignoring %r.", source_path) |
|
288
|
|
|
continue |
|
289
|
|
|
|
|
290
|
|
|
# Check if a source file for the module is available |
|
291
|
|
|
# and is already cached. |
|
292
|
|
|
if self.cache_has_valid_entry(source_path, distribution): |
|
293
|
|
|
desc = self.cache_get(source_path).description |
|
294
|
|
|
|
|
295
|
|
|
if desc is None: |
|
296
|
|
|
try: |
|
297
|
|
|
module = asmodule(name) |
|
298
|
|
|
except ImportError: |
|
299
|
|
|
log.info("Could not import %r.", name, exc_info=True) |
|
300
|
|
|
continue |
|
301
|
|
|
except Exception: |
|
|
|
|
|
|
302
|
|
|
log.warning("Error while importing %r.", name, |
|
303
|
|
|
exc_info=True) |
|
304
|
|
|
continue |
|
305
|
|
|
|
|
306
|
|
|
try: |
|
307
|
|
|
desc = self.widget_description( |
|
308
|
|
|
module, |
|
309
|
|
|
category_name=category_name, |
|
310
|
|
|
distribution=distribution |
|
311
|
|
|
) |
|
312
|
|
|
except WidgetSpecificationError: |
|
313
|
|
|
self.cache_log_error( |
|
314
|
|
|
source_path, WidgetSpecificationError, |
|
315
|
|
|
distribution |
|
316
|
|
|
) |
|
317
|
|
|
|
|
318
|
|
|
continue |
|
319
|
|
|
except Exception: |
|
|
|
|
|
|
320
|
|
|
log.warning("Problem parsing %r", name, exc_info=True) |
|
321
|
|
|
continue |
|
322
|
|
|
yield desc |
|
323
|
|
|
self.cache_insert(source_path, os.stat(source_path).st_mtime, |
|
324
|
|
|
desc, distribution) |
|
325
|
|
|
|
|
326
|
|
|
def widget_description(self, module, widget_name=None, |
|
|
|
|
|
|
327
|
|
|
category_name=None, distribution=None): |
|
328
|
|
|
""" |
|
329
|
|
|
Return a widget description from a module. |
|
330
|
|
|
""" |
|
331
|
|
|
if isinstance(module, str): |
|
332
|
|
|
module = __import__(module, fromlist=[""]) |
|
333
|
|
|
|
|
334
|
|
|
desc = None |
|
335
|
|
|
try: |
|
336
|
|
|
desc = WidgetDescription.from_module(module) |
|
337
|
|
|
except WidgetSpecificationError: |
|
338
|
|
|
try: |
|
339
|
|
|
desc = WidgetDescription.from_file( |
|
340
|
|
|
module.__file__, import_name=module.__name__ |
|
341
|
|
|
) |
|
342
|
|
|
except Exception: |
|
|
|
|
|
|
343
|
|
|
pass |
|
344
|
|
|
|
|
345
|
|
|
if desc is None: |
|
346
|
|
|
raise |
|
347
|
|
|
|
|
348
|
|
|
if widget_name is not None: |
|
349
|
|
|
desc.name = widget_name |
|
350
|
|
|
|
|
351
|
|
|
if category_name is not None: |
|
352
|
|
|
desc.category = category_name |
|
353
|
|
|
|
|
354
|
|
|
if distribution is not None: |
|
355
|
|
|
desc.project_name = distribution.project_name |
|
356
|
|
|
|
|
357
|
|
|
return desc |
|
358
|
|
|
|
|
359
|
|
|
def cache_insert(self, module, mtime, description, distribution=None, |
|
360
|
|
|
error=None): |
|
361
|
|
|
""" |
|
362
|
|
|
Insert the description into the cache. |
|
363
|
|
|
""" |
|
364
|
|
|
if isinstance(module, types.ModuleType): |
|
365
|
|
|
mod_path = module.__file__ |
|
366
|
|
|
mod_name = module.__name__ |
|
367
|
|
|
else: |
|
368
|
|
|
mod_path = module |
|
369
|
|
|
mod_name = None |
|
370
|
|
|
mod_path = fix_pyext(mod_path) |
|
371
|
|
|
|
|
372
|
|
|
project_name = project_version = None |
|
373
|
|
|
|
|
374
|
|
|
if distribution is not None: |
|
375
|
|
|
project_name = distribution.project_name |
|
376
|
|
|
project_version = distribution.version |
|
377
|
|
|
|
|
378
|
|
|
exc_type = exc_val = None |
|
379
|
|
|
|
|
380
|
|
|
if error is not None: |
|
381
|
|
|
if isinstance(error, type): |
|
382
|
|
|
exc_type = error |
|
383
|
|
|
exc_val = None |
|
384
|
|
|
elif isinstance(error, Exception): |
|
385
|
|
|
exc_type = type(error) |
|
386
|
|
|
exc_val = repr(error.args) |
|
387
|
|
|
|
|
388
|
|
|
self.cached_descriptions[mod_path] = \ |
|
389
|
|
|
_CacheEntry(mod_path, mod_name, mtime, project_name, |
|
390
|
|
|
project_version, exc_type, exc_val, |
|
391
|
|
|
description) |
|
392
|
|
|
|
|
393
|
|
|
def cache_get(self, mod_path, distribution=None): |
|
|
|
|
|
|
394
|
|
|
""" |
|
395
|
|
|
Get the cache entry for `mod_path`. |
|
396
|
|
|
""" |
|
397
|
|
|
if isinstance(mod_path, types.ModuleType): |
|
398
|
|
|
mod_path = mod_path.__file__ |
|
399
|
|
|
mod_path = fix_pyext(mod_path) |
|
400
|
|
|
return self.cached_descriptions.get(mod_path) |
|
401
|
|
|
|
|
402
|
|
|
def cache_has_valid_entry(self, mod_path, distribution=None): |
|
403
|
|
|
""" |
|
404
|
|
|
Does the cache have a valid entry for `mod_path`. |
|
405
|
|
|
""" |
|
406
|
|
|
mod_path = fix_pyext(mod_path) |
|
407
|
|
|
|
|
408
|
|
|
if not os.path.exists(mod_path): |
|
409
|
|
|
return False |
|
410
|
|
|
|
|
411
|
|
|
if mod_path in self.cached_descriptions: |
|
412
|
|
|
entry = self.cache_get(mod_path) |
|
413
|
|
|
mtime = os.stat(mod_path).st_mtime |
|
414
|
|
|
if entry.mtime != mtime: |
|
415
|
|
|
return False |
|
416
|
|
|
|
|
417
|
|
|
if distribution is not None: |
|
418
|
|
|
if entry.project_name != distribution.project_name or \ |
|
419
|
|
|
entry.project_version != distribution.version: |
|
420
|
|
|
return False |
|
421
|
|
|
|
|
422
|
|
|
if entry.exc_type == WidgetSpecificationError: |
|
423
|
|
|
return False |
|
424
|
|
|
|
|
425
|
|
|
# All checks pass |
|
426
|
|
|
return True |
|
427
|
|
|
|
|
428
|
|
|
return False |
|
429
|
|
|
|
|
430
|
|
|
def cache_can_ignore(self, mod_path, distribution=None): |
|
|
|
|
|
|
431
|
|
|
""" |
|
432
|
|
|
Can the `mod_path` be ignored (i.e. it was determined that it |
|
433
|
|
|
could not contain a valid widget description, for instance the |
|
434
|
|
|
module does not have a valid description and was not changed from |
|
435
|
|
|
the last discovery run). |
|
436
|
|
|
|
|
437
|
|
|
""" |
|
438
|
|
|
mod_path = fix_pyext(mod_path) |
|
439
|
|
|
if not os.path.exists(mod_path): |
|
440
|
|
|
# Possible orphaned .py[co] file |
|
441
|
|
|
return True |
|
442
|
|
|
|
|
443
|
|
|
mtime = os.stat(mod_path).st_mtime |
|
444
|
|
|
if mod_path in self.cached_descriptions: |
|
445
|
|
|
entry = self.cached_descriptions[mod_path] |
|
446
|
|
|
return entry.mtime == mtime and \ |
|
447
|
|
|
entry.exc_type == WidgetSpecificationError |
|
448
|
|
|
else: |
|
449
|
|
|
return False |
|
450
|
|
|
|
|
451
|
|
|
def cache_log_error(self, mod_path, error, distribution=None): |
|
452
|
|
|
""" |
|
453
|
|
|
Cache that the `error` occurred while processing `mod_path`. |
|
454
|
|
|
""" |
|
455
|
|
|
mod_path = fix_pyext(mod_path) |
|
456
|
|
|
if not os.path.exists(mod_path): |
|
457
|
|
|
# Possible orphaned .py[co] file |
|
458
|
|
|
return |
|
459
|
|
|
mtime = os.stat(mod_path).st_mtime |
|
460
|
|
|
|
|
461
|
|
|
self.cache_insert(mod_path, mtime, None, distribution, error) |
|
462
|
|
|
|
|
463
|
|
|
|
|
464
|
|
|
def fix_pyext(mod_path): |
|
465
|
|
|
""" |
|
466
|
|
|
Fix a module filename path extension to always end with the |
|
467
|
|
|
modules source file (i.e. strip compiled/optimized .pyc, .pyo |
|
468
|
|
|
extension and replace it with .py). |
|
469
|
|
|
|
|
470
|
|
|
""" |
|
471
|
|
|
if mod_path[-4:] in [".pyo", "pyc"]: |
|
472
|
|
|
mod_path = mod_path[:-1] |
|
473
|
|
|
return mod_path |
|
474
|
|
|
|
|
475
|
|
|
|
|
476
|
|
|
def widget_descriptions_from_package(package): |
|
477
|
|
|
package = asmodule(package) |
|
478
|
|
|
|
|
479
|
|
|
desciptions = [] |
|
480
|
|
|
for _, name, ispkg in pkgutil.iter_modules( |
|
481
|
|
|
package.__path__, package.__name__ + "."): |
|
482
|
|
|
if ispkg: |
|
483
|
|
|
continue |
|
484
|
|
|
try: |
|
485
|
|
|
module = asmodule(name) |
|
486
|
|
|
except Exception: |
|
|
|
|
|
|
487
|
|
|
log.error("Error importing %r.", name, exc_info=True) |
|
488
|
|
|
continue |
|
489
|
|
|
|
|
490
|
|
|
desc = None |
|
491
|
|
|
try: |
|
492
|
|
|
desc = WidgetDescription.from_module(module) |
|
493
|
|
|
except Exception: |
|
|
|
|
|
|
494
|
|
|
pass |
|
495
|
|
|
if not desc: |
|
496
|
|
|
try: |
|
497
|
|
|
desc = WidgetDescription.from_file( |
|
498
|
|
|
module.__file__, import_name=name |
|
499
|
|
|
) |
|
500
|
|
|
except Exception: |
|
|
|
|
|
|
501
|
|
|
pass |
|
502
|
|
|
|
|
503
|
|
|
if not desc: |
|
504
|
|
|
log.info("Error in %r", name, exc_info=True) |
|
505
|
|
|
else: |
|
506
|
|
|
desciptions.append(desc) |
|
507
|
|
|
return desciptions |
|
508
|
|
|
|
|
509
|
|
|
|
|
510
|
|
|
def module_name_split(name): |
|
511
|
|
|
""" |
|
512
|
|
|
Split the module name into package name and module name. |
|
513
|
|
|
""" |
|
514
|
|
|
if "." in name: |
|
515
|
|
|
package_name, module = name.rsplit(".", 1) |
|
516
|
|
|
else: |
|
517
|
|
|
package_name, module = "", name |
|
518
|
|
|
return package_name, module |
|
519
|
|
|
|
|
520
|
|
|
|
|
521
|
|
|
def module_modified_time(module): |
|
522
|
|
|
""" |
|
523
|
|
|
Return the `module`s source filename and modified time as a tuple |
|
524
|
|
|
(source_filename, modified_time). In case the module is from a zipped |
|
525
|
|
|
package the modified time is that of of the archive. |
|
526
|
|
|
|
|
527
|
|
|
""" |
|
528
|
|
|
module = asmodule(module) |
|
529
|
|
|
name = module.__name__ |
|
530
|
|
|
module_filename = module.__file__ |
|
531
|
|
|
|
|
532
|
|
|
provider = pkg_resources.get_provider(name) |
|
533
|
|
|
if provider.loader: |
|
534
|
|
|
m_time = os.stat(provider.loader.archive)[stat.ST_MTIME] |
|
535
|
|
|
else: |
|
536
|
|
|
basename = os.path.basename(module_filename) |
|
537
|
|
|
path = pkg_resources.resource_filename(name, basename) |
|
538
|
|
|
m_time = os.stat(path)[stat.ST_MTIME] |
|
539
|
|
|
return (module_filename, m_time) |
|
540
|
|
|
|
|
541
|
|
|
|
|
542
|
|
|
def asmodule(module): |
|
543
|
|
|
""" |
|
544
|
|
|
Return the module references by `module` name. If `module` is |
|
545
|
|
|
already an imported module instance, return it as is. |
|
546
|
|
|
|
|
547
|
|
|
""" |
|
548
|
|
|
if isinstance(module, types.ModuleType): |
|
549
|
|
|
return module |
|
550
|
|
|
elif isinstance(module, str): |
|
551
|
|
|
return __import__(module, fromlist=[""]) |
|
552
|
|
|
else: |
|
553
|
|
|
raise TypeError(type(module)) |
|
554
|
|
|
|
|
555
|
|
|
|
|
556
|
|
|
def run_discovery(entry_point, cached=False): |
|
|
|
|
|
|
557
|
|
|
""" |
|
558
|
|
|
Run the default widget discovery and return a :class:`WidgetRegistry` |
|
559
|
|
|
instance. |
|
560
|
|
|
|
|
561
|
|
|
""" |
|
562
|
|
|
reg_cache = {} |
|
563
|
|
|
if cached: |
|
564
|
|
|
reg_cache = cache.registry_cache() |
|
565
|
|
|
|
|
566
|
|
|
registry = WidgetRegistry() |
|
567
|
|
|
discovery = WidgetDiscovery(registry, cached_descriptions=reg_cache) |
|
568
|
|
|
discovery.run() |
|
569
|
|
|
if cached: |
|
570
|
|
|
cache.save_registry_cache(reg_cache) |
|
571
|
|
|
return registry |
|
572
|
|
|
|