aboutsummaryrefslogtreecommitdiffstats
path: root/pkg_resources.py
diff options
context:
space:
mode:
Diffstat (limited to 'pkg_resources.py')
-rw-r--r--pkg_resources.py290
1 files changed, 199 insertions, 91 deletions
diff --git a/pkg_resources.py b/pkg_resources.py
index 41c73d42..bdd738f2 100644
--- a/pkg_resources.py
+++ b/pkg_resources.py
@@ -41,14 +41,10 @@ if sys.version_info >= (3, 3) and sys.implementation.name == "cpython":
else:
importlib_bootstrap = None
-# This marker is used to simplify the process that checks is the
-# setuptools package was installed by the Setuptools project
-# or by the Distribute project, in case Setuptools creates
-# a distribution with the same version.
-#
-# The bootstrapping script for instance, will check if this
-# attribute is present to decide wether to reinstall the package
-_distribute = True
+try:
+ import parser
+except ImportError:
+ pass
def _bypass_ensure_directory(name, mode=0777):
# Sandbox-bypassing version of ensure_directory()
@@ -98,6 +94,7 @@ _sget_none = _sset_none = lambda *args: None
+
def get_supported_platform():
"""Return this platform's maximum compatible version.
@@ -162,7 +159,7 @@ __all__ = [
# Parsing functions and string utilities
'parse_requirements', 'parse_version', 'safe_name', 'safe_version',
'get_platform', 'compatible_platforms', 'yield_lines', 'split_sections',
- 'safe_extra', 'to_filename',
+ 'safe_extra', 'to_filename', 'invalid_marker', 'evaluate_marker',
# filesystem utilities
'ensure_directory', 'normalize_path',
@@ -250,9 +247,10 @@ def get_build_platform():
needs some hacks for Linux and Mac OS X.
"""
try:
- from distutils.util import get_platform
- except ImportError:
+ # Python 2.7 or >=3.2
from sysconfig import get_platform
+ except ImportError:
+ from distutils.util import get_platform
plat = get_platform()
if sys.platform == "darwin" and not plat.startswith('macosx-'):
@@ -271,6 +269,12 @@ macosVersionString = re.compile(r"macosx-(\d+)\.(\d+)-(.*)")
darwinVersionString = re.compile(r"darwin-(\d+)\.(\d+)\.(\d+)-(.*)")
get_platform = get_build_platform # XXX backward compat
+
+
+
+
+
+
def compatible_platforms(provided,required):
"""Can code for the `provided` platform run on the `required` platform?
@@ -446,7 +450,7 @@ class WorkingSet(object):
def add_entry(self, entry):
"""Add a path item to ``.entries``, finding any distributions on it
- ``find_distributions(entry,True)`` is used to find distributions
+ ``find_distributions(entry, True)`` is used to find distributions
corresponding to the path entry, and they are added. `entry` is
always appended to ``.entries``, even if it is already present.
(This is because ``sys.path`` can contain the same value more than
@@ -553,7 +557,7 @@ class WorkingSet(object):
keys2.append(dist.key)
self._added_new(dist)
- def resolve(self, requirements, env=None, installer=None, replacement=True):
+ def resolve(self, requirements, env=None, installer=None):
"""List all distributions needed to (recursively) meet `requirements`
`requirements` must be a sequence of ``Requirement`` objects. `env`,
@@ -572,9 +576,6 @@ class WorkingSet(object):
while requirements:
req = requirements.pop(0) # process dependencies breadth-first
- if _override_setuptools(req) and replacement:
- req = Requirement.parse('distribute')
-
if req in processed:
# Ignore cyclic or redundant dependencies
continue
@@ -694,7 +695,6 @@ class WorkingSet(object):
activated to fulfill the requirements; all relevant distributions are
included, even if they were already activated in this working set.
"""
-
needed = self.resolve(parse_requirements(requirements))
for dist in needed:
@@ -702,7 +702,6 @@ class WorkingSet(object):
return needed
-
def subscribe(self, callback):
"""Invoke `callback` for all distributions (including existing ones)"""
if callback in self.callbacks:
@@ -711,14 +710,15 @@ class WorkingSet(object):
for dist in self:
callback(dist)
-
def _added_new(self, dist):
for callback in self.callbacks:
callback(dist)
def __getstate__(self):
- return (self.entries[:], self.entry_keys.copy(), self.by_key.copy(),
- self.callbacks[:])
+ return (
+ self.entries[:], self.entry_keys.copy(), self.by_key.copy(),
+ self.callbacks[:]
+ )
def __setstate__(self, (entries, keys, by_key, callbacks)):
self.entries = entries[:]
@@ -727,8 +727,6 @@ class WorkingSet(object):
self.callbacks = callbacks[:]
-
-
class Environment(object):
"""Searchable snapshot of distributions on a search path"""
@@ -1205,6 +1203,160 @@ def to_filename(name):
+_marker_names = {
+ 'os': ['name'], 'sys': ['platform'],
+ 'platform': ['version','machine','python_implementation'],
+ 'python_version': [], 'python_full_version': [], 'extra':[],
+}
+
+_marker_values = {
+ 'os_name': lambda: os.name,
+ 'sys_platform': lambda: sys.platform,
+ 'python_full_version': lambda: sys.version.split()[0],
+ 'python_version': lambda:'%s.%s' % (sys.version_info[0], sys.version_info[1]),
+ 'platform_version': lambda: _platinfo('version'),
+ 'platform_machine': lambda: _platinfo('machine'),
+ 'python_implementation': lambda: _platinfo('python_implementation') or _pyimp(),
+}
+
+def _platinfo(attr):
+ try:
+ import platform
+ except ImportError:
+ return ''
+ return getattr(platform, attr, lambda:'')()
+
+def _pyimp():
+ if sys.platform=='cli':
+ return 'IronPython'
+ elif sys.platform.startswith('java'):
+ return 'Jython'
+ elif '__pypy__' in sys.builtin_module_names:
+ return 'PyPy'
+ else:
+ return 'CPython'
+
+def invalid_marker(text):
+ """Validate text as a PEP 426 environment marker; return exception or False"""
+ try:
+ evaluate_marker(text)
+ except SyntaxError:
+ return sys.exc_info()[1]
+ return False
+
+def evaluate_marker(text, extra=None, _ops={}):
+ """
+ Evaluate a PEP 426 environment marker on CPython 2.4+.
+ Return a boolean indicating the marker result in this environment.
+ Raise SyntaxError if marker is invalid.
+
+ This implementation uses the 'parser' module, which is not implemented on
+ Jython and has been superseded by the 'ast' module in Python 2.6 and
+ later.
+ """
+
+ if not _ops:
+
+ from token import NAME, STRING
+ import token, symbol, operator
+
+ def and_test(nodelist):
+ # MUST NOT short-circuit evaluation, or invalid syntax can be skipped!
+ return reduce(operator.and_, [interpret(nodelist[i]) for i in range(1,len(nodelist),2)])
+
+ def test(nodelist):
+ # MUST NOT short-circuit evaluation, or invalid syntax can be skipped!
+ return reduce(operator.or_, [interpret(nodelist[i]) for i in range(1,len(nodelist),2)])
+
+ def atom(nodelist):
+ t = nodelist[1][0]
+ if t == token.LPAR:
+ if nodelist[2][0] == token.RPAR:
+ raise SyntaxError("Empty parentheses")
+ return interpret(nodelist[2])
+ raise SyntaxError("Language feature not supported in environment markers")
+
+ def comparison(nodelist):
+ if len(nodelist)>4:
+ raise SyntaxError("Chained comparison not allowed in environment markers")
+ comp = nodelist[2][1]
+ cop = comp[1]
+ if comp[0] == NAME:
+ if len(nodelist[2]) == 3:
+ if cop == 'not':
+ cop = 'not in'
+ else:
+ cop = 'is not'
+ try:
+ cop = _ops[cop]
+ except KeyError:
+ raise SyntaxError(repr(cop)+" operator not allowed in environment markers")
+ return cop(evaluate(nodelist[1]), evaluate(nodelist[3]))
+
+ _ops.update({
+ symbol.test: test, symbol.and_test: and_test, symbol.atom: atom,
+ symbol.comparison: comparison, 'not in': lambda x,y: x not in y,
+ 'in': lambda x,y: x in y, '==': operator.eq, '!=': operator.ne,
+ })
+ if hasattr(symbol,'or_test'):
+ _ops[symbol.or_test] = test
+
+ def interpret(nodelist):
+ while len(nodelist)==2: nodelist = nodelist[1]
+ try:
+ op = _ops[nodelist[0]]
+ except KeyError:
+ raise SyntaxError("Comparison or logical expression expected")
+ raise SyntaxError("Language feature not supported in environment markers: "+symbol.sym_name[nodelist[0]])
+ return op(nodelist)
+
+ def evaluate(nodelist):
+ while len(nodelist)==2: nodelist = nodelist[1]
+ kind = nodelist[0]
+ name = nodelist[1]
+ #while len(name)==2: name = name[1]
+ if kind==NAME:
+ try:
+ op = _marker_values[name]
+ except KeyError:
+ raise SyntaxError("Unknown name %r" % name)
+ return op()
+ if kind==STRING:
+ s = nodelist[1]
+ if s[:1] not in "'\"" or s.startswith('"""') or s.startswith("'''") \
+ or '\\' in s:
+ raise SyntaxError(
+ "Only plain strings allowed in environment markers")
+ return s[1:-1]
+ raise SyntaxError("Language feature not supported in environment markers")
+
+ return interpret(parser.expr(text).totuple(1)[1])
+
+def _markerlib_evaluate(text):
+ """
+ Evaluate a PEP 426 environment marker using markerlib.
+ Return a boolean indicating the marker result in this environment.
+ Raise SyntaxError if marker is invalid.
+ """
+ import _markerlib
+ # markerlib implements Metadata 1.2 (PEP 345) environment markers.
+ # Translate the variables to Metadata 2.0 (PEP 426).
+ env = _markerlib.default_environment()
+ for key in env.keys():
+ new_key = key.replace('.', '_')
+ env[new_key] = env.pop(key)
+ try:
+ result = _markerlib.interpret(text, env)
+ except NameError:
+ e = sys.exc_info()[1]
+ raise SyntaxError(e.args[0])
+ return result
+
+if 'parser' not in globals():
+ # fallback to less-complete _markerlib implementation if 'parser' module
+ # is not available.
+ evaluate_marker = _markerlib_evaluate
+
class NullProvider:
"""Try to implement resources and metadata for arbitrary PEP 302 loaders"""
@@ -1879,7 +2031,7 @@ def _handle_ns(packageName, path_item):
return None
module = sys.modules.get(packageName)
if module is None:
- module = sys.modules[packageName] = types.ModuleType(packageName)
+ module = sys.modules[packageName] = imp.new_module(packageName)
module.__path__ = []; _set_parent_ns(packageName)
elif not hasattr(module,'__path__'):
raise TypeError("Not a package:", packageName)
@@ -2058,7 +2210,6 @@ def parse_version(s):
parts.pop()
parts.append(part)
return tuple(parts)
-
class EntryPoint(object):
"""Object representing an advertised importable object"""
@@ -2299,7 +2450,14 @@ class Distribution(object):
dm = self.__dep_map = {None: []}
for name in 'requires.txt', 'depends.txt':
for extra,reqs in split_sections(self._get_metadata(name)):
- if extra: extra = safe_extra(extra)
+ if extra:
+ if ':' in extra:
+ extra, marker = extra.split(':',1)
+ if invalid_marker(marker):
+ reqs=[] # XXX warn
+ elif not evaluate_marker(marker):
+ reqs=[]
+ extra = safe_extra(extra) or None
dm.setdefault(extra,[]).extend(parse_requirements(reqs))
return dm
_dep_map = property(_dep_map)
@@ -2323,6 +2481,8 @@ class Distribution(object):
for line in self.get_metadata_lines(name):
yield line
+
+
def activate(self,path=None):
"""Ensure distribution is importable on `path` (default=sys.path)"""
if path is None: path = sys.path
@@ -2361,6 +2521,9 @@ class Distribution(object):
raise AttributeError,attr
return getattr(self._provider, attr)
+
+
+
#@classmethod
def from_filename(cls,filename,metadata=None, **kw):
return cls.from_location(
@@ -2402,42 +2565,16 @@ class Distribution(object):
-
-
-
-
-
-
-
-
-
-
-
-
def insert_on(self, path, loc = None):
"""Insert self.location in path before its nearest parent directory"""
loc = loc or self.location
-
- if self.project_name == 'setuptools':
- try:
- version = self.version
- except ValueError:
- version = ''
- if '0.7' in version:
- raise ValueError(
- "A 0.7-series setuptools cannot be installed "
- "with distribute. Found one at %s" % str(self.location))
-
if not loc:
return
- if path is sys.path:
- self.check_version_conflict()
-
nloc = _normalize_cached(loc)
bdir = os.path.dirname(nloc)
- npath= map(_normalize_cached, path)
+ npath= [(p and _normalize_cached(p) or p) for p in path]
bp = None
for p, item in enumerate(npath):
@@ -2445,10 +2582,14 @@ class Distribution(object):
break
elif item==bdir and self.precedence==EGG_DIST:
# if it's an .egg, give it precedence over its directory
+ if path is sys.path:
+ self.check_version_conflict()
path.insert(p, loc)
npath.insert(p, nloc)
break
else:
+ if path is sys.path:
+ self.check_version_conflict()
path.append(loc)
return
@@ -2465,9 +2606,8 @@ class Distribution(object):
return
-
def check_version_conflict(self):
- if self.key=='distribute':
+ if self.key=='setuptools':
return # ignore the inevitable setuptools self-conflicts :(
nsp = dict.fromkeys(self._get_metadata('namespace_packages.txt'))
@@ -2726,7 +2866,7 @@ class Requirement:
def __contains__(self,item):
if isinstance(item,Distribution):
- if item.key <> self.key: return False
+ if item.key != self.key: return False
if self.index: item = item.parsed_version # only get if we need it
elif isinstance(item,basestring):
item = parse_version(item)
@@ -2748,22 +2888,11 @@ class Requirement:
def __repr__(self): return "Requirement.parse(%r)" % str(self)
#@staticmethod
- def parse(s, replacement=True):
+ def parse(s):
reqs = list(parse_requirements(s))
if reqs:
- if len(reqs) == 1:
- founded_req = reqs[0]
- # if asked for setuptools distribution
- # and if distribute is installed, we want to give
- # distribute instead
- if _override_setuptools(founded_req) and replacement:
- distribute = list(parse_requirements('distribute'))
- if len(distribute) == 1:
- return distribute[0]
- return founded_req
- else:
- return founded_req
-
+ if len(reqs)==1:
+ return reqs[0]
raise ValueError("Expected only one requirement", s)
raise ValueError("No requirements found", s)
@@ -2780,26 +2909,6 @@ state_machine = {
}
-def _override_setuptools(req):
- """Return True when distribute wants to override a setuptools dependency.
-
- We want to override when the requirement is setuptools and the version is
- a variant of 0.6.
-
- """
- if req.project_name == 'setuptools':
- if not len(req.specs):
- # Just setuptools: ok
- return True
- for comparator, version in req.specs:
- if comparator in ['==', '>=', '>']:
- if '0.7' in version:
- # We want some setuptools not from the 0.6 series.
- return False
- return True
- return False
-
-
def _get_mro(cls):
"""Get an mro for a type or classic class"""
if not isinstance(cls,type):
@@ -2865,7 +2974,6 @@ _initialize(globals())
# Prepare the master working set and make the ``require()`` API available
_declare_state('object', working_set = WorkingSet())
-
try:
# Does the main program list any requirements?
from __main__ import __requires__