2012-09-28 11:00:16 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
2013-02-24 18:42:57 +01:00
|
|
|
# Copyright (C) 2012-2013 The Python Software Foundation.
|
2012-09-28 11:00:16 +02:00
|
|
|
# See LICENSE.txt and CONTRIBUTORS.txt.
|
|
|
|
#
|
2012-09-14 17:07:55 +02:00
|
|
|
"""PEP 376 implementation."""
|
|
|
|
|
2012-10-08 19:34:09 +02:00
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
2013-02-18 18:11:59 +01:00
|
|
|
import base64
|
2012-09-17 01:25:29 +02:00
|
|
|
import codecs
|
2013-07-23 12:20:44 +02:00
|
|
|
import contextlib
|
2012-09-17 09:44:19 +02:00
|
|
|
import hashlib
|
2012-09-17 01:25:29 +02:00
|
|
|
import logging
|
2013-02-18 18:11:59 +01:00
|
|
|
import os
|
2013-07-23 12:20:44 +02:00
|
|
|
import posixpath
|
2012-09-14 17:07:55 +02:00
|
|
|
import sys
|
|
|
|
import zipimport
|
|
|
|
|
2013-07-23 12:20:44 +02:00
|
|
|
from . import DistlibException, resources
|
|
|
|
from .compat import StringIO
|
2012-10-22 12:41:40 +02:00
|
|
|
from .version import get_scheme, UnsupportedVersionError
|
2013-07-16 16:18:58 +02:00
|
|
|
from .metadata import Metadata, METADATA_FILENAME
|
2013-07-16 21:43:26 +02:00
|
|
|
from .util import (parse_requirement, cached_property, parse_name_and_version,
|
|
|
|
read_exports, write_exports, CSVReader, CSVWriter)
|
2012-09-14 17:07:55 +02:00
|
|
|
|
|
|
|
|
2012-10-21 13:10:16 +02:00
|
|
|
__all__ = ['Distribution', 'BaseInstalledDistribution',
|
|
|
|
'InstalledDistribution', 'EggInfoDistribution',
|
2012-10-14 17:12:32 +02:00
|
|
|
'DistributionPath']
|
2012-09-14 17:07:55 +02:00
|
|
|
|
|
|
|
|
2012-09-17 01:25:29 +02:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2013-07-17 12:23:46 +02:00
|
|
|
EXPORTS_FILENAME = 'EXPORTS'
|
|
|
|
|
2013-07-16 16:18:58 +02:00
|
|
|
DIST_FILES = ('INSTALLER', METADATA_FILENAME, 'RECORD', 'REQUESTED',
|
2013-07-17 12:23:46 +02:00
|
|
|
'RESOURCES', EXPORTS_FILENAME, 'SHARED')
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2012-10-01 12:58:41 +02:00
|
|
|
DISTINFO_EXT = '.dist-info'
|
|
|
|
|
2013-03-01 10:34:23 +01:00
|
|
|
|
2012-10-01 13:17:59 +02:00
|
|
|
class _Cache(object):
|
2013-03-01 10:34:23 +01:00
|
|
|
"""
|
|
|
|
A simple cache mapping names and .dist-info paths to distributions
|
|
|
|
"""
|
2012-10-01 12:58:41 +02:00
|
|
|
def __init__(self):
|
2013-03-01 10:34:23 +01:00
|
|
|
"""
|
|
|
|
Initialise an instance. There is normally one for each DistributionPath.
|
|
|
|
"""
|
2012-10-01 12:58:41 +02:00
|
|
|
self.name = {}
|
|
|
|
self.path = {}
|
|
|
|
self.generated = False
|
|
|
|
|
|
|
|
def clear(self):
|
2013-03-01 10:34:23 +01:00
|
|
|
"""
|
|
|
|
Clear the cache, setting it to its initial state.
|
|
|
|
"""
|
2012-10-01 12:58:41 +02:00
|
|
|
self.name.clear()
|
|
|
|
self.path.clear()
|
|
|
|
self.generated = False
|
|
|
|
|
|
|
|
def add(self, dist):
|
2013-03-01 10:34:23 +01:00
|
|
|
"""
|
|
|
|
Add a distribution to the cache.
|
|
|
|
:param dist: The distribution to add.
|
|
|
|
"""
|
2012-10-01 12:58:41 +02:00
|
|
|
if dist.path not in self.path:
|
|
|
|
self.path[dist.path] = dist
|
2013-01-06 11:47:24 +01:00
|
|
|
self.name.setdefault(dist.key, []).append(dist)
|
2012-10-01 13:05:06 +02:00
|
|
|
|
2013-07-23 12:20:44 +02:00
|
|
|
|
2012-10-08 11:39:38 +02:00
|
|
|
class DistributionPath(object):
|
2012-10-01 12:58:41 +02:00
|
|
|
"""
|
|
|
|
Represents a set of distributions installed on a path (typically sys.path).
|
|
|
|
"""
|
|
|
|
def __init__(self, path=None, include_egg=False):
|
2013-03-01 10:34:23 +01:00
|
|
|
"""
|
|
|
|
Create an instance from a path, optionally including legacy (distutils/
|
|
|
|
setuptools/distribute) distributions.
|
|
|
|
:param path: The path to use, as a list of directories. If not specified,
|
|
|
|
sys.path is used.
|
|
|
|
:param include_egg: If True, this instance will look for and return legacy
|
|
|
|
distributions as well as those based on PEP 376.
|
|
|
|
"""
|
2012-10-01 12:58:41 +02:00
|
|
|
if path is None:
|
|
|
|
path = sys.path
|
|
|
|
self.path = path
|
|
|
|
self._include_dist = True
|
|
|
|
self._include_egg = include_egg
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2012-10-01 13:17:59 +02:00
|
|
|
self._cache = _Cache()
|
|
|
|
self._cache_egg = _Cache()
|
2012-10-01 12:58:41 +02:00
|
|
|
self._cache_enabled = True
|
2012-10-21 13:10:16 +02:00
|
|
|
self._scheme = get_scheme('default')
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2013-03-01 10:34:23 +01:00
|
|
|
def _get_cache_enabled(self):
|
|
|
|
return self._cache_enabled
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2013-03-01 10:34:23 +01:00
|
|
|
def _set_cache_enabled(self, value):
|
|
|
|
self._cache_enabled = value
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2013-03-01 10:34:23 +01:00
|
|
|
cache_enabled = property(_get_cache_enabled, _set_cache_enabled)
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2012-10-01 12:58:41 +02:00
|
|
|
def clear_cache(self):
|
2013-03-01 10:34:23 +01:00
|
|
|
"""
|
|
|
|
Clears the internal cache.
|
|
|
|
"""
|
2012-10-01 12:58:41 +02:00
|
|
|
self._cache.clear()
|
|
|
|
self._cache_egg.clear()
|
2012-09-14 17:07:55 +02:00
|
|
|
|
|
|
|
|
2012-10-01 12:58:41 +02:00
|
|
|
def _yield_distributions(self):
|
|
|
|
"""
|
2013-03-01 10:34:23 +01:00
|
|
|
Yield .dist-info and/or .egg(-info) distributions.
|
2012-10-01 12:58:41 +02:00
|
|
|
"""
|
2013-07-23 12:20:44 +02:00
|
|
|
# We need to check if we've seen some resources already, because on
|
|
|
|
# some Linux systems (e.g. some Debian/Ubuntu variants) there are
|
|
|
|
# symlinks which alias other files in the environment.
|
|
|
|
seen = set()
|
2012-10-01 12:58:41 +02:00
|
|
|
for path in self.path:
|
2013-07-23 12:20:44 +02:00
|
|
|
finder = resources.finder_for_path(path)
|
|
|
|
if finder is None:
|
|
|
|
continue
|
|
|
|
r = finder.find('')
|
|
|
|
if not r or not r.is_container:
|
2012-10-01 12:58:41 +02:00
|
|
|
continue
|
2013-07-23 12:20:44 +02:00
|
|
|
rset = sorted(r.resources)
|
|
|
|
for entry in rset:
|
|
|
|
r = finder.find(entry)
|
|
|
|
if not r or r.path in seen:
|
|
|
|
continue
|
|
|
|
if self._include_dist and entry.endswith(DISTINFO_EXT):
|
|
|
|
metadata_path = posixpath.join(entry, METADATA_FILENAME)
|
|
|
|
pydist = finder.find(metadata_path)
|
|
|
|
if not pydist:
|
|
|
|
continue
|
|
|
|
|
|
|
|
metadata = Metadata(fileobj=pydist.as_stream(),
|
|
|
|
scheme='legacy')
|
|
|
|
logger.debug('Found %s', r.path)
|
|
|
|
seen.add(r.path)
|
|
|
|
yield new_dist_class(r.path, metadata=metadata,
|
|
|
|
env=self)
|
|
|
|
elif self._include_egg and entry.endswith(('.egg-info',
|
|
|
|
'.egg')):
|
|
|
|
logger.debug('Found %s', r.path)
|
|
|
|
seen.add(r.path)
|
|
|
|
yield old_dist_class(r.path, self)
|
2012-10-01 12:58:41 +02:00
|
|
|
|
|
|
|
def _generate_cache(self):
|
2013-03-01 10:34:23 +01:00
|
|
|
"""
|
|
|
|
Scan the path for distributions and populate the cache with
|
|
|
|
those that are found.
|
|
|
|
"""
|
2012-10-01 12:58:41 +02:00
|
|
|
gen_dist = not self._cache.generated
|
|
|
|
gen_egg = self._include_egg and not self._cache_egg.generated
|
|
|
|
if gen_dist or gen_egg:
|
|
|
|
for dist in self._yield_distributions():
|
2012-10-14 17:12:32 +02:00
|
|
|
if isinstance(dist, InstalledDistribution):
|
2012-10-01 12:58:41 +02:00
|
|
|
self._cache.add(dist)
|
|
|
|
else:
|
|
|
|
self._cache_egg.add(dist)
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2012-10-01 12:58:41 +02:00
|
|
|
if gen_dist:
|
|
|
|
self._cache.generated = True
|
|
|
|
if gen_egg:
|
|
|
|
self._cache_egg.generated = True
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2012-10-01 12:58:41 +02:00
|
|
|
@classmethod
|
2013-03-01 10:34:23 +01:00
|
|
|
def distinfo_dirname(cls, name, version):
|
2012-10-01 12:58:41 +02:00
|
|
|
"""
|
|
|
|
The *name* and *version* parameters are converted into their
|
|
|
|
filename-escaped form, i.e. any ``'-'`` characters are replaced
|
|
|
|
with ``'_'`` other than the one in ``'dist-info'`` and the one
|
|
|
|
separating the name from the version number.
|
|
|
|
|
|
|
|
:parameter name: is converted to a standard distribution name by replacing
|
|
|
|
any runs of non- alphanumeric characters with a single
|
|
|
|
``'-'``.
|
|
|
|
:type name: string
|
|
|
|
:parameter version: is converted to a standard version string. Spaces
|
|
|
|
become dots, and all other non-alphanumeric characters
|
|
|
|
(except dots) become dashes, with runs of multiple
|
|
|
|
dashes condensed to a single dash.
|
|
|
|
:type version: string
|
|
|
|
:returns: directory name
|
|
|
|
:rtype: string"""
|
|
|
|
name = name.replace('-', '_')
|
2012-10-22 12:41:40 +02:00
|
|
|
return '-'.join([name, version]) + DISTINFO_EXT
|
2012-10-01 12:58:41 +02:00
|
|
|
|
|
|
|
def get_distributions(self):
|
|
|
|
"""
|
2013-03-01 10:34:23 +01:00
|
|
|
Provides an iterator that looks for distributions and returns
|
|
|
|
:class:`InstalledDistribution` or
|
2012-10-14 17:12:32 +02:00
|
|
|
:class:`EggInfoDistribution` instances for each one of them.
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2012-10-14 17:12:32 +02:00
|
|
|
:rtype: iterator of :class:`InstalledDistribution` and
|
|
|
|
:class:`EggInfoDistribution` instances
|
2012-10-01 12:58:41 +02:00
|
|
|
"""
|
|
|
|
if not self._cache_enabled:
|
2013-01-01 22:21:00 +01:00
|
|
|
for dist in self._yield_distributions():
|
2012-10-01 12:58:41 +02:00
|
|
|
yield dist
|
|
|
|
else:
|
|
|
|
self._generate_cache()
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2012-10-01 12:58:41 +02:00
|
|
|
for dist in self._cache.path.values():
|
|
|
|
yield dist
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2012-10-01 12:58:41 +02:00
|
|
|
if self._include_egg:
|
|
|
|
for dist in self._cache_egg.path.values():
|
|
|
|
yield dist
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2012-10-01 12:58:41 +02:00
|
|
|
def get_distribution(self, name):
|
|
|
|
"""
|
2013-03-01 10:34:23 +01:00
|
|
|
Looks for a named distribution on the path.
|
2012-10-01 12:58:41 +02:00
|
|
|
|
|
|
|
This function only returns the first result found, as no more than one
|
|
|
|
value is expected. If nothing is found, ``None`` is returned.
|
|
|
|
|
2013-03-01 10:34:23 +01:00
|
|
|
:rtype: :class:`InstalledDistribution`, :class:`EggInfoDistribution`
|
2012-10-14 17:12:32 +02:00
|
|
|
or ``None``
|
2012-10-01 12:58:41 +02:00
|
|
|
"""
|
|
|
|
result = None
|
2012-12-12 17:16:50 +01:00
|
|
|
name = name.lower()
|
2012-10-01 12:58:41 +02:00
|
|
|
if not self._cache_enabled:
|
|
|
|
for dist in self._yield_distributions():
|
2013-01-06 11:47:24 +01:00
|
|
|
if dist.key == name:
|
2012-10-01 12:58:41 +02:00
|
|
|
result = dist
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
self._generate_cache()
|
|
|
|
|
|
|
|
if name in self._cache.name:
|
|
|
|
result = self._cache.name[name][0]
|
|
|
|
elif self._include_egg and name in self._cache_egg.name:
|
|
|
|
result = self._cache_egg.name[name][0]
|
|
|
|
return result
|
|
|
|
|
|
|
|
def provides_distribution(self, name, version=None):
|
|
|
|
"""
|
|
|
|
Iterates over all distributions to find which distributions provide *name*.
|
|
|
|
If a *version* is provided, it will be used to filter the results.
|
|
|
|
|
|
|
|
This function only returns the first result found, since no more than
|
|
|
|
one values are expected. If the directory is not found, returns ``None``.
|
|
|
|
|
|
|
|
:parameter version: a version specifier that indicates the version
|
|
|
|
required, conforming to the format in ``PEP-345``
|
|
|
|
|
|
|
|
:type name: string
|
|
|
|
:type version: string
|
|
|
|
"""
|
2012-10-21 13:10:16 +02:00
|
|
|
matcher = None
|
2012-10-01 12:58:41 +02:00
|
|
|
if not version is None:
|
|
|
|
try:
|
2012-10-21 13:10:16 +02:00
|
|
|
matcher = self._scheme.matcher('%s (%s)' % (name, version))
|
2012-10-01 12:58:41 +02:00
|
|
|
except ValueError:
|
|
|
|
raise DistlibException('invalid name or version: %r, %r' %
|
|
|
|
(name, version))
|
|
|
|
|
|
|
|
for dist in self.get_distributions():
|
2012-11-12 11:57:47 +01:00
|
|
|
provided = dist.provides
|
2012-10-01 12:58:41 +02:00
|
|
|
|
|
|
|
for p in provided:
|
2013-06-27 23:15:15 +02:00
|
|
|
p_name, p_ver = parse_name_and_version(p)
|
|
|
|
if matcher is None:
|
|
|
|
if p_name == name:
|
2012-10-01 12:58:41 +02:00
|
|
|
yield dist
|
|
|
|
break
|
|
|
|
else:
|
2012-10-21 13:10:16 +02:00
|
|
|
if p_name == name and matcher.match(p_ver):
|
2012-10-01 12:58:41 +02:00
|
|
|
yield dist
|
|
|
|
break
|
|
|
|
|
|
|
|
def get_file_path(self, name, relative_path):
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
|
|
|
Return the path to a resource file.
|
|
|
|
"""
|
2012-10-01 12:58:41 +02:00
|
|
|
dist = self.get_distribution(name)
|
|
|
|
if dist is None:
|
|
|
|
raise LookupError('no distribution named %r found' % name)
|
|
|
|
return dist.get_resource_path(relative_path)
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2012-10-14 23:30:16 +02:00
|
|
|
def get_exported_entries(self, category, name=None):
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
|
|
|
Return all of the exported entries in a particular category.
|
|
|
|
|
|
|
|
:param category: The category to search for entries.
|
|
|
|
:param name: If specified, only entries with that name are returned.
|
|
|
|
"""
|
2012-10-08 12:46:44 +02:00
|
|
|
for dist in self.get_distributions():
|
2012-10-14 23:30:16 +02:00
|
|
|
r = dist.exports
|
2012-10-08 12:46:44 +02:00
|
|
|
if category in r:
|
|
|
|
d = r[category]
|
|
|
|
if name is not None:
|
|
|
|
if name in d:
|
|
|
|
yield d[name]
|
|
|
|
else:
|
|
|
|
for v in d.values():
|
|
|
|
yield v
|
|
|
|
|
2013-07-23 12:20:44 +02:00
|
|
|
|
2012-10-14 17:12:32 +02:00
|
|
|
class Distribution(object):
|
|
|
|
"""
|
|
|
|
A base class for distributions, whether installed or from indexes.
|
|
|
|
Either way, it must have some metadata, so that's all that's needed
|
|
|
|
for construction.
|
|
|
|
"""
|
|
|
|
|
2012-12-20 22:54:53 +01:00
|
|
|
build_time_dependency = False
|
|
|
|
"""
|
|
|
|
Set to True if it's known to be only a build-time dependency (i.e.
|
|
|
|
not needed after installation).
|
|
|
|
"""
|
|
|
|
|
2012-12-09 00:58:54 +01:00
|
|
|
requested = False
|
|
|
|
"""A boolean that indicates whether the ``REQUESTED`` metadata file is
|
|
|
|
present (in other words, whether the package was installed by user
|
|
|
|
request or it was installed as a dependency)."""
|
|
|
|
|
2012-10-14 17:12:32 +02:00
|
|
|
def __init__(self, metadata):
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
|
|
|
Initialise an instance.
|
|
|
|
:param metadata: The instance of :class:`Metadata` describing this
|
|
|
|
distribution.
|
|
|
|
"""
|
2012-10-14 17:12:32 +02:00
|
|
|
self.metadata = metadata
|
2012-10-21 13:10:16 +02:00
|
|
|
self.name = metadata.name
|
2013-01-06 11:47:24 +01:00
|
|
|
self.key = self.name.lower() # for case-insensitive comparisons
|
2012-10-21 13:10:16 +02:00
|
|
|
self.version = metadata.version
|
2012-10-14 17:12:32 +02:00
|
|
|
self.locator = None
|
|
|
|
self.md5_digest = None
|
2013-07-23 12:20:44 +02:00
|
|
|
self.extras = None # additional features requested
|
|
|
|
self.context = None # environment marker overrides
|
2012-10-08 12:46:44 +02:00
|
|
|
|
2012-10-19 11:05:30 +02:00
|
|
|
@property
|
2013-06-11 00:21:44 +02:00
|
|
|
def source_url(self):
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
2013-06-11 00:21:44 +02:00
|
|
|
The source archive download URL for this distribution.
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
2013-06-11 00:21:44 +02:00
|
|
|
return self.metadata.source_url
|
2012-10-14 17:12:32 +02:00
|
|
|
|
2013-06-20 13:46:18 +02:00
|
|
|
download_url = source_url # Backward compatibility
|
|
|
|
|
2013-03-01 11:56:37 +01:00
|
|
|
@property
|
|
|
|
def name_and_version(self):
|
|
|
|
"""
|
|
|
|
A utility property which displays the name and version in parentheses.
|
|
|
|
"""
|
|
|
|
return '%s (%s)' % (self.name, self.version)
|
|
|
|
|
2012-12-18 01:00:22 +01:00
|
|
|
@property
|
2012-11-12 11:57:47 +01:00
|
|
|
def provides(self):
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
|
|
|
A set of distribution names and versions provided by this distribution.
|
|
|
|
:return: A set of "name (version)" strings.
|
|
|
|
"""
|
2013-06-11 00:21:44 +02:00
|
|
|
plist = self.metadata.provides
|
2013-03-11 12:08:11 +01:00
|
|
|
s = '%s (%s)' % (self.name, self.version)
|
|
|
|
if s not in plist:
|
|
|
|
plist.append(s)
|
2013-06-11 23:47:35 +02:00
|
|
|
return plist
|
|
|
|
|
2013-07-14 23:28:31 +02:00
|
|
|
def _get_requirements(self, req_attr):
|
|
|
|
reqts = getattr(self.metadata, req_attr)
|
|
|
|
return set(self.metadata.get_requirements(reqts, extras=self.extras,
|
2013-06-19 16:44:40 +02:00
|
|
|
env=self.context))
|
2012-11-12 11:57:47 +01:00
|
|
|
|
2013-03-11 12:08:11 +01:00
|
|
|
@property
|
2013-06-22 22:29:10 +02:00
|
|
|
def run_requires(self):
|
2013-07-14 23:28:31 +02:00
|
|
|
return self._get_requirements('run_requires')
|
2013-03-11 12:08:11 +01:00
|
|
|
|
2013-06-23 21:11:36 +02:00
|
|
|
@property
|
|
|
|
def meta_requires(self):
|
2013-07-14 23:28:31 +02:00
|
|
|
return self._get_requirements('meta_requires')
|
2013-06-23 21:11:36 +02:00
|
|
|
|
2013-03-11 12:08:11 +01:00
|
|
|
@property
|
2013-06-11 00:21:44 +02:00
|
|
|
def build_requires(self):
|
2013-07-14 23:28:31 +02:00
|
|
|
return self._get_requirements('build_requires')
|
2013-03-11 12:08:11 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def test_requires(self):
|
2013-07-14 23:28:31 +02:00
|
|
|
return self._get_requirements('test_requires')
|
2013-03-11 12:08:11 +01:00
|
|
|
|
|
|
|
@property
|
2013-06-11 23:47:35 +02:00
|
|
|
def dev_requires(self):
|
2013-07-14 23:28:31 +02:00
|
|
|
return self._get_requirements('dev_requires')
|
2012-11-12 11:57:47 +01:00
|
|
|
|
2012-12-17 23:24:33 +01:00
|
|
|
def matches_requirement(self, req):
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
|
|
|
Say if this instance matches (fulfills) a requirement.
|
|
|
|
:param req: The requirement to match.
|
|
|
|
:rtype req: str
|
|
|
|
:return: True if it matches, else False.
|
|
|
|
"""
|
2013-06-28 17:49:18 +02:00
|
|
|
# Requirement may contain extras - parse to lose those
|
|
|
|
# from what's passed to the matcher
|
|
|
|
r = parse_requirement(req)
|
2012-12-17 23:24:33 +01:00
|
|
|
scheme = get_scheme(self.metadata.scheme)
|
|
|
|
try:
|
2013-06-28 17:49:18 +02:00
|
|
|
matcher = scheme.matcher(r.requirement)
|
2012-12-17 23:24:33 +01:00
|
|
|
except UnsupportedVersionError:
|
|
|
|
# XXX compat-mode if cannot read the version
|
|
|
|
logger.warning('could not read version %r - using name only',
|
|
|
|
req)
|
|
|
|
name = req.split()[0]
|
|
|
|
matcher = scheme.matcher(name)
|
|
|
|
|
2013-01-06 11:47:24 +01:00
|
|
|
name = matcher.key # case-insensitive
|
2012-12-17 23:24:33 +01:00
|
|
|
|
|
|
|
result = False
|
|
|
|
for p in self.provides:
|
2013-06-28 17:49:18 +02:00
|
|
|
p_name, p_ver = parse_name_and_version(p)
|
|
|
|
if p_name != name:
|
2012-12-17 23:24:33 +01:00
|
|
|
continue
|
|
|
|
try:
|
2013-06-28 17:49:18 +02:00
|
|
|
result = matcher.match(p_ver)
|
2012-12-17 23:24:33 +01:00
|
|
|
break
|
|
|
|
except UnsupportedVersionError:
|
|
|
|
pass
|
|
|
|
return result
|
|
|
|
|
2012-10-14 17:12:32 +02:00
|
|
|
def __repr__(self):
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
|
|
|
Return a textual representation of this instance,
|
|
|
|
"""
|
2013-06-11 00:21:44 +02:00
|
|
|
if self.source_url:
|
|
|
|
suffix = ' [%s]' % self.source_url
|
2012-10-14 17:12:32 +02:00
|
|
|
else:
|
|
|
|
suffix = ''
|
|
|
|
return '<Distribution %s (%s)%s>' % (self.name, self.version, suffix)
|
|
|
|
|
2012-10-17 20:46:43 +02:00
|
|
|
def __eq__(self, other):
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
|
|
|
See if this distribution is the same as another.
|
|
|
|
:param other: The distribution to compare with. To be equal to one
|
|
|
|
another. distributions must have the same type, name,
|
2013-06-11 00:21:44 +02:00
|
|
|
version and source_url.
|
2013-03-01 11:56:37 +01:00
|
|
|
:return: True if it is the same, else False.
|
|
|
|
"""
|
2012-10-17 20:46:43 +02:00
|
|
|
if type(other) is not type(self):
|
|
|
|
result = False
|
|
|
|
else:
|
|
|
|
result = (self.name == other.name and
|
|
|
|
self.version == other.version and
|
2013-06-11 00:21:44 +02:00
|
|
|
self.source_url == other.source_url)
|
2012-10-17 20:46:43 +02:00
|
|
|
return result
|
|
|
|
|
2012-11-13 10:45:54 +01:00
|
|
|
def __hash__(self):
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
|
|
|
Compute hash in a way which matches the equality test.
|
|
|
|
"""
|
2013-06-11 00:21:44 +02:00
|
|
|
return hash(self.name) + hash(self.version) + hash(self.source_url)
|
2012-10-17 20:46:43 +02:00
|
|
|
|
|
|
|
|
2012-10-21 13:10:16 +02:00
|
|
|
class BaseInstalledDistribution(Distribution):
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
|
|
|
This is the base class for installed distributions (whether PEP 376 or
|
|
|
|
legacy).
|
|
|
|
"""
|
2012-11-08 19:37:04 +01:00
|
|
|
|
|
|
|
hasher = None
|
|
|
|
|
2012-10-21 13:10:16 +02:00
|
|
|
def __init__(self, metadata, path, env=None):
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
|
|
|
Initialise an instance.
|
|
|
|
:param metadata: An instance of :class:`Metadata` which describes the
|
|
|
|
distribution. This will normally have been initialised
|
|
|
|
from a metadata file in the ``path``.
|
|
|
|
:param path: The path of the ``.dist-info`` or ``.egg-info``
|
|
|
|
directory for the distribution.
|
|
|
|
:param env: This is normally the :class:`DistributionPath`
|
|
|
|
instance where this distribution was found.
|
|
|
|
"""
|
2012-10-21 13:10:16 +02:00
|
|
|
super(BaseInstalledDistribution, self).__init__(metadata)
|
|
|
|
self.path = path
|
2013-07-23 12:20:44 +02:00
|
|
|
self.dist_path = env
|
2012-10-21 13:10:16 +02:00
|
|
|
|
2012-11-08 19:37:04 +01:00
|
|
|
def get_hash(self, data, hasher=None):
|
|
|
|
"""
|
|
|
|
Get the hash of some data, using a particular hash algorithm, if
|
|
|
|
specified.
|
|
|
|
|
|
|
|
:param data: The data to be hashed.
|
|
|
|
:type data: bytes
|
|
|
|
:param hasher: The name of a hash implementation, supported by hashlib,
|
|
|
|
or ``None``. Examples of valid values are ``'sha1'``,
|
|
|
|
``'sha224'``, ``'sha384'``, '``sha256'``, ``'md5'`` and
|
|
|
|
``'sha512'``. If no hasher is specified, the ``hasher``
|
|
|
|
attribute of the :class:`InstalledDistribution` instance
|
|
|
|
is used. If the hasher is determined to be ``None``, MD5
|
|
|
|
is used as the hashing algorithm.
|
|
|
|
:returns: The hash of the data. If a hasher was explicitly specified,
|
|
|
|
the returned hash will be prefixed with the specified hasher
|
|
|
|
followed by '='.
|
|
|
|
:rtype: str
|
|
|
|
"""
|
|
|
|
if hasher is None:
|
|
|
|
hasher = self.hasher
|
|
|
|
if hasher is None:
|
|
|
|
hasher = hashlib.md5
|
|
|
|
prefix = ''
|
|
|
|
else:
|
|
|
|
hasher = getattr(hashlib, hasher)
|
|
|
|
prefix = '%s=' % self.hasher
|
2013-02-18 18:11:59 +01:00
|
|
|
digest = hasher(data).digest()
|
|
|
|
digest = base64.urlsafe_b64encode(digest).rstrip(b'=').decode('ascii')
|
|
|
|
return '%s%s' % (prefix, digest)
|
2012-11-08 19:37:04 +01:00
|
|
|
|
2013-07-23 12:20:44 +02:00
|
|
|
|
2012-10-21 13:10:16 +02:00
|
|
|
class InstalledDistribution(BaseInstalledDistribution):
|
2013-07-23 12:20:44 +02:00
|
|
|
"""
|
|
|
|
Created with the *path* of the ``.dist-info`` directory provided to the
|
2013-07-14 23:28:31 +02:00
|
|
|
constructor. It reads the metadata contained in ``pydist.json`` when it is
|
2012-12-13 16:03:38 +01:00
|
|
|
instantiated., or uses a passed in Metadata instance (useful for when
|
2013-07-23 12:20:44 +02:00
|
|
|
dry-run mode is being used).
|
|
|
|
"""
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2013-02-18 18:11:59 +01:00
|
|
|
hasher = 'sha256'
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2012-12-13 16:03:38 +01:00
|
|
|
def __init__(self, path, metadata=None, env=None):
|
2013-07-23 12:20:44 +02:00
|
|
|
self.finder = finder = resources.finder_for_path(path)
|
|
|
|
if finder is None:
|
|
|
|
import pdb; pdb.set_trace ()
|
2012-10-01 12:58:41 +02:00
|
|
|
if env and env._cache_enabled and path in env._cache.path:
|
2012-10-09 19:51:35 +02:00
|
|
|
metadata = env._cache.path[path].metadata
|
2012-12-13 16:03:38 +01:00
|
|
|
elif metadata is None:
|
2013-07-23 12:20:44 +02:00
|
|
|
r = finder.find(METADATA_FILENAME)
|
2013-07-23 13:17:18 +02:00
|
|
|
# Temporary - for legacy support
|
2013-07-23 12:20:44 +02:00
|
|
|
if r is None:
|
|
|
|
r = finder.find('METADATA')
|
|
|
|
if r is None:
|
2013-07-16 16:18:58 +02:00
|
|
|
raise ValueError('no %s found in %s' % (METADATA_FILENAME,
|
|
|
|
path))
|
2013-07-23 12:20:44 +02:00
|
|
|
with contextlib.closing(r.as_stream()) as stream:
|
|
|
|
metadata = Metadata(fileobj=stream, scheme='legacy')
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2012-10-21 13:10:16 +02:00
|
|
|
super(InstalledDistribution, self).__init__(metadata, path, env)
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2012-10-01 12:58:41 +02:00
|
|
|
if env and env._cache_enabled:
|
|
|
|
env._cache.add(self)
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2013-07-23 12:20:44 +02:00
|
|
|
try:
|
|
|
|
r = finder.find('REQUESTED')
|
|
|
|
except AttributeError:
|
|
|
|
import pdb; pdb.set_trace ()
|
|
|
|
self.requested = r is not None
|
2012-12-12 12:57:56 +01:00
|
|
|
|
2012-09-14 17:07:55 +02:00
|
|
|
def __repr__(self):
|
2012-10-14 17:12:32 +02:00
|
|
|
return '<InstalledDistribution %r %s at %r>' % (
|
2012-09-14 17:07:55 +02:00
|
|
|
self.name, self.version, self.path)
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return "%s %s" % (self.name, self.version)
|
|
|
|
|
2013-01-01 16:35:52 +01:00
|
|
|
def _get_records(self):
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
|
|
|
Get the list of installed files for the distribution
|
|
|
|
:return: A list of tuples of path, hash and size. Note that hash and
|
|
|
|
size might be ``None`` for some entries. The path is exactly
|
|
|
|
as stored in the file (which is as in PEP 376).
|
|
|
|
"""
|
2012-09-14 17:07:55 +02:00
|
|
|
results = []
|
2013-07-23 12:20:44 +02:00
|
|
|
r = self.get_distinfo_resource('RECORD')
|
|
|
|
with contextlib.closing(r.as_stream()) as stream:
|
|
|
|
with CSVReader(stream=stream) as record_reader:
|
|
|
|
# Base location is parent dir of .dist-info dir
|
|
|
|
#base_location = os.path.dirname(self.path)
|
|
|
|
#base_location = os.path.abspath(base_location)
|
|
|
|
for row in record_reader:
|
|
|
|
missing = [None for i in range(len(row), 3)]
|
|
|
|
path, checksum, size = row + missing
|
|
|
|
#if not os.path.isabs(path):
|
|
|
|
# path = path.replace('/', os.sep)
|
|
|
|
# path = os.path.join(base_location, path)
|
|
|
|
results.append((path, checksum, size))
|
2012-09-14 17:07:55 +02:00
|
|
|
return results
|
|
|
|
|
2012-10-08 11:39:38 +02:00
|
|
|
@cached_property
|
2012-10-14 23:30:16 +02:00
|
|
|
def exports(self):
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
|
|
|
Return the information exported by this distribution.
|
|
|
|
:return: A dictionary of exports, mapping an export category to a list
|
|
|
|
of :class:`ExportEntry` instances describing the individual
|
|
|
|
export entries.
|
|
|
|
"""
|
2012-10-08 11:39:38 +02:00
|
|
|
result = {}
|
2013-07-23 12:20:44 +02:00
|
|
|
r = self.get_distinfo_resource(EXPORTS_FILENAME)
|
|
|
|
if r:
|
|
|
|
result = self.read_exports()
|
2012-10-08 22:32:48 +02:00
|
|
|
return result
|
|
|
|
|
2013-07-23 12:20:44 +02:00
|
|
|
def read_exports(self):
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
|
|
|
Read exports data from a file in .ini format.
|
2013-07-23 12:20:44 +02:00
|
|
|
|
2013-03-01 11:56:37 +01:00
|
|
|
:return: A dictionary of exports, mapping an export category to a list
|
|
|
|
of :class:`ExportEntry` instances describing the individual
|
|
|
|
export entries.
|
|
|
|
"""
|
2012-10-08 22:32:48 +02:00
|
|
|
result = {}
|
2013-07-23 12:20:44 +02:00
|
|
|
r = self.get_distinfo_resource(EXPORTS_FILENAME)
|
|
|
|
if r:
|
|
|
|
with contextlib.closing(r.as_stream()) as stream:
|
|
|
|
result = read_exports(stream)
|
2012-10-08 11:39:38 +02:00
|
|
|
return result
|
|
|
|
|
2013-07-23 12:20:44 +02:00
|
|
|
def write_exports(self, exports):
|
2013-03-01 11:56:37 +01:00
|
|
|
"""
|
|
|
|
Write a dictionary of exports to a file in .ini format.
|
|
|
|
:param exports: A dictionary of exports, mapping an export category to
|
|
|
|
a list of :class:`ExportEntry` instances describing the
|
|
|
|
individual export entries.
|
|
|
|
"""
|
2013-07-23 12:20:44 +02:00
|
|
|
rf = self.get_distinfo_file(EXPORTS_FILENAME)
|
2012-10-08 22:32:48 +02:00
|
|
|
with open(rf, 'w') as f:
|
2013-07-16 21:43:26 +02:00
|
|
|
write_exports(exports, f)
|
2012-10-08 22:32:48 +02:00
|
|
|
|
2012-09-14 17:07:55 +02:00
|
|
|
def get_resource_path(self, relative_path):
|
2013-03-01 14:57:27 +01:00
|
|
|
"""
|
|
|
|
NOTE: This API may change in the future.
|
|
|
|
|
|
|
|
Return the absolute path to a resource file with the given relative
|
|
|
|
path.
|
|
|
|
|
|
|
|
:param relative_path: The path, relative to .dist-info, of the resource
|
|
|
|
of interest.
|
|
|
|
:return: The absolute path where the resource is to be found.
|
|
|
|
"""
|
2013-07-23 12:20:44 +02:00
|
|
|
r = self.get_distinfo_resource('RESOURCES')
|
|
|
|
with contextlib.closing(r.as_stream()) as stream:
|
|
|
|
with CSVReader(stream=stream) as resources_reader:
|
|
|
|
for relative, destination in resources_reader:
|
|
|
|
if relative == relative_path:
|
|
|
|
return destination
|
2013-02-25 23:21:44 +01:00
|
|
|
raise KeyError('no resource file with relative path %r '
|
|
|
|
'is installed' % relative_path)
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2013-01-01 16:35:52 +01:00
|
|
|
def list_installed_files(self):
|
2012-09-14 17:07:55 +02:00
|
|
|
"""
|
|
|
|
Iterates over the ``RECORD`` entries and returns a tuple
|
2013-01-01 16:35:52 +01:00
|
|
|
``(path, hash, size)`` for each line.
|
2012-09-14 17:07:55 +02:00
|
|
|
|
2012-09-17 09:44:19 +02:00
|
|
|
:returns: iterator of (path, hash, size)
|
2012-09-14 17:07:55 +02:00
|
|
|
"""
|
2013-01-01 16:35:52 +01:00
|
|
|
for result in self._get_records():
|
2012-09-14 17:07:55 +02:00
|
|
|
yield result
|
|
|
|
|
2013-02-17 11:52:32 +01:00
|
|
|
def write_installed_files(self, paths, prefix, dry_run=False):
|
2012-09-17 01:25:29 +02:00
|
|
|
"""
|
|
|
|
Writes the ``RECORD`` file, using the ``paths`` iterable passed in. Any
|
|
|
|
existing ``RECORD`` file is silently overwritten.
|
2013-02-17 11:52:32 +01:00
|
|
|
|
|
|
|
prefix is used to determine when to write absolute paths.
|
2012-09-17 01:25:29 +02:00
|
|
|
"""
|
2013-02-17 11:52:32 +01:00
|
|
|
prefix = os.path.join(prefix, '')
|
2013-01-04 19:14:39 +01:00
|
|
|
base = os.path.dirname(self.path)
|
2013-02-17 11:52:32 +01:00
|
|
|
base_under_prefix = base.startswith(prefix)
|
2013-01-04 19:14:39 +01:00
|
|
|
base = os.path.join(base, '')
|
2013-07-23 12:20:44 +02:00
|
|
|
record_path = self.get_distinfo_file('RECORD')
|
2012-09-17 01:25:29 +02:00
|
|
|
logger.info('creating %s', record_path)
|
2012-12-13 16:03:38 +01:00
|
|
|
if dry_run:
|
2013-03-27 19:31:42 +01:00
|
|
|
return None
|
2013-02-25 23:21:44 +01:00
|
|
|
with CSVWriter(record_path) as writer:
|
2012-09-17 01:25:29 +02:00
|
|
|
for path in paths:
|
2012-12-09 20:57:07 +01:00
|
|
|
if os.path.isdir(path) or path.endswith(('.pyc', '.pyo')):
|
2012-09-17 09:44:19 +02:00
|
|
|
# do not put size and hash, as in PEP-376
|
2013-02-20 13:55:44 +01:00
|
|
|
hash_value = size = ''
|
2012-09-17 01:25:29 +02:00
|
|
|
else:
|
2013-02-20 13:55:44 +01:00
|
|
|
size = '%d' % os.path.getsize(path)
|
2012-09-17 01:25:29 +02:00
|
|
|
with open(path, 'rb') as fp:
|
2013-02-20 13:55:44 +01:00
|
|
|
hash_value = self.get_hash(fp.read())
|
|
|
|
if path.startswith(base) or (base_under_prefix and
|
2013-07-23 12:20:44 +02:00
|
|
|
path.startswith(prefix)):
|
2013-02-20 13:55:44 +01:00
|
|
|
path = os.path.relpath(path, base)
|
|
|
|
writer.writerow((path, hash_value, size))
|
2012-09-17 01:25:29 +02:00
|
|
|
|
|
|
|
# add the RECORD file itself
|
2013-01-04 19:14:39 +01:00
|
|
|
if record_path.startswith(base):
|
|
|
|
record_path = os.path.relpath(record_path, base)
|
2012-09-17 01:25:29 +02:00
|
|
|
writer.writerow((record_path, '', ''))
|
2013-03-27 19:31:42 +01:00
|
|
|
return record_path
|
2012-09-17 01:25:29 +02:00
|
|
|
|
2012-09-17 02:17:23 +02:00
|
|
|
def check_installed_files(self):
|
|
|
|
"""
|
|
|
|
Checks that the hashes and sizes of the files in ``RECORD`` are
|
|
|
|
matched by the files themselves. Returns a (possibly empty) list of
|
|
|
|
mismatches. Each entry in the mismatch list will be a tuple consisting
|
|
|
|
of the path, 'exists', 'size' or 'hash' according to what didn't match
|
2012-09-17 09:44:19 +02:00
|
|
|
(existence is checked first, then size, then hash), the expected
|
2012-09-17 02:17:23 +02:00
|
|
|
value and the actual value.
|
|
|
|
"""
|
|
|
|
mismatches = []
|
2013-01-04 19:14:39 +01:00
|
|
|
base = os.path.dirname(self.path)
|
2013-07-23 12:20:44 +02:00
|
|
|
record_path = self.get_distinfo_file('RECORD')
|
2013-02-18 18:11:59 +01:00
|
|
|
for path, hash_value, size in self.list_installed_files():
|
2013-01-04 19:14:39 +01:00
|
|
|
if not os.path.isabs(path):
|
|
|
|
path = os.path.join(base, path)
|
|