summaryrefslogtreecommitdiff
path: root/pkg_resources.py
diff options
context:
space:
mode:
Diffstat (limited to 'pkg_resources.py')
-rw-r--r--pkg_resources.py251
1 files changed, 161 insertions, 90 deletions
diff --git a/pkg_resources.py b/pkg_resources.py
index 74acecd5..5b095562 100644
--- a/pkg_resources.py
+++ b/pkg_resources.py
@@ -39,15 +39,6 @@ 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
-
def _bypass_ensure_directory(name, mode=0777):
# Sandbox-bypassing version of ensure_directory()
if not WRITE_SUPPORT:
@@ -96,6 +87,7 @@ _sget_none = _sset_none = lambda *args: None
+
def get_supported_platform():
"""Return this platform's maximum compatible version.
@@ -160,7 +152,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',
@@ -269,6 +261,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?
@@ -444,7 +442,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
@@ -551,7 +549,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`,
@@ -570,9 +568,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
@@ -692,7 +687,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:
@@ -700,7 +694,6 @@ class WorkingSet(object):
return needed
-
def subscribe(self, callback):
"""Invoke `callback` for all distributions (including existing ones)"""
if callback in self.callbacks:
@@ -709,14 +702,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[:]
@@ -725,8 +719,6 @@ class WorkingSet(object):
self.callbacks = callbacks[:]
-
-
class Environment(object):
"""Searchable snapshot of distributions on a search path"""
@@ -1178,6 +1170,129 @@ 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; SyntaxError if marker is invalid"""
+
+ 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")
+
+ import parser
+ return interpret(parser.expr(text).totuple(1)[1])
+
+
class NullProvider:
"""Try to implement resources and metadata for arbitrary PEP 302 loaders"""
@@ -1852,7 +1967,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)
@@ -2031,7 +2146,6 @@ def parse_version(s):
parts.pop()
parts.append(part)
return tuple(parts)
-
class EntryPoint(object):
"""Object representing an advertised importable object"""
@@ -2272,7 +2386,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)
@@ -2296,6 +2417,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
@@ -2334,6 +2457,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(
@@ -2375,42 +2501,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):
@@ -2418,10 +2518,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
@@ -2438,9 +2542,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'))
@@ -2699,7 +2802,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)
@@ -2721,22 +2824,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)
@@ -2753,26 +2845,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):
@@ -2838,7 +2910,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__