summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pkg_resources.py159
1 files changed, 141 insertions, 18 deletions
diff --git a/pkg_resources.py b/pkg_resources.py
index 01399db2..4dfa927e 100644
--- a/pkg_resources.py
+++ b/pkg_resources.py
@@ -742,7 +742,7 @@ def register_finder(importer_type, distribution_finder):
"""Register `distribution_finder` to find distributions in sys.path items
`importer_type` is the type or class of a PEP 302 "Importer" (sys.path item
- handler), and `distribution_finder_type` is a callable that, passed a path
+ handler), and `distribution_finder` is a callable that, passed a path
item and the importer instance, yields ``Distribution`` instances found on
that path item. See ``pkg_resources.find_on_path`` for an example."""
_distribution_finders[importer_type] = distribution_finder
@@ -818,6 +818,129 @@ register_finder(ImpWrapper,find_on_path)
+_namespace_handlers = {}
+_namespace_packages = {}
+
+def register_namespace_handler(importer_type, namespace_handler):
+ """Register `namespace_handler` to declare namespace packages
+
+ `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item
+ handler), and `namespace_handler` is a callable like this::
+
+ def namespace_handler(importer,path_entry,moduleName,module):
+ # return a path_entry to use for child packages
+
+ Namespace handlers are only called if the importer object has already
+ agreed that it can handle the relevant path item, and they should only
+ return a subpath if the module __path__ does not already contain an
+ equivalent subpath. For an example namespace handler, see
+ ``pkg_resources.file_ns_handler``.
+ """
+ _namespace_handlers[importer_type] = namespace_handler
+
+
+def _handle_ns(packageName, path_item):
+ """Ensure that named package includes a subpath of path_item (if needed)"""
+ importer = get_importer(path_item)
+ loader = importer.find_module(packageName)
+ if loader is None:
+ return None
+
+ module = sys.modules.get(packageName) or loader.load_module(packageName)
+ if not hasattr(module,'__path__'):
+ raise TypeError("Not a package:", packageName)
+
+ handler = _find_adapter(_distribution_finders, importer)
+ subpath = handler(importer,path_item,packageName,module)
+
+ if subpath is not None:
+ module.__path__.append(subpath)
+
+ return subpath
+
+
+def declare_namespace(packageName):
+ """Declare that package 'packageName' is a namespace package"""
+
+ # XXX nslock.acquire()
+ try:
+ if packageName in _namespace_packages:
+ return
+
+ path, parent = sys.path, None
+ if '.' in packageName:
+ parent = '.'.join(package.split('.')[:-1])
+ declare_namespace(parent)
+ __import__(parent)
+ try:
+ path = sys.modules[parent].__path__
+ except AttributeError:
+ raise TypeError("Not a package:", parent)
+
+ for path_item in path:
+ # Ensure all the parent's path items are reflected in the child,
+ # if they apply
+ _handle_ns(packageName, path_item)
+
+ # Track what packages are namespaces, so when new path items are added,
+ # they can be updated
+ _namespace_packages.setdefault(parent,[]).append(packageName)
+ _namespace_packages.setdefault(packageName,[])
+
+ finally:
+ pass # XXX nslock.release()
+
+def fixup_namespace_packages(path_item, parent=None):
+ """Ensure that previously-declared namespace packages include path_item"""
+ # XXX nslock.acquire()
+ try:
+ for package in _namespace_packages.get(parent,()):
+ subpath = _handle_ns(package, path_item)
+ if subpath: fixup_namespace_packages(subpath,package)
+ finally:
+ pass # XXX nslock.release()
+
+def file_ns_handler(importer, path_item, packageName, module):
+ """Compute an ns-package subpath for a filesystem or zipfile importer"""
+
+ subpath = os.path.join(path_item, packageName.split('.')[-1])
+ normalized = os.path.normpath(os.path.normcase(subpath))
+ for item in module.__path__:
+ if os.path.normpath(os.path.normcase(item))==normalized:
+ break
+ else:
+ # Only return the path if it's not already there
+ return subpath
+
+register_namespace_handler(ImpWrapper,file_ns_handler)
+register_namespace_handler(zipimport.zipimporter,file_ns_handler)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
def yield_lines(strs):
"""Yield non-empty/non-comment lines of a ``basestring`` or sequence"""
if isinstance(strs,basestring):
@@ -988,11 +1111,10 @@ class Distribution(object):
return self.__dep_map
except AttributeError:
dm = self.__dep_map = {None: []}
- if self.metadata.has_metadata('depends.txt'):
- for section,contents in split_sections(
- self.metadata.get_metadata_lines('depends.txt')
- ):
- dm[section] = list(parse_requirements(contents))
+ for section,contents in split_sections(
+ self._get_metadata('depends.txt')
+ ):
+ dm[section] = list(parse_requirements(contents))
return dm
_dep_map = property(_dep_map)
@@ -1010,18 +1132,19 @@ class Distribution(object):
raise InvalidOption("No such option", self, opt)
return deps
+ def _get_metadata(self,name):
+ if self.metadata.has_metadata(name):
+ for line in self.metadata.get_metadata_lines(name):
+ yield line
+
def install_on(self,path=None):
- # XXX this needs to interface with namespace packages and such
+ """Ensure distribution is importable on `path` (default=sys.path)"""
if path is None: path = sys.path
if self.path not in path:
path.append(self.path)
-
-
-def _sort_dists(dists):
- tmp = [(dist.version,dist) for dist in dists]
- tmp.sort()
- dists[::-1] = [d for v,d in tmp]
-
+ if path is sys.path:
+ fixup_namespace_packages(self.path)
+ map(declare_namespace, self._get_metadata('namespace_packages.txt'))
def parse_requirements(strs):
"""Yield ``Requirement`` objects for each specification in `strs`
@@ -1083,10 +1206,10 @@ def parse_requirements(strs):
yield Requirement(distname.replace('_','-'), specs, options)
-
-
-
-
+def _sort_dists(dists):
+ tmp = [(dist.version,dist) for dist in dists]
+ tmp.sort()
+ dists[::-1] = [d for v,d in tmp]