-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
Expand file tree
/
Copy pathpkgutil.py
More file actions
634 lines (519 loc) · 20.8 KB
/
pkgutil.py
File metadata and controls
634 lines (519 loc) · 20.8 KB
Edit and raw actions
OlderNewer
1
"""Utilities to support packages."""
2
3
from collections import namedtuple
4
from functools import singledispatch as simplegeneric
5
import importlib
6
import importlib.util
7
import importlib.machinery
8
import os
9
import os.path
10
import sys
11
from types import ModuleType
12
import warnings
13
14
__all__ = [
15
'get_importer', 'iter_importers', 'get_loader', 'find_loader',
16
'walk_packages', 'iter_modules', 'get_data',
17
'ImpImporter', 'ImpLoader', 'read_code', 'extend_path',
18
'ModuleInfo',
19
]
20
21
22
ModuleInfo = namedtuple('ModuleInfo', 'module_finder name ispkg')
23
ModuleInfo.__doc__ = 'A namedtuple with minimal info about a module.'
24
25
26
def _get_spec(finder, name):
27
"""Return the finder-specific module spec."""
28
# Works with legacy finders.
29
try:
30
find_spec = finder.find_spec
31
except AttributeError:
32
loader = finder.find_module(name)
33
if loader is None:
34
return None
35
return importlib.util.spec_from_loader(name, loader)
36
else:
37
return find_spec(name)
38
39
40
def read_code(stream):
41
# This helper is needed in order for the PEP 302 emulation to
42
# correctly handle compiled files
43
import marshal
44
45
magic = stream.read(4)
46
if magic != importlib.util.MAGIC_NUMBER:
47
return None
48
49
stream.read(8) # Skip timestamp and size
50
return marshal.load(stream)
51
52
53
def walk_packages(path=None, prefix='', onerror=None):
54
"""Yields ModuleInfo for all modules recursively
55
on path, or, if path is None, all accessible modules.
56
57
'path' should be either None or a list of paths to look for
58
modules in.
59
60
'prefix' is a string to output on the front of every module name
61
on output.
62
63
Note that this function must import all *packages* (NOT all
64
modules!) on the given path, in order to access the __path__
65
attribute to find submodules.
66
67
'onerror' is a function which gets called with one argument (the
68
name of the package which was being imported) if any exception
69
occurs while trying to import a package. If no onerror function is
70
supplied, ImportErrors are caught and ignored, while all other
71
exceptions are propagated, terminating the search.
72
73
Examples:
74
75
# list all modules python can access
76
walk_packages()
77
78
# list all submodules of ctypes
79
walk_packages(ctypes.__path__, ctypes.__name__+'.')
80
"""
81
82
def seen(p, m={}):
83
if p in m:
84
return True
85
m[p] = True
86
87
for info in iter_modules(path, prefix):
88
yield info
89
90
if info.ispkg:
91
try:
92
__import__(info.name)
93
except ImportError:
94
if onerror is not None:
95
onerror(info.name)
96
except Exception:
97
if onerror is not None:
98
onerror(info.name)
99
else:
100
raise
101
else:
102
path = getattr(sys.modules[info.name], '__path__', None) or []
103
104
# don't traverse path items we've seen before
105
path = [p for p in path if not seen(p)]
106
107
yield from walk_packages(path, info.name+'.', onerror)
108
109
110
def iter_modules(path=None, prefix=''):
111
"""Yields ModuleInfo for all submodules on path,
112
or, if path is None, all top-level modules on sys.path.
113
114
'path' should be either None or a list of paths to look for
115
modules in.
116
117
'prefix' is a string to output on the front of every module name
118
on output.
119
"""
120
if path is None:
121
importers = iter_importers()
122
else:
123
importers = map(get_importer, path)
124
125
yielded = {}
126
for i in importers:
127
for name, ispkg in iter_importer_modules(i, prefix):
128
if name not in yielded:
129
yielded[name] = 1
130
yield ModuleInfo(i, name, ispkg)
131
132
133
@simplegeneric
134
def iter_importer_modules(importer, prefix=''):
135
if not hasattr(importer, 'iter_modules'):
136
return []
137
return importer.iter_modules(prefix)
138
139
140
# Implement a file walker for the normal importlib path hook
141
def _iter_file_finder_modules(importer, prefix=''):
142
if importer.path is None or not os.path.isdir(importer.path):
143
return
144
145
yielded = {}
146
import inspect
147
try:
148
filenames = os.listdir(importer.path)
149
except OSError:
150
# ignore unreadable directories like import does
151
filenames = []
152
filenames.sort() # handle packages before same-named modules
153
154
for fn in filenames:
155
modname = inspect.getmodulename(fn)
156
if modname=='__init__' or modname in yielded:
157
continue
158
159
path = os.path.join(importer.path, fn)
160
ispkg = False
161
162
if not modname and os.path.isdir(path) and '.' not in fn:
163
modname = fn
164
try:
165
dircontents = os.listdir(path)
166
except OSError:
167
# ignore unreadable directories like import does
168
dircontents = []
169
for fn in dircontents:
170
subname = inspect.getmodulename(fn)
171
if subname=='__init__':
172
ispkg = True
173
break
174
else:
175
continue # not a package
176
177
if modname and '.' not in modname:
178
yielded[modname] = 1
179
yield prefix + modname, ispkg
180
181
iter_importer_modules.register(
182
importlib.machinery.FileFinder, _iter_file_finder_modules)
183
184
185
def _import_imp():
186
global imp
187
with warnings.catch_warnings():
188
warnings.simplefilter('ignore', DeprecationWarning)
189
imp = importlib.import_module('imp')
190
191
class ImpImporter:
192
"""PEP 302 Finder that wraps Python's "classic" import algorithm
193
194
ImpImporter(dirname) produces a PEP 302 finder that searches that
195
directory. ImpImporter(None) produces a PEP 302 finder that searches
196
the current sys.path, plus any modules that are frozen or built-in.
197
198
Note that ImpImporter does not currently support being used by placement
199
on sys.meta_path.
200
"""
201
202
def __init__(self, path=None):
203
global imp
204
warnings.warn("This emulation is deprecated, use 'importlib' instead",
205
DeprecationWarning)
206
_import_imp()
207
self.path = path
208
209
def find_module(self, fullname, path=None):
210
# Note: we ignore 'path' argument since it is only used via meta_path
211
subname = fullname.split(".")[-1]
212
if subname != fullname and self.path is None:
213
return None
214
if self.path is None:
215
path = None
216
else:
217
path = [os.path.realpath(self.path)]
218
try:
219
file, filename, etc = imp.find_module(subname, path)
220
except ImportError:
221
return None
222
return ImpLoader(fullname, file, filename, etc)
223
224
def iter_modules(self, prefix=''):
225
if self.path is None or not os.path.isdir(self.path):
226
return
227
228
yielded = {}
229
import inspect
230
try:
231
filenames = os.listdir(self.path)
232
except OSError:
233
# ignore unreadable directories like import does
234
filenames = []
235
filenames.sort() # handle packages before same-named modules
236
237
for fn in filenames:
238
modname = inspect.getmodulename(fn)
239
if modname=='__init__' or modname in yielded:
240
continue
241
242
path = os.path.join(self.path, fn)
243
ispkg = False
244
245
if not modname and os.path.isdir(path) and '.' not in fn:
246
modname = fn
247
try:
248
dircontents = os.listdir(path)
249
except OSError:
250
# ignore unreadable directories like import does
251
dircontents = []
252
for fn in dircontents:
253
subname = inspect.getmodulename(fn)
254
if subname=='__init__':
255
ispkg = True
256
break
257
else:
258
continue # not a package
259
260
if modname and '.' not in modname:
261
yielded[modname] = 1
262
yield prefix + modname, ispkg
263
264
265
class ImpLoader:
266
"""PEP 302 Loader that wraps Python's "classic" import algorithm
267
"""
268
code = source = None
269
270
def __init__(self, fullname, file, filename, etc):
271
warnings.warn("This emulation is deprecated, use 'importlib' instead",
272
DeprecationWarning)
273
_import_imp()
274
self.file = file
275
self.filename = filename
276
self.fullname = fullname
277
self.etc = etc
278
279
def load_module(self, fullname):
280
self._reopen()
281
try:
282
mod = imp.load_module(fullname, self.file, self.filename, self.etc)
283
finally:
284
if self.file:
285
self.file.close()
286
# Note: we don't set __loader__ because we want the module to look
287
# normal; i.e. this is just a wrapper for standard import machinery
288
return mod
289
290
def get_data(self, pathname):
291
with open(pathname, "rb") as file:
292
return file.read()
293
294
def _reopen(self):
295
if self.file and self.file.closed:
296
mod_type = self.etc[2]
297
if mod_type==imp.PY_SOURCE:
298
self.file = open(self.filename, 'r')
299
elif mod_type in (imp.PY_COMPILED, imp.C_EXTENSION):
300
self.file = open(self.filename, 'rb')
301
302
def _fix_name(self, fullname):
303
if fullname is None:
304
fullname = self.fullname
305
elif fullname != self.fullname:
306
raise ImportError("Loader for module %s cannot handle "
307
"module %s" % (self.fullname, fullname))
308
return fullname
309
310
def is_package(self, fullname):
311
fullname = self._fix_name(fullname)
312
return self.etc[2]==imp.PKG_DIRECTORY
313
314
def get_code(self, fullname=None):
315
fullname = self._fix_name(fullname)
316
if self.code is None:
317
mod_type = self.etc[2]
318
if mod_type==imp.PY_SOURCE:
319
source = self.get_source(fullname)
320
self.code = compile(source, self.filename, 'exec')
321
elif mod_type==imp.PY_COMPILED:
322
self._reopen()
323
try:
324
self.code = read_code(self.file)
325
finally:
326
self.file.close()
327
elif mod_type==imp.PKG_DIRECTORY:
328
self.code = self._get_delegate().get_code()
329
return self.code
330
331
def get_source(self, fullname=None):
332
fullname = self._fix_name(fullname)
333
if self.source is None:
334
mod_type = self.etc[2]
335
if mod_type==imp.PY_SOURCE:
336
self._reopen()
337
try:
338
self.source = self.file.read()
339
finally:
340
self.file.close()
341
elif mod_type==imp.PY_COMPILED:
342
if os.path.exists(self.filename[:-1]):
343
with open(self.filename[:-1], 'r') as f:
344
self.source = f.read()
345
elif mod_type==imp.PKG_DIRECTORY:
346
self.source = self._get_delegate().get_source()
347
return self.source
348
349
def _get_delegate(self):
350
finder = ImpImporter(self.filename)
351
spec = _get_spec(finder, '__init__')
352
return spec.loader
353
354
def get_filename(self, fullname=None):
355
fullname = self._fix_name(fullname)
356
mod_type = self.etc[2]
357
if mod_type==imp.PKG_DIRECTORY:
358
return self._get_delegate().get_filename()
359
elif mod_type in (imp.PY_SOURCE, imp.PY_COMPILED, imp.C_EXTENSION):
360
return self.filename
361
return None
362
363
364
try:
365
import zipimport
366
from zipimport import zipimporter
367
368
def iter_zipimport_modules(importer, prefix=''):
369
dirlist = sorted(zipimport._zip_directory_cache[importer.archive])
370
_prefix = importer.prefix
371
plen = len(_prefix)
372
yielded = {}
373
import inspect
374
for fn in dirlist:
375
if not fn.startswith(_prefix):
376
continue
377
378
fn = fn[plen:].split(os.sep)
379
380
if len(fn)==2 and fn[1].startswith('__init__.py'):
381
if fn[0] not in yielded:
382
yielded[fn[0]] = 1
383
yield prefix + fn[0], True
384
385
if len(fn)!=1:
386
continue
387
388
modname = inspect.getmodulename(fn[0])
389
if modname=='__init__':
390
continue
391
392
if modname and '.' not in modname and modname not in yielded:
393
yielded[modname] = 1
394
yield prefix + modname, False
395
396
iter_importer_modules.register(zipimporter, iter_zipimport_modules)
397
398
except ImportError:
399
pass
400
401
402
def get_importer(path_item):
403
"""Retrieve a finder for the given path item
404
405
The returned finder is cached in sys.path_importer_cache
406
if it was newly created by a path hook.
407
408
The cache (or part of it) can be cleared manually if a
409
rescan of sys.path_hooks is necessary.
410
"""
411
try:
412
importer = sys.path_importer_cache[path_item]
413
except KeyError:
414
for path_hook in sys.path_hooks:
415
try:
416
importer = path_hook(path_item)
417
sys.path_importer_cache.setdefault(path_item, importer)
418
break
419
except ImportError:
420
pass
421
else:
422
importer = None
423
return importer
424
425
426
def iter_importers(fullname=""):
427
"""Yield finders for the given module name
428
429
If fullname contains a '.', the finders will be for the package
430
containing fullname, otherwise they will be all registered top level
431
finders (i.e. those on both sys.meta_path and sys.path_hooks).
432
433
If the named module is in a package, that package is imported as a side
434
effect of invoking this function.
435
436
If no module name is specified, all top level finders are produced.
437
"""
438
if fullname.startswith('.'):
439
msg = "Relative module name {!r} not supported".format(fullname)
440
raise ImportError(msg)
441
if '.' in fullname:
442
# Get the containing package's __path__
443
pkg_name = fullname.rpartition(".")[0]
444
pkg = importlib.import_module(pkg_name)
445
path = getattr(pkg, '__path__', None)
446
if path is None:
447
return
448
else:
449
yield from sys.meta_path
450
path = sys.path
451
for item in path:
452
yield get_importer(item)
453
454
455
def get_loader(module_or_name):
456
"""Get a "loader" object for module_or_name
457
458
Returns None if the module cannot be found or imported.
459
If the named module is not already imported, its containing package
460
(if any) is imported, in order to establish the package __path__.
461
"""
462
if module_or_name in sys.modules:
463
module_or_name = sys.modules[module_or_name]
464
if module_or_name is None:
465
return None
466
if isinstance(module_or_name, ModuleType):
467
module = module_or_name
468
loader = getattr(module, '__loader__', None)
469
if loader is not None:
470
return loader
471
if getattr(module, '__spec__', None) is None:
472
return None
473
fullname = module.__name__
474
else:
475
fullname = module_or_name
476
return find_loader(fullname)
477
478
479
def find_loader(fullname):
480
"""Find a "loader" object for fullname
481
482
This is a backwards compatibility wrapper around
483
importlib.util.find_spec that converts most failures to ImportError
484
and only returns the loader rather than the full spec
485
"""
486
if fullname.startswith('.'):
487
msg = "Relative module name {!r} not supported".format(fullname)
488
raise ImportError(msg)
489
try:
490
spec = importlib.util.find_spec(fullname)
491
except (ImportError, AttributeError, TypeError, ValueError) as ex:
492
# This hack fixes an impedance mismatch between pkgutil and
493
# importlib, where the latter raises other errors for cases where
494
# pkgutil previously raised ImportError
495
msg = "Error while finding loader for {!r} ({}: {})"
496
raise ImportError(msg.format(fullname, type(ex), ex)) from ex
497
return spec.loader if spec is not None else None
498
499
500
def extend_path(path, name):
501
"""Extend a package's path.
502
503
Intended use is to place the following code in a package's __init__.py:
504
505
from pkgutil import extend_path
506
__path__ = extend_path(__path__, __name__)
507
508
This will add to the package's __path__ all subdirectories of
509
directories on sys.path named after the package. This is useful
510
if one wants to distribute different parts of a single logical
511
package as multiple directories.
512
513
It also looks for *.pkg files beginning where * matches the name
514
argument. This feature is similar to *.pth files (see site.py),
515
except that it doesn't special-case lines starting with 'import'.
516
A *.pkg file is trusted at face value: apart from checking for
517
duplicates, all entries found in a *.pkg file are added to the
518
path, regardless of whether they are exist the filesystem. (This
519
is a feature.)
520
521
If the input path is not a list (as is the case for frozen
522
packages) it is returned unchanged. The input path is not
523
modified; an extended copy is returned. Items are only appended
524
to the copy at the end.
525
526
It is assumed that sys.path is a sequence. Items of sys.path that
527
are not (unicode or 8-bit) strings referring to existing
528
directories are ignored. Unicode items of sys.path that cause
529
errors when used as filenames may cause this function to raise an
530
exception (in line with os.path.isdir() behavior).
531
"""
532
533
if not isinstance(path, list):
534
# This could happen e.g. when this is called from inside a
535
# frozen package. Return the path unchanged in that case.
536
return path
537
538
sname_pkg = name + ".pkg"
539
540
path = path[:] # Start with a copy of the existing path
541
542
parent_package, _, final_name = name.rpartition('.')
543
if parent_package:
544
try:
545
search_path = sys.modules[parent_package].__path__
546
except (KeyError, AttributeError):
547
# We can't do anything: find_loader() returns None when
548
# passed a dotted name.
549
return path
550
else:
551
search_path = sys.path
552
553
for dir in search_path:
554
if not isinstance(dir, str):
555
continue
556
557
finder = get_importer(dir)
558
if finder is not None:
559
portions = []
560
if hasattr(finder, 'find_spec'):
561
spec = finder.find_spec(final_name)
562
if spec is not None:
563
portions = spec.submodule_search_locations or []
564
# Is this finder PEP 420 compliant?
565
elif hasattr(finder, 'find_loader'):
566
_, portions = finder.find_loader(final_name)
567
568
for portion in portions:
569
# XXX This may still add duplicate entries to path on
570
# case-insensitive filesystems
571
if portion not in path:
572
path.append(portion)
573
574
# XXX Is this the right thing for subpackages like zope.app?
575
# It looks for a file named "zope.app.pkg"
576
pkgfile = os.path.join(dir, sname_pkg)
577
if os.path.isfile(pkgfile):
578
try:
579
f = open(pkgfile)
580
except OSError as msg:
581
sys.stderr.write("Can't open %s: %s\n" %
582
(pkgfile, msg))
583
else:
584
with f:
585
for line in f:
586
line = line.rstrip('\n')
587
if not line or line.startswith('#'):
588
continue
589
path.append(line) # Don't check for existence!
590
591
return path
592
593
594
def get_data(package, resource):
595
"""Get a resource from a package.
596
597
This is a wrapper round the PEP 302 loader get_data API. The package
598
argument should be the name of a package, in standard module format
599
(foo.bar). The resource argument should be in the form of a relative
600
filename, using '/' as the path separator. The parent directory name '..'
601
is not allowed, and nor is a rooted name (starting with a '/').
602
603
The function returns a binary string, which is the contents of the
604
specified resource.
605
606
For packages located in the filesystem, which have already been imported,
607
this is the rough equivalent of
608
609
d = os.path.dirname(sys.modules[package].__file__)
610
data = open(os.path.join(d, resource), 'rb').read()
611
612
If the package cannot be located or loaded, or it uses a PEP 302 loader
613
which does not support get_data(), then None is returned.
614
"""
615
616
spec = importlib.util.find_spec(package)
617
if spec is None:
618
return None
619
loader = spec.loader
620
if loader is None or not hasattr(loader, 'get_data'):
621
return None
622
# XXX needs test
623
mod = (sys.modules.get(package) or
624
importlib._bootstrap._load(spec))
625
if mod is None or not hasattr(mod, '__file__'):
626
return None
627
628
# Modify the resource name to be compatible with the loader.get_data
629
# signature - an os.path format "filename" starting with the dirname of
630
# the package's __file__
631
parts = resource.split('/')
632
parts.insert(0, os.path.dirname(mod.__file__))
633
resource_name = os.path.join(*parts)
634
return loader.get_data(resource_name)