Advertisement
Guest User

pep382.py module sketch 2

a guest
Jul 8th, 2011
65
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.88 KB | None | 0 0
  1. # Performance note: this slows import of all builtin/frozen modules, and adds
  2. # an isdir() check for each directory on sys.path that precedes the target.  If
  3. # you have a lot of (directory) path entries and import a lot of stuff from
  4. # far down the path, this may be very slow if (e.g.) those paths are over a
  5. # network.  So, we operate in an "explicit only" mode by default, where only
  6. # registered namespace packages get the extra processing this way.
  7.  
  8. __all__ = [
  9.     'is_namespace', 'register_namespace', 'namespace_subpath',
  10.     'iter_namespaces', 'extend_namespaces', 'meta_importer',
  11. ]
  12.  
  13. import sys, new, zipimport, os
  14.  
  15. try:
  16.     _make_set = set
  17. except NameError:
  18.     _make_set = dict.fromkeys   # Python 2.3, use a dict instead of a set
  19.  
  20. if not hasattr(sys, 'namespace_packages'):
  21.     sys.namespace_packages = _make_set([])
  22.  
  23. def is_namespace(pkgname):
  24.     return pkgname in sys.namespace_packages
  25.  
  26. def register_namespace(pkgname):
  27.     if not is_namespace(pkgname):
  28.         sys.namespace_packages.update(_make_set([pkgname]))
  29.     # Make sure we can process at least the registered namespace(s)
  30.     meta_importer.install(True)
  31.  
  32. def get_importer(entry):
  33.     global ImpImporter, get_importer
  34.     try:
  35.         from pkgutil import get_importer, ImpImporter
  36.     except ImportError:
  37.         from pkg_resources import get_importer, ImpWrapper as ImpImporter
  38.     ImpImporter.namespace_subpath = _fs_namespace_subpath
  39.     return get_importer(entry)
  40.  
  41.  
  42. def namespace_subpath(finder, fullname):
  43.     if hasattr(finder, 'namespace_subpath'):
  44.         return finder.namespace_subpath(fullname)
  45.     elif isinstance(finder, zipimport.zipimporter):
  46.         return _zip_namespace_subpath(finder, fullname)
  47.  
  48. def iter_namespaces(parent=''):
  49.     """Iterate over registered namespaces that are a child of `parent`"""
  50.     parent = parent.split('.')
  51.     for name in sys.namespace_packages:
  52.         path = name.split('.')
  53.         path.pop()
  54.         if path==parent:
  55.             yield name
  56.  
  57. def extend_namespaces(path_entry, parent=''):
  58.     """Extend imported namespaces' paths with subpaths of `path_entry`"""
  59.     import imp
  60.     imp.acquire_lock()
  61.     try:
  62.         finder = get_importer(path_entry)
  63.  
  64.         for name in iter_namespaces(parent):
  65.             if name not in sys.modules:
  66.                 # only extend already-imported namespaces
  67.                 continue
  68.  
  69.             # Is there a portion for this namespace under path_entry?
  70.             subpath = namespace_subpath(finder, name)
  71.             if subpath is None:
  72.                 continue
  73.  
  74.             # Yes, so extend its __path__, and recurse to child namespaces
  75.             path = getattr(sys.modules[name], '__path__', [])
  76.             if subpath not in path:
  77.                 path.append(subpath)
  78.                 extend_namespaces(subpath, name)
  79.  
  80.     finally:
  81.         imp.release_lock()
  82.  
  83. class NamespaceMetaImporter:
  84.     """Implement PEP 382 namespace protocol for Python 2.x"""
  85.     registration_required = True
  86.  
  87.     def find_module(self, name, path):
  88.         is_ns = is_namespace(name)
  89.         if self.registration_required and not is_ns:
  90.             # We're only running for explicitly-registered packages, fall back
  91.             # to normal import
  92.             return None
  93.         loader = None
  94.         nspth = []
  95.         for entry in path:
  96.             finder = get_importer(entry)
  97.             subpath = namespace_subpath(finder, name)
  98.             if subpath is not None:
  99.                 parents.append(finder)
  100.                 nspth.append(subpath)
  101.                 is_ns = True
  102.                 if loader is None:
  103.                     loader = finder.find_module(name)
  104.                     loader = self._wrap_loader(loader, subpath, name)
  105.             elif not is_ns:
  106.                 # only search normal modules until first ns found!
  107.                 loader = finder.find_module(name)
  108.                 if not nspth and loader is not None:
  109.                     # Found regular module or package first, return it now
  110.                     return loader
  111.         if not nspth:
  112.             # Nothing found, fall through to normal import process
  113.             return None
  114.  
  115.         register_namespace(name)    # make sure we're in sys.namespace_packages
  116.         nspkg = sys.modules.setdefault(name, new.module(name))
  117.         nspkg.__path__ = nspth
  118.         if loader is not None:
  119.             return loader
  120.  
  121.         # Namespace package with no __init__ - return dummy loader
  122.         return self
  123.  
  124.     def load_module(self, name):
  125.         # We already initialized an empty namespace package, so just return it
  126.         return sys.modules[name]
  127.  
  128.     def wrap_loader(self, loader, subpath, name):
  129.         if loader is None:
  130.             return None
  131.         elif hasattr(loader, 'is_package') and not loader.is_package(name):
  132.             # It's a module, not a package: ignore it
  133.             return None
  134.         finder = get_importer(subpath)
  135.         subloader = finder.find_module(name+'.__init__')
  136.         if subloader is None:
  137.             # Guess it must be a module after all; skip it
  138.             return None
  139.  
  140.         # Ok, now we have a parent loader (which we want to set as the
  141.         # __loader__, if we set one), and a subloader, which we'll use to fetch
  142.         # the __init__ code from, assuming it has a get_code() method:
  143.  
  144.         if isinstance(finder, ImpImporter) or not hasattr(subloader, 'get_code'):
  145.             # Use as-is.  This should work for pkgutil and pkg_resources'
  146.             # ImpLoader classes, or anything else that wraps imp in similar
  147.             # fashion, but could have weird consequences for specialized
  148.             # importers.  (OTOH, they probably don't support checking namespace
  149.             # existence, so we probably won't get here for them anyway!)
  150.             return subloader
  151.  
  152.         # This should works for zipimport and most other things with a get_code
  153.         return CodeLoader(loader, subloader, name+'.__init__')
  154.  
  155.     def install(self, require_registration=False):
  156.         """Put this meta-importer on sys.meta_path"""
  157.         if self not in sys.meta_path:
  158.             # We install at the end, so other meta-hooks take precedence
  159.             sys.meta_path.append(self)
  160.         if not require_registration:
  161.             self.require_registration = False
  162.  
  163.  
  164.  
  165.     def iter_modules(self, prefix=''):
  166.         # This is just here to make sure that an appropriate iter_modules()
  167.         # hook is registered for zipimporter objects
  168.         try:
  169.             from pkgutil import iter_importer_modules, ImpImporter
  170.         except ImportError:
  171.             pass
  172.         else:
  173.             iter_importer_modules.register(ImpImporter, _fs_iter_modules)
  174.             iter_importer_modules.register(
  175.                 zipimport.zipimporter, _zip_iter_modules
  176.             )
  177.         return []
  178.  
  179.  
  180. meta_importer = NamespaceMetaImporter()
  181.  
  182.  
  183. class CodeLoader(object):
  184.     """Run __init__ code in package, without erasing __path__"""
  185.  
  186.     def __init__(self, loader, subloader, subname):
  187.         self.loader = loader
  188.         self.subloader = subloader
  189.         self.subname = subname
  190.  
  191.     def load_module(self, fullname):
  192.         pkg = sys.modules[fullname]
  193.         if self.loader is not None:
  194.             pkg.__loader__ = self.loader
  195.         code = self.subloader.get_code(self.subname)
  196.         fn = getattr(code, 'co_filename', None)
  197.         if fn:
  198.             pkg.__file__ = fn
  199.         exec code in pkg.__dict__
  200.         return pkg
  201.  
  202.  
  203.  
  204.  
  205.  
  206. def _fs_namespace_subpath(imp_importer, fullname):
  207.     subname = fullname.split('.')[-1]
  208.     prefix = os.path.join(imp_importer.path, subname)
  209.     if os.path.isdir(prefix):
  210.         for filename in listdir(prefix):
  211.             if filename.endswith('.ns'):
  212.                 return prefix
  213.     return None # not a namespace
  214.  
  215. def _zip_namespace_subpath(zipimporter, fullname):
  216.     subname = fullname.split('.')[-1]
  217.     prefix = zipimporter.prefix + subname
  218.     for filename in zipimport._zip_directory_cache[zipimporter.archive]:
  219.         if filename.endswith('.ns') and os.path.dirname(filename)==prefix:
  220.             return zipimporter.archive + os.sep + prefix
  221.     return None # not a namespace
  222.  
  223. def _fs_iter_modules(self, prefix=''):
  224.     # Namespace-enhanced iter_modules for filesystem imports
  225.  
  226.     if self.path is not None and os.path.isdir(self.path):
  227.         from re import compile
  228.         # XXX should support unicode identifiers on Python 3
  229.         isident = re.compile(r'^[A-Za-z_][A-Za-z_0-9]*$').match
  230.  
  231.         for base, dirs, files in os.walk(self.path):
  232.             if base==self.path:
  233.                 # Walk possible packages
  234.                 dirs[:] = (d for d in dirs if isident(d))
  235.             else:
  236.                 # Don't traverse subdirs of those packages
  237.                 del dirs[:]
  238.  
  239.                 for f in files:
  240.                     if f.endswith('.ns'):
  241.                         yield prefix + os.path.basename(base), True
  242.  
  243.         # Now add the standard modules and packages
  244.         for t in self.iter_modules(prefix):
  245.             yield t
  246.  
  247. def _zip_iter_modules(importer, prefix=''):
  248.     # Namespace-enhanced iter_modules for zip imports
  249.  
  250.     dirlist = zipimport._zip_directory_cache[importer.archive].keys()
  251.     dirlist.sort()
  252.     _prefix = importer.prefix
  253.     plen = len(_prefix)
  254.     yielded = {}
  255.     import inspect
  256.     for fn in dirlist:
  257.         if not fn.startswith(_prefix):
  258.             continue
  259.         fn = fn[plen:].split(os.sep)
  260.         if len(fn)==2 and (
  261.             fn[1].startsswith('__init__.py') or fn[1].endswith('.ns')
  262.         ):
  263.             if fn[0] not in yielded:
  264.                 yielded[fn[0]] = 1
  265.                 yield prefix + fn[0], True
  266.         if len(fn)!=1:
  267.             continue
  268.         modname = inspect.getmodulename(fn[0])
  269.         if modname=='__init__':
  270.             continue
  271.         if modname and '.' not in modname and modname not in yielded:
  272.             yielded[modname] = 1
  273.             yield prefix + modname, False
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement