summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPJ Eby <distutils-sig@python.org>2005-05-25 03:06:53 +0000
committerPJ Eby <distutils-sig@python.org>2005-05-25 03:06:53 +0000
commit30a898c612be3c66e976dcd95817501a706814b3 (patch)
treed412e46e5dd0bd2be514314274bee1b3f3c7fa96
parente3437307c3d19ddb753f4fa5d3b3a6aefdfa33cc (diff)
downloadpython-setuptools-git-30a898c612be3c66e976dcd95817501a706814b3.tar.gz
Implement draft support for namespace packages, both declaring them
initially and fixing them up when new eggs are added to sys.path. At the moment, all namespace packages are fixed up whenever any new egg is added to sys.path, but this might not scale well if there are lots of eggs and lots of namespace packages. Perhaps we should limit namespace fixups to namespace packages explicitly declared in the egg? --HG-- branch : setuptools extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/trunk/setuptools%4041014
-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]