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
|
|
|
|