diff options
Diffstat (limited to 'pkg_resources.py')
| -rw-r--r-- | pkg_resources.py | 214 |
1 files changed, 127 insertions, 87 deletions
diff --git a/pkg_resources.py b/pkg_resources.py index 5734989d..11debf65 100644 --- a/pkg_resources.py +++ b/pkg_resources.py @@ -29,6 +29,10 @@ import token import symbol import operator import platform +import collections +import plistlib +import email.parser +import tempfile from pkgutil import get_importer try: @@ -233,11 +237,9 @@ def get_provider(moduleOrReq): def _macosx_vers(_cache=[]): if not _cache: - 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'): @@ -309,13 +311,9 @@ def compatible_platforms(provided, required): macosversion = "%s.%s" % (reqMac.group(1), reqMac.group(2)) if dversion == 7 and macosversion >= "10.3" or \ dversion == 8 and macosversion >= "10.4": - - #import warnings - #warnings.warn("Mac eggs should be rebuilt to " - # "use the macosx designation instead of darwin.", - # category=DeprecationWarning) return True - return False # egg isn't macosx or legacy darwin + # egg isn't macosx or legacy darwin + return False # are they the same major version and machine type? if provMac.group(1) != reqMac.group(1) or \ @@ -345,8 +343,10 @@ run_main = run_script def get_distribution(dist): """Return a current distribution object for a Requirement or string""" - if isinstance(dist, basestring): dist = Requirement.parse(dist) - if isinstance(dist, Requirement): dist = get_provider(dist) + if isinstance(dist, basestring): + dist = Requirement.parse(dist) + if isinstance(dist, Requirement): + dist = get_provider(dist) if not isinstance(dist, Distribution): raise TypeError("Expected string, Requirement, or Distribution", dist) return dist @@ -640,7 +640,8 @@ class WorkingSet(object): to_activate.append(dist) if dist not in req: # Oops, the "best" so far conflicts with a dependency - raise VersionConflict(dist, req) # XXX put more info here + # XXX put more info here + raise VersionConflict(dist, req) requirements.extend(dist.requires(req.extras)[::-1]) processed[req] = True @@ -656,8 +657,10 @@ class WorkingSet(object): distributions, errors = working_set.find_plugins( Environment(plugin_dirlist) ) - map(working_set.add, distributions) # add plugins+libs to sys.path - print 'Could not load', errors # display errors + # add plugins+libs to sys.path + map(working_set.add, distributions) + # display errors + print('Could not load', errors) The `plugin_env` should be an ``Environment`` instance that contains only distributions that are in the project's "plugin directory" or @@ -885,7 +888,8 @@ class Environment(object): def __iter__(self): """Yield the unique project names of the available distributions""" for key in self._distmap.keys(): - if self[key]: yield key + if self[key]: + yield key def __iadd__(self, other): """In-place addition of a distribution or environment""" @@ -1530,33 +1534,51 @@ class EmptyProvider(NullProvider): empty_provider = EmptyProvider() -def build_zipmanifest(path): +class ZipManifests(dict): + """ + zip manifest builder + """ + + @classmethod + def build(cls, path): + """ + Build a dictionary similar to the zipimport directory + caches, except instead of tuples, store ZipInfo objects. + + Use a platform-specific path separator (os.sep) for the path keys + for compatibility with pypy on Windows. + """ + with ContextualZipFile(path) as zfile: + items = ( + ( + name.replace('/', os.sep), + zfile.getinfo(name), + ) + for name in zfile.namelist() + ) + return dict(items) + + load = build + + +class MemoizedZipManifests(ZipManifests): """ - This builds a similar dictionary to the zipimport directory - caches. However instead of tuples, ZipInfo objects are stored. - - The translation of the tuple is as follows: - * [0] - zipinfo.filename on stock pythons this needs "/" --> os.sep - on pypy it is the same (one reason why distribute did work - in some cases on pypy and win32). - * [1] - zipinfo.compress_type - * [2] - zipinfo.compress_size - * [3] - zipinfo.file_size - * [4] - len(utf-8 encoding of filename) if zipinfo & 0x800 - len(ascii encoding of filename) otherwise - * [5] - (zipinfo.date_time[0] - 1980) << 9 | - zipinfo.date_time[1] << 5 | zipinfo.date_time[2] - * [6] - (zipinfo.date_time[3] - 1980) << 11 | - zipinfo.date_time[4] << 5 | (zipinfo.date_time[5] // 2) - * [7] - zipinfo.CRC + Memoized zipfile manifests. """ - zipinfo = dict() - with ContextualZipFile(path) as zfile: - for zitem in zfile.namelist(): - zpath = zitem.replace('/', os.sep) - zipinfo[zpath] = zfile.getinfo(zitem) - assert zipinfo[zpath] is not None - return zipinfo + manifest_mod = collections.namedtuple('manifest_mod', 'manifest mtime') + + def load(self, path): + """ + Load a manifest at path or return a suitable manifest already loaded. + """ + path = os.path.normpath(path) + mtime = os.stat(path).st_mtime + + if path not in self or self[path].mtime != mtime: + manifest = self.build(path) + self[path] = self.manifest_mod(manifest, mtime) + + return self[path].manifest class ContextualZipFile(zipfile.ZipFile): @@ -1583,10 +1605,14 @@ class ZipProvider(EggProvider): """Resource support for zips and eggs""" eagers = None + _zip_manifests = ( + MemoizedZipManifests() + if os.environ.get('PKG_RESOURCES_CACHE_ZIP_MANIFESTS') else + ZipManifests() + ) def __init__(self, module): EggProvider.__init__(self, module) - self.zipinfo = build_zipmanifest(self.loader.archive) self.zip_pre = self.loader.archive+os.sep def _zipinfo_name(self, fspath): @@ -1599,14 +1625,19 @@ class ZipProvider(EggProvider): ) def _parts(self, zip_path): - # Convert a zipfile subpath into an egg-relative path part list - fspath = self.zip_pre+zip_path # pseudo-fs 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) raise AssertionError( "%s is not a subpath of %s" % (fspath, self.egg_root) ) + @property + def zipinfo(self): + return self._zip_manifests.load(self.loader.archive) + def get_resource_filename(self, manager, resource_name): if not self.egg_name: raise NotImplementedError( @@ -1802,7 +1833,6 @@ class EggMetadata(ZipProvider): def __init__(self, importer): """Create a metadata provider from a zipimporter""" - self.zipinfo = build_zipmanifest(importer.archive) self.zip_pre = importer.archive+os.sep self.loader = importer if importer.prefix: @@ -1987,7 +2017,8 @@ def fixup_namespace_packages(path_item, parent=None): try: for package in _namespace_packages.get(parent,()): subpath = _handle_ns(package, path_item) - if subpath: fixup_namespace_packages(subpath, package) + if subpath: + fixup_namespace_packages(subpath, package) finally: imp.release_lock() @@ -2119,13 +2150,16 @@ def parse_version(s): for part in _parse_version_parts(s.lower()): if part.startswith('*'): # remove '-' before a prerelease tag - if part<'*final': - while parts and parts[-1]=='*final-': parts.pop() + if part < '*final': + while parts and parts[-1] == '*final-': + parts.pop() # remove trailing zeros from each series of numeric parts while parts and parts[-1]=='00000000': parts.pop() parts.append(part) return tuple(parts) + + class EntryPoint(object): """Object representing an advertised importable object""" @@ -2150,7 +2184,8 @@ class EntryPoint(object): return "EntryPoint.parse(%r)" % str(self) def load(self, require=True, env=None, installer=None): - if require: self.require(env, installer) + if require: + self.require(env, installer) entry = __import__(self.module_name, globals(), globals(), ['__name__']) for attr in self.attrs: @@ -2180,22 +2215,21 @@ class EntryPoint(object): """ try: attrs = extras = () - name, value = src.split('=',1) + name, value = src.split('=', 1) if '[' in value: - value, extras = value.split('[',1) - req = Requirement.parse("x["+extras) - if req.specs: raise ValueError + value, extras = value.split('[', 1) + req = Requirement.parse("x[" + extras) + if req.specs: + raise ValueError extras = req.extras if ':' in value: - value, attrs = value.split(':',1) + value, attrs = value.split(':', 1) if not MODULE(attrs.rstrip()): raise ValueError attrs = attrs.rstrip().split('.') except ValueError: - raise ValueError( - "EntryPoint must be in 'name=module:attrs [extras]' format", - src - ) + msg = "EntryPoint must be in 'name=module:attrs [extras]' format" + raise ValueError(msg, src) else: return cls(name.strip(), value.strip(), attrs, extras, dist) @@ -2352,7 +2386,7 @@ class Distribution(object): for extra, reqs in split_sections(self._get_metadata(name)): if extra: if ':' in extra: - extra, marker = extra.split(':',1) + extra, marker = extra.split(':', 1) if invalid_marker(marker): # XXX warn reqs=[] @@ -2366,7 +2400,7 @@ class Distribution(object): """List of Requirements needed for this distro if `extras` are used""" dm = self._dep_map deps = [] - deps.extend(dm.get(None,())) + deps.extend(dm.get(None, ())) for ext in extras: try: deps.extend(dm[safe_extra(ext)]) @@ -2383,7 +2417,8 @@ class Distribution(object): def activate(self, path=None): """Ensure distribution is importable on `path` (default=sys.path)""" - if path is None: path = sys.path + if path is None: + path = sys.path self.insert_on(path) if path is sys.path: fixup_namespace_packages(self.location) @@ -2399,7 +2434,7 @@ class Distribution(object): ) if self.platform: - filename += '-'+self.platform + filename += '-' + self.platform return filename def __repr__(self): @@ -2409,8 +2444,10 @@ class Distribution(object): return str(self) def __str__(self): - try: version = getattr(self,'version',None) - except ValueError: version = None + try: + version = getattr(self, 'version', None) + except ValueError: + version = None version = version or "[unknown version]" return "%s %s" % (self.project_name, version) @@ -2466,9 +2503,9 @@ class Distribution(object): npath= [(p and _normalize_cached(p) or p) for p in path] for p, item in enumerate(npath): - if item==nloc: + if item == nloc: break - elif item==bdir and self.precedence==EGG_DIST: + 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() @@ -2482,7 +2519,7 @@ class Distribution(object): return # p is the spot where we found or inserted loc; now remove duplicates - while 1: + while True: try: np = npath.index(nloc, p+1) except ValueError: @@ -2495,7 +2532,7 @@ class Distribution(object): return def check_version_conflict(self): - if self.key=='setuptools': + if self.key == 'setuptools': # ignore the inevitable setuptools self-conflicts :( return @@ -2520,16 +2557,14 @@ class Distribution(object): try: self.version except ValueError: - issue_warning("Unbuilt egg for "+repr(self)) + issue_warning("Unbuilt egg for " + repr(self)) return False return True def clone(self,**kw): """Copy this distribution, substituting in any changed keyword args""" - for attr in ( - 'project_name', 'version', 'py_version', 'platform', 'location', - 'precedence' - ): + names = 'project_name version py_version platform location precedence' + for attr in names.split(): kw.setdefault(attr, getattr(self, attr, None)) kw.setdefault('metadata', self._provider) return self.__class__(**kw) @@ -2550,9 +2585,8 @@ class DistInfoDistribution(Distribution): try: return self._pkg_info except AttributeError: - from email.parser import Parser metadata = self.get_metadata(self.PKG_INFO) - self._pkg_info = Parser().parsestr(metadata) + self._pkg_info = email.parser.Parser().parsestr(metadata) return self._pkg_info @property @@ -2620,8 +2654,7 @@ def issue_warning(*args,**kw): level += 1 except ValueError: pass - from warnings import warn - warn(stacklevel = level+1, *args, **kw) + warnings.warn(stacklevel=level + 1, *args, **kw) def parse_requirements(strs): @@ -2664,7 +2697,9 @@ def parse_requirements(strs): raise ValueError(msg, line, "at", line[p:]) match = TERMINATOR(line, p) - if match: p = match.end() # skip the terminator, if any + # skip the terminator, if any + if match: + p = match.end() return line, p, items for line in lines: @@ -2710,11 +2745,15 @@ class Requirement: def __str__(self): specs = ','.join([''.join(s) for s in self.specs]) extras = ','.join(self.extras) - if extras: extras = '[%s]' % extras + if extras: + extras = '[%s]' % extras return '%s%s%s' % (self.project_name, extras, specs) def __eq__(self, other): - return isinstance(other, Requirement) and self.hashCmp==other.hashCmp + return ( + isinstance(other, Requirement) and + self.hashCmp == other.hashCmp + ) def __contains__(self, item): if isinstance(item, Distribution): @@ -2731,16 +2770,17 @@ class Requirement: for parsed, trans, op, ver in self.index: # Indexing: 0, 1, -1 action = trans[compare(item, parsed)] - if action=='F': + if action == 'F': return False - elif action=='T': + elif action == 'T': return True - elif action=='+': + elif action == '+': last = True - elif action=='-' or last is None: + elif action == '-' or last is None: last = False # no rules encountered - if last is None: last = True + if last is None: + last = True return last def __hash__(self): @@ -2752,7 +2792,7 @@ class Requirement: def parse(s): reqs = list(parse_requirements(s)) if reqs: - if len(reqs)==1: + if len(reqs) == 1: return reqs[0] raise ValueError("Expected only one requirement", s) raise ValueError("No requirements found", s) @@ -2814,12 +2854,11 @@ def split_sections(s): yield section, content def _mkstemp(*args,**kw): - from tempfile import mkstemp old_open = os.open try: # temporarily bypass sandboxing os.open = os_open - return mkstemp(*args,**kw) + return tempfile.mkstemp(*args,**kw) finally: # and then put it back os.open = old_open @@ -2848,4 +2887,5 @@ run_main = run_script # calling ``require()``) will get activated as well. add_activation_listener(lambda dist: dist.activate()) working_set.entries=[] -list(map(working_set.add_entry, sys.path)) # match order +# match order +list(map(working_set.add_entry, sys.path)) |
