summaryrefslogtreecommitdiff
path: root/pkg_resources/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'pkg_resources/__init__.py')
-rw-r--r--pkg_resources/__init__.py303
1 files changed, 186 insertions, 117 deletions
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
index 2eab8230..37bf1482 100644
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -1,3 +1,5 @@
+# coding: utf-8
+
"""
Package resource API
--------------------
@@ -65,6 +67,7 @@ try:
except ImportError:
importlib_machinery = None
+from pkg_resources.extern import appdirs
from pkg_resources.extern import packaging
__import__('pkg_resources.extern.packaging.version')
__import__('pkg_resources.extern.packaging.specifiers')
@@ -155,7 +158,7 @@ class _SetuptoolsVersionMixin(object):
# pad for numeric comparison
yield part.zfill(8)
else:
- yield '*'+part
+ yield '*' + part
# ensure that alpha/beta/candidate are before final
yield '*final'
@@ -209,36 +212,44 @@ def parse_version(v):
_state_vars = {}
+
def _declare_state(vartype, **kw):
globals().update(kw)
_state_vars.update(dict.fromkeys(kw, vartype))
+
def __getstate__():
state = {}
g = globals()
for k, v in _state_vars.items():
- state[k] = g['_sget_'+v](g[k])
+ state[k] = g['_sget_' + v](g[k])
return state
+
def __setstate__(state):
g = globals()
for k, v in state.items():
- g['_sset_'+_state_vars[k]](k, g[k], v)
+ g['_sset_' + _state_vars[k]](k, g[k], v)
return state
+
def _sget_dict(val):
return val.copy()
+
def _sset_dict(key, ob, state):
ob.clear()
ob.update(state)
+
def _sget_object(val):
return val.__getstate__()
+
def _sset_object(key, ob, state):
ob.__setstate__(state)
+
_sget_none = _sset_none = lambda *args: None
@@ -265,9 +276,10 @@ def get_supported_platform():
pass
return plat
+
__all__ = [
# Basic resource access and distribution/entry point discovery
- 'require', 'run_script', 'get_provider', 'get_distribution',
+ 'require', 'run_script', 'get_provider', 'get_distribution',
'load_entry_point', 'get_entry_map', 'get_entry_info',
'iter_entry_points',
'resource_string', 'resource_stream', 'resource_filename',
@@ -311,10 +323,12 @@ __all__ = [
'run_main', 'AvailableDistributions',
]
+
class ResolutionError(Exception):
"""Abstract base for dependency resolution errors"""
+
def __repr__(self):
- return self.__class__.__name__+repr(self.args)
+ return self.__class__.__name__ + repr(self.args)
class VersionConflict(ResolutionError):
@@ -391,6 +405,8 @@ class DistributionNotFound(ResolutionError):
class UnknownExtra(ResolutionError):
"""Distribution doesn't have an "extra feature" of the given name"""
+
+
_provider_factories = {}
PY_MAJOR = sys.version[:3]
@@ -400,6 +416,7 @@ SOURCE_DIST = 1
CHECKOUT_DIST = 0
DEVELOP_DIST = -1
+
def register_loader_type(loader_type, provider_factory):
"""Register `provider_factory` to make providers for `loader_type`
@@ -409,6 +426,7 @@ def register_loader_type(loader_type, provider_factory):
"""
_provider_factories[loader_type] = provider_factory
+
def get_provider(moduleOrReq):
"""Return an IResourceProvider for the named module or requirement"""
if isinstance(moduleOrReq, Requirement):
@@ -421,6 +439,7 @@ def get_provider(moduleOrReq):
loader = getattr(module, '__loader__', None)
return _find_adapter(_provider_factories, loader)(module)
+
def _macosx_vers(_cache=[]):
if not _cache:
version = platform.mac_ver()[0]
@@ -436,9 +455,11 @@ def _macosx_vers(_cache=[]):
_cache.append(version.split('.'))
return _cache[0]
+
def _macosx_arch(machine):
return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine)
+
def get_build_platform():
"""Return this platform's string for platform-specific distributions
@@ -464,6 +485,7 @@ def get_build_platform():
pass
return plat
+
macosVersionString = re.compile(r"macosx-(\d+)\.(\d+)-(.*)")
darwinVersionString = re.compile(r"darwin-(\d+)\.(\d+)\.(\d+)-(.*)")
# XXX backward compat
@@ -477,7 +499,7 @@ def compatible_platforms(provided, required):
XXX Needs compatibility checks for Linux and other unixy OSes.
"""
- if provided is None or required is None or provided==required:
+ if provided is None or required is None or provided == required:
# easy case
return True
@@ -524,9 +546,11 @@ def run_script(dist_spec, script_name):
ns['__name__'] = name
require(dist_spec)[0].run_script(script_name, ns)
+
# backward compatibility
run_main = run_script
+
def get_distribution(dist):
"""Return a current distribution object for a Requirement or string"""
if isinstance(dist, six.string_types):
@@ -537,14 +561,17 @@ def get_distribution(dist):
raise TypeError("Expected string, Requirement, or Distribution", dist)
return dist
+
def load_entry_point(dist, group, name):
"""Return `name` entry point of `group` for `dist` or raise ImportError"""
return get_distribution(dist).load_entry_point(group, name)
+
def get_entry_map(dist, group=None):
"""Return the entry point map for `group`, or the full entry map"""
return get_distribution(dist).get_entry_map(group)
+
def get_entry_info(dist, group, name):
"""Return the EntryPoint object for `group`+`name`, or ``None``"""
return get_distribution(dist).get_entry_info(group, name)
@@ -732,7 +759,7 @@ class WorkingSet(object):
for key in self.entry_keys[item]:
if key not in seen:
- seen[key]=1
+ seen[key] = 1
yield self.by_key[key]
def add(self, dist, entry=None, insert=True, replace=False):
@@ -752,8 +779,8 @@ class WorkingSet(object):
if entry is None:
entry = dist.location
- keys = self.entry_keys.setdefault(entry,[])
- keys2 = self.entry_keys.setdefault(dist.location,[])
+ keys = self.entry_keys.setdefault(entry, [])
+ keys2 = self.entry_keys.setdefault(dist.location, [])
if not replace and dist.key in self.by_key:
# ignore hidden distros
return
@@ -947,11 +974,17 @@ class WorkingSet(object):
return needed
- def subscribe(self, callback):
- """Invoke `callback` for all distributions (including existing ones)"""
+ def subscribe(self, callback, existing=True):
+ """Invoke `callback` for all distributions
+
+ If `existing=True` (default),
+ call on all existing ones, as well.
+ """
if callback in self.callbacks:
return
self.callbacks.append(callback)
+ if not existing:
+ return
for dist in self:
callback(dist)
@@ -1027,7 +1060,7 @@ class Environment(object):
is returned.
"""
return (self.python is None or dist.py_version is None
- or dist.py_version==self.python) \
+ or dist.py_version == self.python) \
and compatible_platforms(dist.platform, self.platform)
def remove(self, dist):
@@ -1232,7 +1265,7 @@ class ResourceManager:
extract, as it tracks the generated names for possible cleanup later.
"""
extract_path = self.extraction_path or get_default_cache()
- target_path = os.path.join(extract_path, archive_name+'-tmp', *names)
+ target_path = os.path.join(extract_path, archive_name + '-tmp', *names)
try:
_bypass_ensure_directory(target_path)
except:
@@ -1326,49 +1359,18 @@ class ResourceManager:
"""
# XXX
-def get_default_cache():
- """Determine the default cache location
- This returns the ``PYTHON_EGG_CACHE`` environment variable, if set.
- Otherwise, on Windows, it returns a "Python-Eggs" subdirectory of the
- "Application Data" directory. On all other systems, it's "~/.python-eggs".
+def get_default_cache():
"""
- try:
- return os.environ['PYTHON_EGG_CACHE']
- except KeyError:
- pass
+ Return the ``PYTHON_EGG_CACHE`` environment variable
+ or a platform-relevant user cache dir for an app
+ named "Python-Eggs".
+ """
+ return (
+ os.environ.get('PYTHON_EGG_CACHE')
+ or appdirs.user_cache_dir(appname='Python-Eggs')
+ )
- if os.name!='nt':
- return os.path.expanduser('~/.python-eggs')
-
- # XXX this may be locale-specific!
- app_data = 'Application Data'
- app_homes = [
- # best option, should be locale-safe
- (('APPDATA',), None),
- (('USERPROFILE',), app_data),
- (('HOMEDRIVE','HOMEPATH'), app_data),
- (('HOMEPATH',), app_data),
- (('HOME',), None),
- # 95/98/ME
- (('WINDIR',), app_data),
- ]
-
- for keys, subdir in app_homes:
- dirname = ''
- for key in keys:
- if key in os.environ:
- dirname = os.path.join(dirname, os.environ[key])
- else:
- break
- else:
- if subdir:
- dirname = os.path.join(dirname, subdir)
- return os.path.join(dirname, 'Python-Eggs')
- else:
- raise RuntimeError(
- "Please set the PYTHON_EGG_CACHE enviroment variable"
- )
def safe_name(name):
"""Convert an arbitrary string to a standard distribution name
@@ -1386,7 +1388,7 @@ def safe_version(version):
# normalize the version
return str(packaging.version.Version(version))
except packaging.version.InvalidVersion:
- version = version.replace(' ','.')
+ version = version.replace(' ', '.')
return re.sub('[^A-Za-z0-9.]+', '-', version)
@@ -1404,7 +1406,7 @@ def to_filename(name):
Any '-' characters are currently replaced with '_'.
"""
- return name.replace('-','_')
+ return name.replace('-', '_')
def invalid_marker(text):
@@ -1462,16 +1464,11 @@ class NullProvider:
def has_metadata(self, name):
return self.egg_info and self._has(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(self, name):
+ if not self.egg_info:
+ return ""
+ value = self._get(self._fn(self.egg_info, name))
+ return value.decode('utf-8') if six.PY3 else value
def get_metadata_lines(self, name):
return yield_lines(self.get_metadata(name))
@@ -1491,7 +1488,7 @@ class NullProvider:
return []
def run_script(self, script_name, namespace):
- script = 'scripts/'+script_name
+ script = 'scripts/' + script_name
if not self.has_metadata(script):
raise ResolutionError("No script named %r" % script_name)
script_text = self.get_metadata(script).replace('\r\n', '\n')
@@ -1507,7 +1504,7 @@ class NullProvider:
cache[script_filename] = (
len(script_text), 0, script_text.split('\n'), script_filename
)
- script_code = compile(script_text, script_filename,'exec')
+ script_code = compile(script_text, script_filename, 'exec')
exec(script_code, namespace, namespace)
def _has(self, path):
@@ -1537,6 +1534,7 @@ class NullProvider:
"Can't perform this operation for loaders without 'get_data()'"
)
+
register_loader_type(object, NullProvider)
@@ -1552,7 +1550,7 @@ class EggProvider(NullProvider):
# of multiple eggs; that's why we use module_path instead of .archive
path = self.module_path
old = None
- while path!=old:
+ while path != old:
if _is_unpacked_egg(path):
self.egg_name = os.path.basename(path)
self.egg_info = os.path.join(path, 'EGG-INFO')
@@ -1561,6 +1559,7 @@ class EggProvider(NullProvider):
old = path
path, base = os.path.split(path)
+
class DefaultProvider(EggProvider):
"""Provides access to package resources in the filesystem"""
@@ -1586,6 +1585,7 @@ class DefaultProvider(EggProvider):
type(None))
register_loader_type(loader_cls, cls)
+
DefaultProvider._register()
@@ -1600,6 +1600,7 @@ class EmptyProvider(NullProvider):
def __init__(self):
pass
+
empty_provider = EmptyProvider()
@@ -1678,7 +1679,7 @@ class ZipProvider(EggProvider):
def __init__(self, module):
EggProvider.__init__(self, module)
- self.zip_pre = self.loader.archive+os.sep
+ self.zip_pre = self.loader.archive + os.sep
def _zipinfo_name(self, fspath):
# Convert a virtual filename (full path to file) into a zipfile subpath
@@ -1692,9 +1693,9 @@ class ZipProvider(EggProvider):
def _parts(self, zip_path):
# Convert a zipfile subpath into an egg-relative path part list.
# pseudo-fs path
- fspath = self.zip_pre+zip_path
- if fspath.startswith(self.egg_root+os.sep):
- return fspath[len(self.egg_root)+1:].split(os.sep)
+ fspath = self.zip_pre + zip_path
+ if fspath.startswith(self.egg_root + os.sep):
+ return fspath[len(self.egg_root) + 1:].split(os.sep)
raise AssertionError(
"%s is not a subpath of %s" % (fspath, self.egg_root)
)
@@ -1765,7 +1766,7 @@ class ZipProvider(EggProvider):
# so proceed.
return real_path
# Windows, del old file and retry
- elif os.name=='nt':
+ elif os.name == 'nt':
unlink(real_path)
rename(tmpnam, real_path)
return real_path
@@ -1785,7 +1786,7 @@ class ZipProvider(EggProvider):
if not os.path.isfile(file_path):
return False
stat = os.stat(file_path)
- if stat.st_size!=size or stat.st_mtime!=timestamp:
+ if stat.st_size != size or stat.st_mtime != timestamp:
return False
# check that the contents match
zip_contents = self.loader.get_data(zip_path)
@@ -1835,6 +1836,7 @@ class ZipProvider(EggProvider):
def _resource_to_zip(self, resource_name):
return self._zipinfo_name(self._fn(self.module_path, resource_name))
+
register_loader_type(zipimport.zipimporter, ZipProvider)
@@ -1854,20 +1856,24 @@ class FileMetadata(EmptyProvider):
self.path = path
def has_metadata(self, name):
- return name=='PKG-INFO' and os.path.isfile(self.path)
+ return name == 'PKG-INFO' and os.path.isfile(self.path)
def get_metadata(self, name):
- if name=='PKG-INFO':
- with io.open(self.path, encoding='utf-8') as f:
- try:
- metadata = f.read()
- except UnicodeDecodeError as exc:
- # add path context to error message
- tmpl = " in {self.path}"
- exc.reason += tmpl.format(self=self)
- raise
- return metadata
- raise KeyError("No metadata except PKG-INFO is available")
+ if name != 'PKG-INFO':
+ raise KeyError("No metadata except PKG-INFO is available")
+
+ with io.open(self.path, encoding='utf-8', errors="replace") as f:
+ metadata = f.read()
+ self._warn_on_replacement(metadata)
+ return metadata
+
+ def _warn_on_replacement(self, metadata):
+ # Python 2.6 and 3.2 compat for: replacement_char = '�'
+ replacement_char = b'\xef\xbf\xbd'.decode('utf-8')
+ if replacement_char in metadata:
+ tmpl = "{self.path} could not be properly decoded in UTF-8"
+ msg = tmpl.format(**locals())
+ warnings.warn(msg)
def get_metadata_lines(self, name):
return yield_lines(self.get_metadata(name))
@@ -1904,7 +1910,7 @@ class EggMetadata(ZipProvider):
def __init__(self, importer):
"""Create a metadata provider from a zipimporter"""
- self.zip_pre = importer.archive+os.sep
+ self.zip_pre = importer.archive + os.sep
self.loader = importer
if importer.prefix:
self.module_path = os.path.join(importer.archive, importer.prefix)
@@ -1912,7 +1918,9 @@ class EggMetadata(ZipProvider):
self.module_path = importer.archive
self._setup_prefix()
-_declare_state('dict', _distribution_finders = {})
+
+_declare_state('dict', _distribution_finders={})
+
def register_finder(importer_type, distribution_finder):
"""Register `distribution_finder` to find distributions in sys.path items
@@ -1930,6 +1938,7 @@ def find_distributions(path_item, only=False):
finder = _find_adapter(_distribution_finders, importer)
return finder(importer, path_item, only)
+
def find_eggs_in_zip(importer, path_item, only=False):
"""
Find eggs in zip files; possibly multiple nested eggs.
@@ -1950,12 +1959,17 @@ def find_eggs_in_zip(importer, path_item, only=False):
for dist in find_eggs_in_zip(zipimport.zipimporter(subpath), subpath):
yield dist
+
register_finder(zipimport.zipimporter, find_eggs_in_zip)
+
def find_nothing(importer, path_item, only=False):
return ()
+
+
register_finder(object, find_nothing)
+
def find_on_path(importer, path_item, only=False):
"""Yield distributions accessible on a sys.path directory"""
path_item = _normalize_cached(path_item)
@@ -1964,7 +1978,7 @@ def find_on_path(importer, path_item, only=False):
if _is_unpacked_egg(path_item):
yield Distribution.from_filename(
path_item, metadata=PathMetadata(
- path_item, os.path.join(path_item,'EGG-INFO')
+ path_item, os.path.join(path_item, 'EGG-INFO')
)
)
else:
@@ -1996,6 +2010,8 @@ def find_on_path(importer, path_item, only=False):
for item in dists:
yield item
break
+
+
register_finder(pkgutil.ImpImporter, find_on_path)
if hasattr(importlib_machinery, 'FileFinder'):
@@ -2022,6 +2038,7 @@ def register_namespace_handler(importer_type, namespace_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)"""
@@ -2036,7 +2053,7 @@ def _handle_ns(packageName, path_item):
module = sys.modules[packageName] = types.ModuleType(packageName)
module.__path__ = []
_set_parent_ns(packageName)
- elif not hasattr(module,'__path__'):
+ elif not hasattr(module, '__path__'):
raise TypeError("Not a package:", packageName)
handler = _find_adapter(_namespace_handlers, importer)
subpath = handler(importer, path_item, packageName, module)
@@ -2054,6 +2071,16 @@ def _rebuild_mod_path(orig_path, package_name, module):
corresponding to their sys.path order
"""
sys_path = [_normalize_cached(p) for p in sys.path]
+
+ def safe_sys_path_index(entry):
+ """
+ Workaround for #520 and #513.
+ """
+ try:
+ return sys_path.index(entry)
+ except ValueError:
+ return float('inf')
+
def position_in_sys_path(path):
"""
Return the ordinal of the path based on its position in sys.path
@@ -2061,7 +2088,7 @@ def _rebuild_mod_path(orig_path, package_name, module):
path_parts = path.split(os.sep)
module_parts = package_name.count('.') + 1
parts = path_parts[:-module_parts]
- return sys_path.index(_normalize_cached(os.sep.join(parts)))
+ return safe_sys_path_index(_normalize_cached(os.sep.join(parts)))
orig_path.sort(key=position_in_sys_path)
module.__path__[:] = [_normalize_cached(p) for p in orig_path]
@@ -2088,8 +2115,8 @@ def declare_namespace(packageName):
# 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,[])
+ _namespace_packages.setdefault(parent, []).append(packageName)
+ _namespace_packages.setdefault(packageName, [])
for path_item in path:
# Ensure all the parent's path items are reflected in the child,
@@ -2099,29 +2126,32 @@ def declare_namespace(packageName):
finally:
_imp.release_lock()
+
def fixup_namespace_packages(path_item, parent=None):
"""Ensure that previously-declared namespace packages include path_item"""
_imp.acquire_lock()
try:
- for package in _namespace_packages.get(parent,()):
+ for package in _namespace_packages.get(parent, ()):
subpath = _handle_ns(package, path_item)
if subpath:
fixup_namespace_packages(subpath, package)
finally:
_imp.release_lock()
+
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 = _normalize_cached(subpath)
for item in module.__path__:
- if _normalize_cached(item)==normalized:
+ if _normalize_cached(item) == normalized:
break
else:
# Only return the path if it's not already there
return subpath
+
register_namespace_handler(pkgutil.ImpImporter, file_ns_handler)
register_namespace_handler(zipimport.zipimporter, file_ns_handler)
@@ -2132,6 +2162,7 @@ if hasattr(importlib_machinery, 'FileFinder'):
def null_ns_handler(importer, path_item, packageName, module):
return None
+
register_namespace_handler(object, null_ns_handler)
@@ -2139,6 +2170,7 @@ def normalize_path(filename):
"""Normalize a file/dir name for comparison purposes"""
return os.path.normcase(os.path.realpath(filename))
+
def _normalize_cached(filename, _cache={}):
try:
return _cache[filename]
@@ -2146,6 +2178,7 @@ def _normalize_cached(filename, _cache={}):
_cache[filename] = result = normalize_path(filename)
return result
+
def _is_unpacked_egg(path):
"""
Determine if given path appears to be an unpacked egg.
@@ -2154,6 +2187,7 @@ def _is_unpacked_egg(path):
path.lower().endswith('.egg')
)
+
def _set_parent_ns(packageName):
parts = packageName.split('.')
name = parts.pop()
@@ -2175,6 +2209,7 @@ def yield_lines(strs):
for s in yield_lines(ss):
yield s
+
MODULE = re.compile(r"\w+(\.\w+)*$").match
EGG_NAME = re.compile(
r"""
@@ -2293,7 +2328,7 @@ class EntryPoint(object):
ep = cls.parse(line, dist)
if ep.name in this:
raise ValueError("Duplicate entry point", group, ep.name)
- this[ep.name]=ep
+ this[ep.name] = ep
return this
@classmethod
@@ -2355,7 +2390,7 @@ class Distribution(object):
@classmethod
def from_location(cls, location, basename, metadata=None, **kw):
- project_name, version, py_version, platform = [None]*4
+ project_name, version, py_version, platform = [None] * 4
basename, ext = os.path.splitext(basename)
if ext.lower() in _distributionImpl:
cls = _distributionImpl[ext.lower()]
@@ -2477,11 +2512,11 @@ class Distribution(object):
extra, marker = extra.split(':', 1)
if invalid_marker(marker):
# XXX warn
- reqs=[]
+ reqs = []
elif not evaluate_marker(marker):
- reqs=[]
+ reqs = []
extra = safe_extra(extra) or None
- dm.setdefault(extra,[]).extend(parse_requirements(reqs))
+ dm.setdefault(extra, []).extend(parse_requirements(reqs))
return dm
def requires(self, extras=()):
@@ -2503,11 +2538,11 @@ class Distribution(object):
for line in self.get_metadata_lines(name):
yield line
- def activate(self, path=None):
+ def activate(self, path=None, replace=False):
"""Ensure distribution is importable on `path` (default=sys.path)"""
if path is None:
path = sys.path
- self.insert_on(path, replace=True)
+ self.insert_on(path, replace=replace)
if path is sys.path:
fixup_namespace_packages(self.location)
for pkg in self._get_metadata('namespace_packages.txt'):
@@ -2577,7 +2612,7 @@ class Distribution(object):
self._get_metadata('entry_points.txt'), self
)
if group is not None:
- return ep_map.get(group,{})
+ return ep_map.get(group, {})
return ep_map
def get_entry_info(self, group, name):
@@ -2585,7 +2620,24 @@ class Distribution(object):
return self.get_entry_map(group).get(name)
def insert_on(self, path, loc=None, replace=False):
- """Insert self.location in path before its nearest parent directory"""
+ """Ensure self.location is on path
+
+ If replace=False (default):
+ - If location is already in path anywhere, do nothing.
+ - Else:
+ - If it's an egg and its parent directory is on path,
+ insert just ahead of the parent.
+ - Else: add to the end of path.
+ If replace=True:
+ - If location is already on path anywhere (not eggs)
+ or higher priority than its parent (eggs)
+ do nothing.
+ - Else:
+ - If it's an egg and its parent directory is on path,
+ insert just ahead of the parent,
+ removing any lower-priority entries.
+ - Else: add it to the front of path.
+ """
loc = loc or self.location
if not loc:
@@ -2593,13 +2645,20 @@ class Distribution(object):
nloc = _normalize_cached(loc)
bdir = os.path.dirname(nloc)
- npath= [(p and _normalize_cached(p) or p) for p in path]
+ npath = [(p and _normalize_cached(p) or p) for p in path]
for p, item in enumerate(npath):
if item == nloc:
- break
+ if replace:
+ break
+ else:
+ # don't modify path (even removing duplicates) if found and not replace
+ return
elif item == bdir and self.precedence == EGG_DIST:
# if it's an .egg, give it precedence over its directory
+ # UNLESS it's already been added to sys.path and replace=False
+ if (not replace) and nloc in npath[p:]:
+ return
if path is sys.path:
self.check_version_conflict()
path.insert(p, loc)
@@ -2617,7 +2676,7 @@ class Distribution(object):
# p is the spot where we found or inserted loc; now remove duplicates
while True:
try:
- np = npath.index(nloc, p+1)
+ np = npath.index(nloc, p + 1)
except ValueError:
break
else:
@@ -2657,7 +2716,7 @@ class Distribution(object):
return False
return True
- def clone(self,**kw):
+ def clone(self, **kw):
"""Copy this distribution, substituting in any changed keyword args"""
names = 'project_name version py_version platform location precedence'
for attr in names.split():
@@ -2744,7 +2803,7 @@ _distributionImpl = {
}
-def issue_warning(*args,**kw):
+def issue_warning(*args, **kw):
level = 1
g = globals()
try:
@@ -2758,6 +2817,7 @@ def issue_warning(*args,**kw):
class RequirementParseError(ValueError):
+
def __str__(self):
return ' '.join(self.args)
@@ -2782,6 +2842,7 @@ def parse_requirements(strs):
class Requirement(packaging.requirements.Requirement):
+
def __init__(self, requirement_string):
"""DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!"""
try:
@@ -2837,10 +2898,12 @@ class Requirement(packaging.requirements.Requirement):
def _get_mro(cls):
"""Get an mro for a type or classic class"""
if not isinstance(cls, type):
- class cls(cls, object): pass
+ class cls(cls, object):
+ pass
return cls.__mro__[1:]
return cls.__mro__
+
def _find_adapter(registry, ob):
"""Return an adapter factory for `ob` from `registry`"""
for t in _get_mro(getattr(ob, '__class__', type(ob))):
@@ -2890,12 +2953,13 @@ def split_sections(s):
# wrap up last segment
yield section, content
-def _mkstemp(*args,**kw):
+
+def _mkstemp(*args, **kw):
old_open = os.open
try:
# temporarily bypass sandboxing
os.open = os_open
- return tempfile.mkstemp(*args,**kw)
+ return tempfile.mkstemp(*args, **kw)
finally:
# and then put it back
os.open = old_open
@@ -2946,11 +3010,16 @@ def _initialize_master_working_set():
run_script = working_set.run_script
# backward compatibility
run_main = run_script
- # Activate all distributions already on sys.path, and ensure that
- # all distributions added to the working set in the future (e.g. by
- # calling ``require()``) will get activated as well.
- add_activation_listener(lambda dist: dist.activate())
- working_set.entries=[]
+ # Activate all distributions already on sys.path with replace=False and
+ # ensure that all distributions added to the working set in the future
+ # (e.g. by calling ``require()``) will get activated as well,
+ # with higher priority (replace=True).
+ dist = None # ensure dist is defined for del dist below
+ for dist in working_set:
+ dist.activate(replace=False)
+ del dist
+ add_activation_listener(lambda dist: dist.activate(replace=True), existing=False)
+ working_set.entries = []
# match order
list(map(working_set.add_entry, sys.path))
globals().update(locals())