summaryrefslogtreecommitdiff
path: root/pkg_resources.py
diff options
context:
space:
mode:
Diffstat (limited to 'pkg_resources.py')
-rw-r--r--pkg_resources.py238
1 files changed, 197 insertions, 41 deletions
diff --git a/pkg_resources.py b/pkg_resources.py
index abb90b1a..2ba0febf 100644
--- a/pkg_resources.py
+++ b/pkg_resources.py
@@ -14,6 +14,7 @@ method.
"""
import sys, os, zipimport, time, re, imp
+from urlparse import urlparse, urlunparse
try:
frozenset
@@ -21,13 +22,27 @@ except NameError:
from sets import ImmutableSet as frozenset
# capture these to bypass sandboxing
-from os import utime, rename, unlink, mkdir
+from os import utime
+try:
+ from os import mkdir, rename, unlink
+ WRITE_SUPPORT = True
+except ImportError:
+ # no write support, probably under GAE
+ WRITE_SUPPORT = False
+
from os import open as os_open
from os.path import isdir, split
+# Avoid try/except due to potential problems with delayed import mechanisms.
+if sys.version_info >= (3, 3) and sys.implementation.name == "cpython":
+ import importlib._bootstrap as importlib_bootstrap
+else:
+ importlib_bootstrap = None
def _bypass_ensure_directory(name, mode=0777):
# Sandbox-bypassing version of ensure_directory()
+ if not WRITE_SUPPORT:
+ raise IOError('"os.mkdir" not supported on this platform.')
dirname, filename = split(name)
if dirname and filename and not isdir(dirname):
_bypass_ensure_directory(dirname)
@@ -73,8 +88,6 @@ _sget_none = _sset_none = lambda *args: None
-
-
def get_supported_platform():
"""Return this platform's maximum compatible version.
@@ -159,7 +172,8 @@ __all__ = [
]
class ResolutionError(Exception):
"""Abstract base for dependency resolution errors"""
- def __repr__(self): return self.__class__.__name__+repr(self.args)
+ def __repr__(self):
+ return self.__class__.__name__+repr(self.args)
class VersionConflict(ResolutionError):
"""An already-installed version conflicts with the requested version"""
@@ -170,6 +184,7 @@ class DistributionNotFound(ResolutionError):
class UnknownExtra(ResolutionError):
"""Distribution doesn't have an "extra feature" of the given name"""
_provider_factories = {}
+
PY_MAJOR = sys.version[:3]
EGG_DIST = 3
BINARY_DIST = 2
@@ -200,8 +215,19 @@ def get_provider(moduleOrReq):
def _macosx_vers(_cache=[]):
if not _cache:
- from platform import mac_ver
- _cache.append(mac_ver()[0].split('.'))
+ import platform
+ version = platform.mac_ver()[0]
+ # fallback for MacPorts
+ if version == '':
+ import plistlib
+ plist = '/System/Library/CoreServices/SystemVersion.plist'
+ if os.path.exists(plist):
+ if hasattr(plistlib, 'readPlist'):
+ plist_content = plistlib.readPlist(plist)
+ if 'ProductVersion' in plist_content:
+ version = plist_content['ProductVersion']
+
+ _cache.append(version.split('.'))
return _cache[0]
def _macosx_arch(machine):
@@ -213,7 +239,11 @@ def get_build_platform():
XXX Currently this is the same as ``distutils.util.get_platform()``, but it
needs some hacks for Linux and Mac OS X.
"""
- from distutils.util import get_platform
+ try:
+ from distutils.util import get_platform
+ except ImportError:
+ from sysconfig import get_platform
+
plat = get_platform()
if sys.platform == "darwin" and not plat.startswith('macosx-'):
try:
@@ -237,8 +267,6 @@ get_platform = get_build_platform # XXX backward compat
-
-
def compatible_platforms(provided,required):
"""Can code for the `provided` platform run on the `required` platform?
@@ -353,15 +381,6 @@ class IMetadataProvider:
-
-
-
-
-
-
-
-
-
class IResourceProvider(IMetadataProvider):
"""An object that provides access to package resources"""
@@ -493,6 +512,10 @@ class WorkingSet(object):
"""
seen = {}
for item in self.entries:
+ if item not in self.entry_keys:
+ # workaround a cache issue
+ continue
+
for key in self.entry_keys[item]:
if key not in seen:
seen[key]=1
@@ -557,7 +580,13 @@ class WorkingSet(object):
env = Environment(self.entries)
dist = best[req.key] = env.best_match(req, self, installer)
if dist is None:
- raise DistributionNotFound(req) # XXX put more info here
+ #msg = ("The '%s' distribution was not found on this "
+ # "system, and is required by this application.")
+ #raise DistributionNotFound(msg % req)
+
+ # unfortunately, zc.buildout uses a str(err)
+ # to get the name of the distribution here..
+ raise DistributionNotFound(req)
to_activate.append(dist)
if dist not in req:
# Oops, the "best" so far conflicts with a dependency
@@ -578,7 +607,7 @@ class WorkingSet(object):
Environment(plugin_dirlist)
)
map(working_set.add, distributions) # add plugins+libs to sys.path
- print "Couldn't load", errors # display errors
+ print 'Could not load', errors # display errors
The `plugin_env` should be an ``Environment`` instance that contains
only distributions that are in the project's "plugin directory" or
@@ -1167,10 +1196,16 @@ class NullProvider:
def has_metadata(self, name):
return self.egg_info and self._has(self._fn(self.egg_info,name))
- def get_metadata(self, name):
- if not self.egg_info:
- return ""
- return self._get(self._fn(self.egg_info,name))
+ if sys.version_info <= (3,):
+ def get_metadata(self, name):
+ if not self.egg_info:
+ return ""
+ return self._get(self._fn(self.egg_info,name))
+ else:
+ def get_metadata(self, name):
+ if not self.egg_info:
+ return ""
+ return self._get(self._fn(self.egg_info,name)).decode("utf-8")
def get_metadata_lines(self, name):
return yield_lines(self.get_metadata(name))
@@ -1288,6 +1323,9 @@ class DefaultProvider(EggProvider):
register_loader_type(type(None), DefaultProvider)
+if importlib_bootstrap is not None:
+ register_loader_type(importlib_bootstrap.SourceFileLoader, DefaultProvider)
+
class EmptyProvider(NullProvider):
"""Provider that returns nothing for all requests"""
@@ -1364,6 +1402,10 @@ class ZipProvider(EggProvider):
timestamp = time.mktime(date_time)
try:
+ if not WRITE_SUPPORT:
+ raise IOError('"os.rename" and "os.unlink" are not supported '
+ 'on this platform')
+
real_path = manager.get_cache_path(
self.egg_name, self._parts(zip_path)
)
@@ -1489,7 +1531,10 @@ class FileMetadata(EmptyProvider):
def get_metadata(self,name):
if name=='PKG-INFO':
- return open(self.path,'rU').read()
+ f = open(self.path,'rU')
+ metadata = f.read()
+ f.close()
+ return metadata
raise KeyError("No metadata except PKG-INFO is available")
def get_metadata_lines(self,name):
@@ -1694,7 +1739,7 @@ def find_on_path(importer, path_item, only=False):
# scan for .egg and .egg-info in directory
for entry in os.listdir(path_item):
lower = entry.lower()
- if lower.endswith('.egg-info'):
+ if lower.endswith('.egg-info') or lower.endswith('.dist-info'):
fullpath = os.path.join(path_item, entry)
if os.path.isdir(fullpath):
# egg-info directory, allow getting metadata
@@ -1708,15 +1753,24 @@ def find_on_path(importer, path_item, only=False):
for dist in find_distributions(os.path.join(path_item, entry)):
yield dist
elif not only and lower.endswith('.egg-link'):
- for line in file(os.path.join(path_item, entry)):
+ entry_file = open(os.path.join(path_item, entry))
+ try:
+ entry_lines = entry_file.readlines()
+ finally:
+ entry_file.close()
+ for line in entry_lines:
if not line.strip(): continue
for item in find_distributions(os.path.join(path_item,line.rstrip())):
yield item
break
register_finder(ImpWrapper,find_on_path)
-_declare_state('dict', _namespace_handlers = {})
-_declare_state('dict', _namespace_packages = {})
+if importlib_bootstrap is not None:
+ register_finder(importlib_bootstrap.FileFinder, find_on_path)
+
+_declare_state('dict', _namespace_handlers={})
+_declare_state('dict', _namespace_packages={})
+
def register_namespace_handler(importer_type, namespace_handler):
"""Register `namespace_handler` to declare namespace packages
@@ -1768,7 +1822,8 @@ def declare_namespace(packageName):
if '.' in packageName:
parent = '.'.join(packageName.split('.')[:-1])
declare_namespace(parent)
- __import__(parent)
+ if parent not in _namespace_packages:
+ __import__(parent)
try:
path = sys.modules[parent].__path__
except AttributeError:
@@ -1812,6 +1867,9 @@ def file_ns_handler(importer, path_item, packageName, module):
register_namespace_handler(ImpWrapper,file_ns_handler)
register_namespace_handler(zipimport.zipimporter,file_ns_handler)
+if importlib_bootstrap is not None:
+ register_namespace_handler(importlib_bootstrap.FileFinder, file_ns_handler)
+
def null_ns_handler(importer, path_item, packageName, module):
return None
@@ -2040,12 +2098,19 @@ class EntryPoint(object):
parse_map = classmethod(parse_map)
-
-
+def _remove_md5_fragment(location):
+ if not location:
+ return ''
+ parsed = urlparse(location)
+ if parsed[-1].startswith('md5='):
+ return urlunparse(parsed[:-1] + ('',))
+ return location
class Distribution(object):
"""Wrap an actual or potential sys.path entry w/metadata"""
+ PKG_INFO = 'PKG-INFO'
+
def __init__(self,
location=None, metadata=None, project_name=None, version=None,
py_version=PY_MAJOR, platform=None, precedence = EGG_DIST
@@ -2063,27 +2128,47 @@ class Distribution(object):
def from_location(cls,location,basename,metadata=None,**kw):
project_name, version, py_version, platform = [None]*4
basename, ext = os.path.splitext(basename)
- if ext.lower() in (".egg",".egg-info"):
+ if ext.lower() in _distributionImpl:
+ # .dist-info gets much metadata differently
match = EGG_NAME(basename)
if match:
project_name, version, py_version, platform = match.group(
'name','ver','pyver','plat'
)
+ cls = _distributionImpl[ext.lower()]
return cls(
location, metadata, project_name=project_name, version=version,
py_version=py_version, platform=platform, **kw
)
from_location = classmethod(from_location)
+
hashcmp = property(
lambda self: (
- getattr(self,'parsed_version',()), self.precedence, self.key,
- -len(self.location or ''), self.location, self.py_version,
+ getattr(self,'parsed_version',()),
+ self.precedence,
+ self.key,
+ _remove_md5_fragment(self.location),
+ self.py_version,
self.platform
)
)
- def __cmp__(self, other): return cmp(self.hashcmp, other)
def __hash__(self): return hash(self.hashcmp)
+ def __lt__(self, other):
+ return self.hashcmp < other.hashcmp
+ def __le__(self, other):
+ return self.hashcmp <= other.hashcmp
+ def __gt__(self, other):
+ return self.hashcmp > other.hashcmp
+ def __ge__(self, other):
+ return self.hashcmp >= other.hashcmp
+ def __eq__(self, other):
+ if not isinstance(other, self.__class__):
+ # It's not a Distribution, so they are not equal
+ return False
+ return self.hashcmp == other.hashcmp
+ def __ne__(self, other):
+ return not self == other
# These properties have to be lazy so that we don't have to load any
# metadata until/unless it's actually needed. (i.e., some distributions
@@ -2113,13 +2198,13 @@ class Distribution(object):
try:
return self._version
except AttributeError:
- for line in self._get_metadata('PKG-INFO'):
+ for line in self._get_metadata(self.PKG_INFO):
if line.lower().startswith('version:'):
self._version = safe_version(line.split(':',1)[1].strip())
return self._version
else:
raise ValueError(
- "Missing 'Version:' header and/or PKG-INFO file", self
+ "Missing 'Version:' header and/or %s file" % self.PKG_INFO, self
)
version = property(version)
@@ -2301,9 +2386,11 @@ class Distribution(object):
or modname in _namespace_packages
):
continue
-
+ if modname in ('pkg_resources', 'setuptools', 'site'):
+ continue
fn = getattr(sys.modules[modname], '__file__', None)
- if fn and (normalize_path(fn).startswith(loc) or fn.startswith(loc)):
+ if fn and (normalize_path(fn).startswith(loc) or
+ fn.startswith(self.location)):
continue
issue_warning(
"Module %s was already imported from %s, but %s is being added"
@@ -2337,6 +2424,74 @@ class Distribution(object):
extras = property(extras)
+class DistInfoDistribution(Distribution):
+ """Wrap an actual or potential sys.path entry w/metadata, .dist-info style"""
+ PKG_INFO = 'METADATA'
+ EQEQ = re.compile(r"([\(,])\s*(\d.*?)\s*([,\)])")
+
+ @property
+ def _parsed_pkg_info(self):
+ """Parse and cache metadata"""
+ try:
+ return self._pkg_info
+ except AttributeError:
+ from email.parser import Parser
+ self._pkg_info = Parser().parsestr(self.get_metadata(self.PKG_INFO))
+ return self._pkg_info
+
+ @property
+ def _dep_map(self):
+ try:
+ return self.__dep_map
+ except AttributeError:
+ self.__dep_map = self._compute_dependencies()
+ return self.__dep_map
+
+ def _preparse_requirement(self, requires_dist):
+ """Convert 'Foobar (1); baz' to ('Foobar ==1', 'baz')
+ Split environment marker, add == prefix to version specifiers as
+ necessary, and remove parenthesis.
+ """
+ parts = requires_dist.split(';', 1) + ['']
+ distvers = parts[0].strip()
+ mark = parts[1].strip()
+ distvers = re.sub(self.EQEQ, r"\1==\2\3", distvers)
+ distvers = distvers.replace('(', '').replace(')', '')
+ return (distvers, mark)
+
+ def _compute_dependencies(self):
+ """Recompute this distribution's dependencies."""
+ from _markerlib import compile as compile_marker
+ dm = self.__dep_map = {None: []}
+
+ reqs = []
+ # Including any condition expressions
+ for req in self._parsed_pkg_info.get_all('Requires-Dist') or []:
+ distvers, mark = self._preparse_requirement(req)
+ parsed = parse_requirements(distvers).next()
+ parsed.marker_fn = compile_marker(mark)
+ reqs.append(parsed)
+
+ def reqs_for_extra(extra):
+ for req in reqs:
+ if req.marker_fn(override={'extra':extra}):
+ yield req
+
+ common = frozenset(reqs_for_extra(None))
+ dm[None].extend(common)
+
+ for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []:
+ extra = safe_extra(extra.strip())
+ dm[extra] = list(frozenset(reqs_for_extra(extra)) - common)
+
+ return dm
+
+
+_distributionImpl = {'.egg': Distribution,
+ '.egg-info': Distribution,
+ '.dist-info': DistInfoDistribution }
+
+
def issue_warning(*args,**kw):
level = 1
g = globals()
@@ -2485,8 +2640,9 @@ class Requirement:
elif isinstance(item,basestring):
item = parse_version(item)
last = None
+ compare = lambda a, b: (a > b) - (a < b) # -1, 0, 1
for parsed,trans,op,ver in self.index:
- action = trans[cmp(item,parsed)]
+ action = trans[compare(item,parsed)] # Indexing: 0, 1, -1
if action=='F': return False
elif action=='T': return True
elif action=='+': last = True