Path: blob/main/test/lib/python3.9/site-packages/pkg_resources/__init__.py
4808 views
"""1Package resource API2--------------------34A resource is a logical file contained within a package, or a logical5subdirectory thereof. The package resource API expects resource names6to have their path parts separated with ``/``, *not* whatever the local7path separator is. Do not use os.path operations to manipulate resource8names being passed into the API.910The package resource API is designed to work with normal filesystem packages,11.egg files, and unpacked .egg files. It can also work in a limited way with12.zip files and with custom PEP 302 loaders that support the ``get_data()``13method.14"""1516import sys17import os18import io19import time20import re21import types22import zipfile23import zipimport24import warnings25import stat26import functools27import pkgutil28import operator29import platform30import collections31import plistlib32import email.parser33import errno34import tempfile35import textwrap36import itertools37import inspect38import ntpath39import posixpath40import importlib41from pkgutil import get_importer4243try:44import _imp45except ImportError:46# Python 3.2 compatibility47import imp as _imp4849try:50FileExistsError51except NameError:52FileExistsError = OSError5354# capture these to bypass sandboxing55from os import utime56try:57from os import mkdir, rename, unlink58WRITE_SUPPORT = True59except ImportError:60# no write support, probably under GAE61WRITE_SUPPORT = False6263from os import open as os_open64from os.path import isdir, split6566try:67import importlib.machinery as importlib_machinery68# access attribute to force import under delayed import mechanisms.69importlib_machinery.__name__70except ImportError:71importlib_machinery = None7273from pkg_resources.extern.jaraco.text import (74yield_lines,75drop_comment,76join_continuation,77)7879from pkg_resources.extern import appdirs80from pkg_resources.extern import packaging81__import__('pkg_resources.extern.packaging.version')82__import__('pkg_resources.extern.packaging.specifiers')83__import__('pkg_resources.extern.packaging.requirements')84__import__('pkg_resources.extern.packaging.markers')85__import__('pkg_resources.extern.packaging.utils')8687if sys.version_info < (3, 5):88raise RuntimeError("Python 3.5 or later is required")8990# declare some globals that will be defined later to91# satisfy the linters.92require = None93working_set = None94add_activation_listener = None95resources_stream = None96cleanup_resources = None97resource_dir = None98resource_stream = None99set_extraction_path = None100resource_isdir = None101resource_string = None102iter_entry_points = None103resource_listdir = None104resource_filename = None105resource_exists = None106_distribution_finders = None107_namespace_handlers = None108_namespace_packages = None109110111class PEP440Warning(RuntimeWarning):112"""113Used when there is an issue with a version or specifier not complying with114PEP 440.115"""116117118def parse_version(v):119try:120return packaging.version.Version(v)121except packaging.version.InvalidVersion:122warnings.warn(123f"{v} is an invalid version and will not be supported in "124"a future release",125PkgResourcesDeprecationWarning,126)127return packaging.version.LegacyVersion(v)128129130_state_vars = {}131132133def _declare_state(vartype, **kw):134globals().update(kw)135_state_vars.update(dict.fromkeys(kw, vartype))136137138def __getstate__():139state = {}140g = globals()141for k, v in _state_vars.items():142state[k] = g['_sget_' + v](g[k])143return state144145146def __setstate__(state):147g = globals()148for k, v in state.items():149g['_sset_' + _state_vars[k]](k, g[k], v)150return state151152153def _sget_dict(val):154return val.copy()155156157def _sset_dict(key, ob, state):158ob.clear()159ob.update(state)160161162def _sget_object(val):163return val.__getstate__()164165166def _sset_object(key, ob, state):167ob.__setstate__(state)168169170_sget_none = _sset_none = lambda *args: None171172173def get_supported_platform():174"""Return this platform's maximum compatible version.175176distutils.util.get_platform() normally reports the minimum version177of macOS that would be required to *use* extensions produced by178distutils. But what we want when checking compatibility is to know the179version of macOS that we are *running*. To allow usage of packages that180explicitly require a newer version of macOS, we must also know the181current version of the OS.182183If this condition occurs for any other platform with a version in its184platform strings, this function should be extended accordingly.185"""186plat = get_build_platform()187m = macosVersionString.match(plat)188if m is not None and sys.platform == "darwin":189try:190plat = 'macosx-%s-%s' % ('.'.join(_macos_vers()[:2]), m.group(3))191except ValueError:192# not macOS193pass194return plat195196197__all__ = [198# Basic resource access and distribution/entry point discovery199'require', 'run_script', 'get_provider', 'get_distribution',200'load_entry_point', 'get_entry_map', 'get_entry_info',201'iter_entry_points',202'resource_string', 'resource_stream', 'resource_filename',203'resource_listdir', 'resource_exists', 'resource_isdir',204205# Environmental control206'declare_namespace', 'working_set', 'add_activation_listener',207'find_distributions', 'set_extraction_path', 'cleanup_resources',208'get_default_cache',209210# Primary implementation classes211'Environment', 'WorkingSet', 'ResourceManager',212'Distribution', 'Requirement', 'EntryPoint',213214# Exceptions215'ResolutionError', 'VersionConflict', 'DistributionNotFound',216'UnknownExtra', 'ExtractionError',217218# Warnings219'PEP440Warning',220221# Parsing functions and string utilities222'parse_requirements', 'parse_version', 'safe_name', 'safe_version',223'get_platform', 'compatible_platforms', 'yield_lines', 'split_sections',224'safe_extra', 'to_filename', 'invalid_marker', 'evaluate_marker',225226# filesystem utilities227'ensure_directory', 'normalize_path',228229# Distribution "precedence" constants230'EGG_DIST', 'BINARY_DIST', 'SOURCE_DIST', 'CHECKOUT_DIST', 'DEVELOP_DIST',231232# "Provider" interfaces, implementations, and registration/lookup APIs233'IMetadataProvider', 'IResourceProvider', 'FileMetadata',234'PathMetadata', 'EggMetadata', 'EmptyProvider', 'empty_provider',235'NullProvider', 'EggProvider', 'DefaultProvider', 'ZipProvider',236'register_finder', 'register_namespace_handler', 'register_loader_type',237'fixup_namespace_packages', 'get_importer',238239# Warnings240'PkgResourcesDeprecationWarning',241242# Deprecated/backward compatibility only243'run_main', 'AvailableDistributions',244]245246247class ResolutionError(Exception):248"""Abstract base for dependency resolution errors"""249250def __repr__(self):251return self.__class__.__name__ + repr(self.args)252253254class VersionConflict(ResolutionError):255"""256An already-installed version conflicts with the requested version.257258Should be initialized with the installed Distribution and the requested259Requirement.260"""261262_template = "{self.dist} is installed but {self.req} is required"263264@property265def dist(self):266return self.args[0]267268@property269def req(self):270return self.args[1]271272def report(self):273return self._template.format(**locals())274275def with_context(self, required_by):276"""277If required_by is non-empty, return a version of self that is a278ContextualVersionConflict.279"""280if not required_by:281return self282args = self.args + (required_by,)283return ContextualVersionConflict(*args)284285286class ContextualVersionConflict(VersionConflict):287"""288A VersionConflict that accepts a third parameter, the set of the289requirements that required the installed Distribution.290"""291292_template = VersionConflict._template + ' by {self.required_by}'293294@property295def required_by(self):296return self.args[2]297298299class DistributionNotFound(ResolutionError):300"""A requested distribution was not found"""301302_template = ("The '{self.req}' distribution was not found "303"and is required by {self.requirers_str}")304305@property306def req(self):307return self.args[0]308309@property310def requirers(self):311return self.args[1]312313@property314def requirers_str(self):315if not self.requirers:316return 'the application'317return ', '.join(self.requirers)318319def report(self):320return self._template.format(**locals())321322def __str__(self):323return self.report()324325326class UnknownExtra(ResolutionError):327"""Distribution doesn't have an "extra feature" of the given name"""328329330_provider_factories = {}331332PY_MAJOR = '{}.{}'.format(*sys.version_info)333EGG_DIST = 3334BINARY_DIST = 2335SOURCE_DIST = 1336CHECKOUT_DIST = 0337DEVELOP_DIST = -1338339340def register_loader_type(loader_type, provider_factory):341"""Register `provider_factory` to make providers for `loader_type`342343`loader_type` is the type or class of a PEP 302 ``module.__loader__``,344and `provider_factory` is a function that, passed a *module* object,345returns an ``IResourceProvider`` for that module.346"""347_provider_factories[loader_type] = provider_factory348349350def get_provider(moduleOrReq):351"""Return an IResourceProvider for the named module or requirement"""352if isinstance(moduleOrReq, Requirement):353return working_set.find(moduleOrReq) or require(str(moduleOrReq))[0]354try:355module = sys.modules[moduleOrReq]356except KeyError:357__import__(moduleOrReq)358module = sys.modules[moduleOrReq]359loader = getattr(module, '__loader__', None)360return _find_adapter(_provider_factories, loader)(module)361362363def _macos_vers(_cache=[]):364if not _cache:365version = platform.mac_ver()[0]366# fallback for MacPorts367if version == '':368plist = '/System/Library/CoreServices/SystemVersion.plist'369if os.path.exists(plist):370if hasattr(plistlib, 'readPlist'):371plist_content = plistlib.readPlist(plist)372if 'ProductVersion' in plist_content:373version = plist_content['ProductVersion']374375_cache.append(version.split('.'))376return _cache[0]377378379def _macos_arch(machine):380return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine)381382383def get_build_platform():384"""Return this platform's string for platform-specific distributions385386XXX Currently this is the same as ``distutils.util.get_platform()``, but it387needs some hacks for Linux and macOS.388"""389from sysconfig import get_platform390391plat = get_platform()392if sys.platform == "darwin" and not plat.startswith('macosx-'):393try:394version = _macos_vers()395machine = os.uname()[4].replace(" ", "_")396return "macosx-%d.%d-%s" % (397int(version[0]), int(version[1]),398_macos_arch(machine),399)400except ValueError:401# if someone is running a non-Mac darwin system, this will fall402# through to the default implementation403pass404return plat405406407macosVersionString = re.compile(r"macosx-(\d+)\.(\d+)-(.*)")408darwinVersionString = re.compile(r"darwin-(\d+)\.(\d+)\.(\d+)-(.*)")409# XXX backward compat410get_platform = get_build_platform411412413def compatible_platforms(provided, required):414"""Can code for the `provided` platform run on the `required` platform?415416Returns true if either platform is ``None``, or the platforms are equal.417418XXX Needs compatibility checks for Linux and other unixy OSes.419"""420if provided is None or required is None or provided == required:421# easy case422return True423424# macOS special cases425reqMac = macosVersionString.match(required)426if reqMac:427provMac = macosVersionString.match(provided)428429# is this a Mac package?430if not provMac:431# this is backwards compatibility for packages built before432# setuptools 0.6. All packages built after this point will433# use the new macOS designation.434provDarwin = darwinVersionString.match(provided)435if provDarwin:436dversion = int(provDarwin.group(1))437macosversion = "%s.%s" % (reqMac.group(1), reqMac.group(2))438if dversion == 7 and macosversion >= "10.3" or \439dversion == 8 and macosversion >= "10.4":440return True441# egg isn't macOS or legacy darwin442return False443444# are they the same major version and machine type?445if provMac.group(1) != reqMac.group(1) or \446provMac.group(3) != reqMac.group(3):447return False448449# is the required OS major update >= the provided one?450if int(provMac.group(2)) > int(reqMac.group(2)):451return False452453return True454455# XXX Linux and other platforms' special cases should go here456return False457458459def run_script(dist_spec, script_name):460"""Locate distribution `dist_spec` and run its `script_name` script"""461ns = sys._getframe(1).f_globals462name = ns['__name__']463ns.clear()464ns['__name__'] = name465require(dist_spec)[0].run_script(script_name, ns)466467468# backward compatibility469run_main = run_script470471472def get_distribution(dist):473"""Return a current distribution object for a Requirement or string"""474if isinstance(dist, str):475dist = Requirement.parse(dist)476if isinstance(dist, Requirement):477dist = get_provider(dist)478if not isinstance(dist, Distribution):479raise TypeError("Expected string, Requirement, or Distribution", dist)480return dist481482483def load_entry_point(dist, group, name):484"""Return `name` entry point of `group` for `dist` or raise ImportError"""485return get_distribution(dist).load_entry_point(group, name)486487488def get_entry_map(dist, group=None):489"""Return the entry point map for `group`, or the full entry map"""490return get_distribution(dist).get_entry_map(group)491492493def get_entry_info(dist, group, name):494"""Return the EntryPoint object for `group`+`name`, or ``None``"""495return get_distribution(dist).get_entry_info(group, name)496497498class IMetadataProvider:499def has_metadata(name):500"""Does the package's distribution contain the named metadata?"""501502def get_metadata(name):503"""The named metadata resource as a string"""504505def get_metadata_lines(name):506"""Yield named metadata resource as list of non-blank non-comment lines507508Leading and trailing whitespace is stripped from each line, and lines509with ``#`` as the first non-blank character are omitted."""510511def metadata_isdir(name):512"""Is the named metadata a directory? (like ``os.path.isdir()``)"""513514def metadata_listdir(name):515"""List of metadata names in the directory (like ``os.listdir()``)"""516517def run_script(script_name, namespace):518"""Execute the named script in the supplied namespace dictionary"""519520521class IResourceProvider(IMetadataProvider):522"""An object that provides access to package resources"""523524def get_resource_filename(manager, resource_name):525"""Return a true filesystem path for `resource_name`526527`manager` must be an ``IResourceManager``"""528529def get_resource_stream(manager, resource_name):530"""Return a readable file-like object for `resource_name`531532`manager` must be an ``IResourceManager``"""533534def get_resource_string(manager, resource_name):535"""Return a string containing the contents of `resource_name`536537`manager` must be an ``IResourceManager``"""538539def has_resource(resource_name):540"""Does the package contain the named resource?"""541542def resource_isdir(resource_name):543"""Is the named resource a directory? (like ``os.path.isdir()``)"""544545def resource_listdir(resource_name):546"""List of resource names in the directory (like ``os.listdir()``)"""547548549class WorkingSet:550"""A collection of active distributions on sys.path (or a similar list)"""551552def __init__(self, entries=None):553"""Create working set from list of path entries (default=sys.path)"""554self.entries = []555self.entry_keys = {}556self.by_key = {}557self.normalized_to_canonical_keys = {}558self.callbacks = []559560if entries is None:561entries = sys.path562563for entry in entries:564self.add_entry(entry)565566@classmethod567def _build_master(cls):568"""569Prepare the master working set.570"""571ws = cls()572try:573from __main__ import __requires__574except ImportError:575# The main program does not list any requirements576return ws577578# ensure the requirements are met579try:580ws.require(__requires__)581except VersionConflict:582return cls._build_from_requirements(__requires__)583584return ws585586@classmethod587def _build_from_requirements(cls, req_spec):588"""589Build a working set from a requirement spec. Rewrites sys.path.590"""591# try it without defaults already on sys.path592# by starting with an empty path593ws = cls([])594reqs = parse_requirements(req_spec)595dists = ws.resolve(reqs, Environment())596for dist in dists:597ws.add(dist)598599# add any missing entries from sys.path600for entry in sys.path:601if entry not in ws.entries:602ws.add_entry(entry)603604# then copy back to sys.path605sys.path[:] = ws.entries606return ws607608def add_entry(self, entry):609"""Add a path item to ``.entries``, finding any distributions on it610611``find_distributions(entry, True)`` is used to find distributions612corresponding to the path entry, and they are added. `entry` is613always appended to ``.entries``, even if it is already present.614(This is because ``sys.path`` can contain the same value more than615once, and the ``.entries`` of the ``sys.path`` WorkingSet should always616equal ``sys.path``.)617"""618self.entry_keys.setdefault(entry, [])619self.entries.append(entry)620for dist in find_distributions(entry, True):621self.add(dist, entry, False)622623def __contains__(self, dist):624"""True if `dist` is the active distribution for its project"""625return self.by_key.get(dist.key) == dist626627def find(self, req):628"""Find a distribution matching requirement `req`629630If there is an active distribution for the requested project, this631returns it as long as it meets the version requirement specified by632`req`. But, if there is an active distribution for the project and it633does *not* meet the `req` requirement, ``VersionConflict`` is raised.634If there is no active distribution for the requested project, ``None``635is returned.636"""637dist = self.by_key.get(req.key)638639if dist is None:640canonical_key = self.normalized_to_canonical_keys.get(req.key)641642if canonical_key is not None:643req.key = canonical_key644dist = self.by_key.get(canonical_key)645646if dist is not None and dist not in req:647# XXX add more info648raise VersionConflict(dist, req)649return dist650651def iter_entry_points(self, group, name=None):652"""Yield entry point objects from `group` matching `name`653654If `name` is None, yields all entry points in `group` from all655distributions in the working set, otherwise only ones matching656both `group` and `name` are yielded (in distribution order).657"""658return (659entry660for dist in self661for entry in dist.get_entry_map(group).values()662if name is None or name == entry.name663)664665def run_script(self, requires, script_name):666"""Locate distribution for `requires` and run `script_name` script"""667ns = sys._getframe(1).f_globals668name = ns['__name__']669ns.clear()670ns['__name__'] = name671self.require(requires)[0].run_script(script_name, ns)672673def __iter__(self):674"""Yield distributions for non-duplicate projects in the working set675676The yield order is the order in which the items' path entries were677added to the working set.678"""679seen = {}680for item in self.entries:681if item not in self.entry_keys:682# workaround a cache issue683continue684685for key in self.entry_keys[item]:686if key not in seen:687seen[key] = 1688yield self.by_key[key]689690def add(self, dist, entry=None, insert=True, replace=False):691"""Add `dist` to working set, associated with `entry`692693If `entry` is unspecified, it defaults to the ``.location`` of `dist`.694On exit from this routine, `entry` is added to the end of the working695set's ``.entries`` (if it wasn't already present).696697`dist` is only added to the working set if it's for a project that698doesn't already have a distribution in the set, unless `replace=True`.699If it's added, any callbacks registered with the ``subscribe()`` method700will be called.701"""702if insert:703dist.insert_on(self.entries, entry, replace=replace)704705if entry is None:706entry = dist.location707keys = self.entry_keys.setdefault(entry, [])708keys2 = self.entry_keys.setdefault(dist.location, [])709if not replace and dist.key in self.by_key:710# ignore hidden distros711return712713self.by_key[dist.key] = dist714normalized_name = packaging.utils.canonicalize_name(dist.key)715self.normalized_to_canonical_keys[normalized_name] = dist.key716if dist.key not in keys:717keys.append(dist.key)718if dist.key not in keys2:719keys2.append(dist.key)720self._added_new(dist)721722# FIXME: 'WorkingSet.resolve' is too complex (11)723def resolve(self, requirements, env=None, installer=None, # noqa: C901724replace_conflicting=False, extras=None):725"""List all distributions needed to (recursively) meet `requirements`726727`requirements` must be a sequence of ``Requirement`` objects. `env`,728if supplied, should be an ``Environment`` instance. If729not supplied, it defaults to all distributions available within any730entry or distribution in the working set. `installer`, if supplied,731will be invoked with each requirement that cannot be met by an732already-installed distribution; it should return a ``Distribution`` or733``None``.734735Unless `replace_conflicting=True`, raises a VersionConflict exception736if737any requirements are found on the path that have the correct name but738the wrong version. Otherwise, if an `installer` is supplied it will be739invoked to obtain the correct version of the requirement and activate740it.741742`extras` is a list of the extras to be used with these requirements.743This is important because extra requirements may look like `my_req;744extra = "my_extra"`, which would otherwise be interpreted as a purely745optional requirement. Instead, we want to be able to assert that these746requirements are truly required.747"""748749# set up the stack750requirements = list(requirements)[::-1]751# set of processed requirements752processed = {}753# key -> dist754best = {}755to_activate = []756757req_extras = _ReqExtras()758759# Mapping of requirement to set of distributions that required it;760# useful for reporting info about conflicts.761required_by = collections.defaultdict(set)762763while requirements:764# process dependencies breadth-first765req = requirements.pop(0)766if req in processed:767# Ignore cyclic or redundant dependencies768continue769770if not req_extras.markers_pass(req, extras):771continue772773dist = best.get(req.key)774if dist is None:775# Find the best distribution and add it to the map776dist = self.by_key.get(req.key)777if dist is None or (dist not in req and replace_conflicting):778ws = self779if env is None:780if dist is None:781env = Environment(self.entries)782else:783# Use an empty environment and workingset to avoid784# any further conflicts with the conflicting785# distribution786env = Environment([])787ws = WorkingSet([])788dist = best[req.key] = env.best_match(789req, ws, installer,790replace_conflicting=replace_conflicting791)792if dist is None:793requirers = required_by.get(req, None)794raise DistributionNotFound(req, requirers)795to_activate.append(dist)796if dist not in req:797# Oops, the "best" so far conflicts with a dependency798dependent_req = required_by[req]799raise VersionConflict(dist, req).with_context(dependent_req)800801# push the new requirements onto the stack802new_requirements = dist.requires(req.extras)[::-1]803requirements.extend(new_requirements)804805# Register the new requirements needed by req806for new_requirement in new_requirements:807required_by[new_requirement].add(req.project_name)808req_extras[new_requirement] = req.extras809810processed[req] = True811812# return list of distros to activate813return to_activate814815def find_plugins(816self, plugin_env, full_env=None, installer=None, fallback=True):817"""Find all activatable distributions in `plugin_env`818819Example usage::820821distributions, errors = working_set.find_plugins(822Environment(plugin_dirlist)823)824# add plugins+libs to sys.path825map(working_set.add, distributions)826# display errors827print('Could not load', errors)828829The `plugin_env` should be an ``Environment`` instance that contains830only distributions that are in the project's "plugin directory" or831directories. The `full_env`, if supplied, should be an ``Environment``832contains all currently-available distributions. If `full_env` is not833supplied, one is created automatically from the ``WorkingSet`` this834method is called on, which will typically mean that every directory on835``sys.path`` will be scanned for distributions.836837`installer` is a standard installer callback as used by the838``resolve()`` method. The `fallback` flag indicates whether we should839attempt to resolve older versions of a plugin if the newest version840cannot be resolved.841842This method returns a 2-tuple: (`distributions`, `error_info`), where843`distributions` is a list of the distributions found in `plugin_env`844that were loadable, along with any other distributions that are needed845to resolve their dependencies. `error_info` is a dictionary mapping846unloadable plugin distributions to an exception instance describing the847error that occurred. Usually this will be a ``DistributionNotFound`` or848``VersionConflict`` instance.849"""850851plugin_projects = list(plugin_env)852# scan project names in alphabetic order853plugin_projects.sort()854855error_info = {}856distributions = {}857858if full_env is None:859env = Environment(self.entries)860env += plugin_env861else:862env = full_env + plugin_env863864shadow_set = self.__class__([])865# put all our entries in shadow_set866list(map(shadow_set.add, self))867868for project_name in plugin_projects:869870for dist in plugin_env[project_name]:871872req = [dist.as_requirement()]873874try:875resolvees = shadow_set.resolve(req, env, installer)876877except ResolutionError as v:878# save error info879error_info[dist] = v880if fallback:881# try the next older version of project882continue883else:884# give up on this project, keep going885break886887else:888list(map(shadow_set.add, resolvees))889distributions.update(dict.fromkeys(resolvees))890891# success, no need to try any more versions of this project892break893894distributions = list(distributions)895distributions.sort()896897return distributions, error_info898899def require(self, *requirements):900"""Ensure that distributions matching `requirements` are activated901902`requirements` must be a string or a (possibly-nested) sequence903thereof, specifying the distributions and versions required. The904return value is a sequence of the distributions that needed to be905activated to fulfill the requirements; all relevant distributions are906included, even if they were already activated in this working set.907"""908needed = self.resolve(parse_requirements(requirements))909910for dist in needed:911self.add(dist)912913return needed914915def subscribe(self, callback, existing=True):916"""Invoke `callback` for all distributions917918If `existing=True` (default),919call on all existing ones, as well.920"""921if callback in self.callbacks:922return923self.callbacks.append(callback)924if not existing:925return926for dist in self:927callback(dist)928929def _added_new(self, dist):930for callback in self.callbacks:931callback(dist)932933def __getstate__(self):934return (935self.entries[:], self.entry_keys.copy(), self.by_key.copy(),936self.normalized_to_canonical_keys.copy(), self.callbacks[:]937)938939def __setstate__(self, e_k_b_n_c):940entries, keys, by_key, normalized_to_canonical_keys, callbacks = e_k_b_n_c941self.entries = entries[:]942self.entry_keys = keys.copy()943self.by_key = by_key.copy()944self.normalized_to_canonical_keys = normalized_to_canonical_keys.copy()945self.callbacks = callbacks[:]946947948class _ReqExtras(dict):949"""950Map each requirement to the extras that demanded it.951"""952953def markers_pass(self, req, extras=None):954"""955Evaluate markers for req against each extra that956demanded it.957958Return False if the req has a marker and fails959evaluation. Otherwise, return True.960"""961extra_evals = (962req.marker.evaluate({'extra': extra})963for extra in self.get(req, ()) + (extras or (None,))964)965return not req.marker or any(extra_evals)966967968class Environment:969"""Searchable snapshot of distributions on a search path"""970971def __init__(972self, search_path=None, platform=get_supported_platform(),973python=PY_MAJOR):974"""Snapshot distributions available on a search path975976Any distributions found on `search_path` are added to the environment.977`search_path` should be a sequence of ``sys.path`` items. If not978supplied, ``sys.path`` is used.979980`platform` is an optional string specifying the name of the platform981that platform-specific distributions must be compatible with. If982unspecified, it defaults to the current platform. `python` is an983optional string naming the desired version of Python (e.g. ``'3.6'``);984it defaults to the current version.985986You may explicitly set `platform` (and/or `python`) to ``None`` if you987wish to map *all* distributions, not just those compatible with the988running platform or Python version.989"""990self._distmap = {}991self.platform = platform992self.python = python993self.scan(search_path)994995def can_add(self, dist):996"""Is distribution `dist` acceptable for this environment?997998The distribution must match the platform and python version999requirements specified when this environment was created, or False1000is returned.1001"""1002py_compat = (1003self.python is None1004or dist.py_version is None1005or dist.py_version == self.python1006)1007return py_compat and compatible_platforms(dist.platform, self.platform)10081009def remove(self, dist):1010"""Remove `dist` from the environment"""1011self._distmap[dist.key].remove(dist)10121013def scan(self, search_path=None):1014"""Scan `search_path` for distributions usable in this environment10151016Any distributions found are added to the environment.1017`search_path` should be a sequence of ``sys.path`` items. If not1018supplied, ``sys.path`` is used. Only distributions conforming to1019the platform/python version defined at initialization are added.1020"""1021if search_path is None:1022search_path = sys.path10231024for item in search_path:1025for dist in find_distributions(item):1026self.add(dist)10271028def __getitem__(self, project_name):1029"""Return a newest-to-oldest list of distributions for `project_name`10301031Uses case-insensitive `project_name` comparison, assuming all the1032project's distributions use their project's name converted to all1033lowercase as their key.10341035"""1036distribution_key = project_name.lower()1037return self._distmap.get(distribution_key, [])10381039def add(self, dist):1040"""Add `dist` if we ``can_add()`` it and it has not already been added1041"""1042if self.can_add(dist) and dist.has_version():1043dists = self._distmap.setdefault(dist.key, [])1044if dist not in dists:1045dists.append(dist)1046dists.sort(key=operator.attrgetter('hashcmp'), reverse=True)10471048def best_match(1049self, req, working_set, installer=None, replace_conflicting=False):1050"""Find distribution best matching `req` and usable on `working_set`10511052This calls the ``find(req)`` method of the `working_set` to see if a1053suitable distribution is already active. (This may raise1054``VersionConflict`` if an unsuitable version of the project is already1055active in the specified `working_set`.) If a suitable distribution1056isn't active, this method returns the newest distribution in the1057environment that meets the ``Requirement`` in `req`. If no suitable1058distribution is found, and `installer` is supplied, then the result of1059calling the environment's ``obtain(req, installer)`` method will be1060returned.1061"""1062try:1063dist = working_set.find(req)1064except VersionConflict:1065if not replace_conflicting:1066raise1067dist = None1068if dist is not None:1069return dist1070for dist in self[req.key]:1071if dist in req:1072return dist1073# try to download/install1074return self.obtain(req, installer)10751076def obtain(self, requirement, installer=None):1077"""Obtain a distribution matching `requirement` (e.g. via download)10781079Obtain a distro that matches requirement (e.g. via download). In the1080base ``Environment`` class, this routine just returns1081``installer(requirement)``, unless `installer` is None, in which case1082None is returned instead. This method is a hook that allows subclasses1083to attempt other ways of obtaining a distribution before falling back1084to the `installer` argument."""1085if installer is not None:1086return installer(requirement)10871088def __iter__(self):1089"""Yield the unique project names of the available distributions"""1090for key in self._distmap.keys():1091if self[key]:1092yield key10931094def __iadd__(self, other):1095"""In-place addition of a distribution or environment"""1096if isinstance(other, Distribution):1097self.add(other)1098elif isinstance(other, Environment):1099for project in other:1100for dist in other[project]:1101self.add(dist)1102else:1103raise TypeError("Can't add %r to environment" % (other,))1104return self11051106def __add__(self, other):1107"""Add an environment or distribution to an environment"""1108new = self.__class__([], platform=None, python=None)1109for env in self, other:1110new += env1111return new111211131114# XXX backward compatibility1115AvailableDistributions = Environment111611171118class ExtractionError(RuntimeError):1119"""An error occurred extracting a resource11201121The following attributes are available from instances of this exception:11221123manager1124The resource manager that raised this exception11251126cache_path1127The base directory for resource extraction11281129original_error1130The exception instance that caused extraction to fail1131"""113211331134class ResourceManager:1135"""Manage resource extraction and packages"""1136extraction_path = None11371138def __init__(self):1139self.cached_files = {}11401141def resource_exists(self, package_or_requirement, resource_name):1142"""Does the named resource exist?"""1143return get_provider(package_or_requirement).has_resource(resource_name)11441145def resource_isdir(self, package_or_requirement, resource_name):1146"""Is the named resource an existing directory?"""1147return get_provider(package_or_requirement).resource_isdir(1148resource_name1149)11501151def resource_filename(self, package_or_requirement, resource_name):1152"""Return a true filesystem path for specified resource"""1153return get_provider(package_or_requirement).get_resource_filename(1154self, resource_name1155)11561157def resource_stream(self, package_or_requirement, resource_name):1158"""Return a readable file-like object for specified resource"""1159return get_provider(package_or_requirement).get_resource_stream(1160self, resource_name1161)11621163def resource_string(self, package_or_requirement, resource_name):1164"""Return specified resource as a string"""1165return get_provider(package_or_requirement).get_resource_string(1166self, resource_name1167)11681169def resource_listdir(self, package_or_requirement, resource_name):1170"""List the contents of the named resource directory"""1171return get_provider(package_or_requirement).resource_listdir(1172resource_name1173)11741175def extraction_error(self):1176"""Give an error message for problems extracting file(s)"""11771178old_exc = sys.exc_info()[1]1179cache_path = self.extraction_path or get_default_cache()11801181tmpl = textwrap.dedent("""1182Can't extract file(s) to egg cache11831184The following error occurred while trying to extract file(s)1185to the Python egg cache:11861187{old_exc}11881189The Python egg cache directory is currently set to:11901191{cache_path}11921193Perhaps your account does not have write access to this directory?1194You can change the cache directory by setting the PYTHON_EGG_CACHE1195environment variable to point to an accessible directory.1196""").lstrip()1197err = ExtractionError(tmpl.format(**locals()))1198err.manager = self1199err.cache_path = cache_path1200err.original_error = old_exc1201raise err12021203def get_cache_path(self, archive_name, names=()):1204"""Return absolute location in cache for `archive_name` and `names`12051206The parent directory of the resulting path will be created if it does1207not already exist. `archive_name` should be the base filename of the1208enclosing egg (which may not be the name of the enclosing zipfile!),1209including its ".egg" extension. `names`, if provided, should be a1210sequence of path name parts "under" the egg's extraction location.12111212This method should only be called by resource providers that need to1213obtain an extraction location, and only for names they intend to1214extract, as it tracks the generated names for possible cleanup later.1215"""1216extract_path = self.extraction_path or get_default_cache()1217target_path = os.path.join(extract_path, archive_name + '-tmp', *names)1218try:1219_bypass_ensure_directory(target_path)1220except Exception:1221self.extraction_error()12221223self._warn_unsafe_extraction_path(extract_path)12241225self.cached_files[target_path] = 11226return target_path12271228@staticmethod1229def _warn_unsafe_extraction_path(path):1230"""1231If the default extraction path is overridden and set to an insecure1232location, such as /tmp, it opens up an opportunity for an attacker to1233replace an extracted file with an unauthorized payload. Warn the user1234if a known insecure location is used.12351236See Distribute #375 for more details.1237"""1238if os.name == 'nt' and not path.startswith(os.environ['windir']):1239# On Windows, permissions are generally restrictive by default1240# and temp directories are not writable by other users, so1241# bypass the warning.1242return1243mode = os.stat(path).st_mode1244if mode & stat.S_IWOTH or mode & stat.S_IWGRP:1245msg = (1246"Extraction path is writable by group/others "1247"and vulnerable to attack when "1248"used with get_resource_filename ({path}). "1249"Consider a more secure "1250"location (set with .set_extraction_path or the "1251"PYTHON_EGG_CACHE environment variable)."1252).format(**locals())1253warnings.warn(msg, UserWarning)12541255def postprocess(self, tempname, filename):1256"""Perform any platform-specific postprocessing of `tempname`12571258This is where Mac header rewrites should be done; other platforms don't1259have anything special they should do.12601261Resource providers should call this method ONLY after successfully1262extracting a compressed resource. They must NOT call it on resources1263that are already in the filesystem.12641265`tempname` is the current (temporary) name of the file, and `filename`1266is the name it will be renamed to by the caller after this routine1267returns.1268"""12691270if os.name == 'posix':1271# Make the resource executable1272mode = ((os.stat(tempname).st_mode) | 0o555) & 0o77771273os.chmod(tempname, mode)12741275def set_extraction_path(self, path):1276"""Set the base path where resources will be extracted to, if needed.12771278If you do not call this routine before any extractions take place, the1279path defaults to the return value of ``get_default_cache()``. (Which1280is based on the ``PYTHON_EGG_CACHE`` environment variable, with various1281platform-specific fallbacks. See that routine's documentation for more1282details.)12831284Resources are extracted to subdirectories of this path based upon1285information given by the ``IResourceProvider``. You may set this to a1286temporary directory, but then you must call ``cleanup_resources()`` to1287delete the extracted files when done. There is no guarantee that1288``cleanup_resources()`` will be able to remove all extracted files.12891290(Note: you may not change the extraction path for a given resource1291manager once resources have been extracted, unless you first call1292``cleanup_resources()``.)1293"""1294if self.cached_files:1295raise ValueError(1296"Can't change extraction path, files already extracted"1297)12981299self.extraction_path = path13001301def cleanup_resources(self, force=False):1302"""1303Delete all extracted resource files and directories, returning a list1304of the file and directory names that could not be successfully removed.1305This function does not have any concurrency protection, so it should1306generally only be called when the extraction path is a temporary1307directory exclusive to a single process. This method is not1308automatically called; you must call it explicitly or register it as an1309``atexit`` function if you wish to ensure cleanup of a temporary1310directory used for extractions.1311"""1312# XXX131313141315def get_default_cache():1316"""1317Return the ``PYTHON_EGG_CACHE`` environment variable1318or a platform-relevant user cache dir for an app1319named "Python-Eggs".1320"""1321return (1322os.environ.get('PYTHON_EGG_CACHE')1323or appdirs.user_cache_dir(appname='Python-Eggs')1324)132513261327def safe_name(name):1328"""Convert an arbitrary string to a standard distribution name13291330Any runs of non-alphanumeric/. characters are replaced with a single '-'.1331"""1332return re.sub('[^A-Za-z0-9.]+', '-', name)133313341335def safe_version(version):1336"""1337Convert an arbitrary string to a standard version string1338"""1339try:1340# normalize the version1341return str(packaging.version.Version(version))1342except packaging.version.InvalidVersion:1343version = version.replace(' ', '.')1344return re.sub('[^A-Za-z0-9.]+', '-', version)134513461347def safe_extra(extra):1348"""Convert an arbitrary string to a standard 'extra' name13491350Any runs of non-alphanumeric characters are replaced with a single '_',1351and the result is always lowercased.1352"""1353return re.sub('[^A-Za-z0-9.-]+', '_', extra).lower()135413551356def to_filename(name):1357"""Convert a project or version name to its filename-escaped form13581359Any '-' characters are currently replaced with '_'.1360"""1361return name.replace('-', '_')136213631364def invalid_marker(text):1365"""1366Validate text as a PEP 508 environment marker; return an exception1367if invalid or False otherwise.1368"""1369try:1370evaluate_marker(text)1371except SyntaxError as e:1372e.filename = None1373e.lineno = None1374return e1375return False137613771378def evaluate_marker(text, extra=None):1379"""1380Evaluate a PEP 508 environment marker.1381Return a boolean indicating the marker result in this environment.1382Raise SyntaxError if marker is invalid.13831384This implementation uses the 'pyparsing' module.1385"""1386try:1387marker = packaging.markers.Marker(text)1388return marker.evaluate()1389except packaging.markers.InvalidMarker as e:1390raise SyntaxError(e) from e139113921393class NullProvider:1394"""Try to implement resources and metadata for arbitrary PEP 302 loaders"""13951396egg_name = None1397egg_info = None1398loader = None13991400def __init__(self, module):1401self.loader = getattr(module, '__loader__', None)1402self.module_path = os.path.dirname(getattr(module, '__file__', ''))14031404def get_resource_filename(self, manager, resource_name):1405return self._fn(self.module_path, resource_name)14061407def get_resource_stream(self, manager, resource_name):1408return io.BytesIO(self.get_resource_string(manager, resource_name))14091410def get_resource_string(self, manager, resource_name):1411return self._get(self._fn(self.module_path, resource_name))14121413def has_resource(self, resource_name):1414return self._has(self._fn(self.module_path, resource_name))14151416def _get_metadata_path(self, name):1417return self._fn(self.egg_info, name)14181419def has_metadata(self, name):1420if not self.egg_info:1421return self.egg_info14221423path = self._get_metadata_path(name)1424return self._has(path)14251426def get_metadata(self, name):1427if not self.egg_info:1428return ""1429path = self._get_metadata_path(name)1430value = self._get(path)1431try:1432return value.decode('utf-8')1433except UnicodeDecodeError as exc:1434# Include the path in the error message to simplify1435# troubleshooting, and without changing the exception type.1436exc.reason += ' in {} file at path: {}'.format(name, path)1437raise14381439def get_metadata_lines(self, name):1440return yield_lines(self.get_metadata(name))14411442def resource_isdir(self, resource_name):1443return self._isdir(self._fn(self.module_path, resource_name))14441445def metadata_isdir(self, name):1446return self.egg_info and self._isdir(self._fn(self.egg_info, name))14471448def resource_listdir(self, resource_name):1449return self._listdir(self._fn(self.module_path, resource_name))14501451def metadata_listdir(self, name):1452if self.egg_info:1453return self._listdir(self._fn(self.egg_info, name))1454return []14551456def run_script(self, script_name, namespace):1457script = 'scripts/' + script_name1458if not self.has_metadata(script):1459raise ResolutionError(1460"Script {script!r} not found in metadata at {self.egg_info!r}"1461.format(**locals()),1462)1463script_text = self.get_metadata(script).replace('\r\n', '\n')1464script_text = script_text.replace('\r', '\n')1465script_filename = self._fn(self.egg_info, script)1466namespace['__file__'] = script_filename1467if os.path.exists(script_filename):1468with open(script_filename) as fid:1469source = fid.read()1470code = compile(source, script_filename, 'exec')1471exec(code, namespace, namespace)1472else:1473from linecache import cache1474cache[script_filename] = (1475len(script_text), 0, script_text.split('\n'), script_filename1476)1477script_code = compile(script_text, script_filename, 'exec')1478exec(script_code, namespace, namespace)14791480def _has(self, path):1481raise NotImplementedError(1482"Can't perform this operation for unregistered loader type"1483)14841485def _isdir(self, path):1486raise NotImplementedError(1487"Can't perform this operation for unregistered loader type"1488)14891490def _listdir(self, path):1491raise NotImplementedError(1492"Can't perform this operation for unregistered loader type"1493)14941495def _fn(self, base, resource_name):1496self._validate_resource_path(resource_name)1497if resource_name:1498return os.path.join(base, *resource_name.split('/'))1499return base15001501@staticmethod1502def _validate_resource_path(path):1503"""1504Validate the resource paths according to the docs.1505https://setuptools.pypa.io/en/latest/pkg_resources.html#basic-resource-access15061507>>> warned = getfixture('recwarn')1508>>> warnings.simplefilter('always')1509>>> vrp = NullProvider._validate_resource_path1510>>> vrp('foo/bar.txt')1511>>> bool(warned)1512False1513>>> vrp('../foo/bar.txt')1514>>> bool(warned)1515True1516>>> warned.clear()1517>>> vrp('/foo/bar.txt')1518>>> bool(warned)1519True1520>>> vrp('foo/../../bar.txt')1521>>> bool(warned)1522True1523>>> warned.clear()1524>>> vrp('foo/f../bar.txt')1525>>> bool(warned)1526False15271528Windows path separators are straight-up disallowed.1529>>> vrp(r'\\foo/bar.txt')1530Traceback (most recent call last):1531...1532ValueError: Use of .. or absolute path in a resource path \1533is not allowed.15341535>>> vrp(r'C:\\foo/bar.txt')1536Traceback (most recent call last):1537...1538ValueError: Use of .. or absolute path in a resource path \1539is not allowed.15401541Blank values are allowed15421543>>> vrp('')1544>>> bool(warned)1545False15461547Non-string values are not.15481549>>> vrp(None)1550Traceback (most recent call last):1551...1552AttributeError: ...1553"""1554invalid = (1555os.path.pardir in path.split(posixpath.sep) or1556posixpath.isabs(path) or1557ntpath.isabs(path)1558)1559if not invalid:1560return15611562msg = "Use of .. or absolute path in a resource path is not allowed."15631564# Aggressively disallow Windows absolute paths1565if ntpath.isabs(path) and not posixpath.isabs(path):1566raise ValueError(msg)15671568# for compatibility, warn; in future1569# raise ValueError(msg)1570warnings.warn(1571msg[:-1] + " and will raise exceptions in a future release.",1572DeprecationWarning,1573stacklevel=4,1574)15751576def _get(self, path):1577if hasattr(self.loader, 'get_data'):1578return self.loader.get_data(path)1579raise NotImplementedError(1580"Can't perform this operation for loaders without 'get_data()'"1581)158215831584register_loader_type(object, NullProvider)158515861587def _parents(path):1588"""1589yield all parents of path including path1590"""1591last = None1592while path != last:1593yield path1594last = path1595path, _ = os.path.split(path)159615971598class EggProvider(NullProvider):1599"""Provider based on a virtual filesystem"""16001601def __init__(self, module):1602super().__init__(module)1603self._setup_prefix()16041605def _setup_prefix(self):1606# Assume that metadata may be nested inside a "basket"1607# of multiple eggs and use module_path instead of .archive.1608eggs = filter(_is_egg_path, _parents(self.module_path))1609egg = next(eggs, None)1610egg and self._set_egg(egg)16111612def _set_egg(self, path):1613self.egg_name = os.path.basename(path)1614self.egg_info = os.path.join(path, 'EGG-INFO')1615self.egg_root = path161616171618class DefaultProvider(EggProvider):1619"""Provides access to package resources in the filesystem"""16201621def _has(self, path):1622return os.path.exists(path)16231624def _isdir(self, path):1625return os.path.isdir(path)16261627def _listdir(self, path):1628return os.listdir(path)16291630def get_resource_stream(self, manager, resource_name):1631return open(self._fn(self.module_path, resource_name), 'rb')16321633def _get(self, path):1634with open(path, 'rb') as stream:1635return stream.read()16361637@classmethod1638def _register(cls):1639loader_names = 'SourceFileLoader', 'SourcelessFileLoader',1640for name in loader_names:1641loader_cls = getattr(importlib_machinery, name, type(None))1642register_loader_type(loader_cls, cls)164316441645DefaultProvider._register()164616471648class EmptyProvider(NullProvider):1649"""Provider that returns nothing for all requests"""16501651module_path = None16521653_isdir = _has = lambda self, path: False16541655def _get(self, path):1656return ''16571658def _listdir(self, path):1659return []16601661def __init__(self):1662pass166316641665empty_provider = EmptyProvider()166616671668class ZipManifests(dict):1669"""1670zip manifest builder1671"""16721673@classmethod1674def build(cls, path):1675"""1676Build a dictionary similar to the zipimport directory1677caches, except instead of tuples, store ZipInfo objects.16781679Use a platform-specific path separator (os.sep) for the path keys1680for compatibility with pypy on Windows.1681"""1682with zipfile.ZipFile(path) as zfile:1683items = (1684(1685name.replace('/', os.sep),1686zfile.getinfo(name),1687)1688for name in zfile.namelist()1689)1690return dict(items)16911692load = build169316941695class MemoizedZipManifests(ZipManifests):1696"""1697Memoized zipfile manifests.1698"""1699manifest_mod = collections.namedtuple('manifest_mod', 'manifest mtime')17001701def load(self, path):1702"""1703Load a manifest at path or return a suitable manifest already loaded.1704"""1705path = os.path.normpath(path)1706mtime = os.stat(path).st_mtime17071708if path not in self or self[path].mtime != mtime:1709manifest = self.build(path)1710self[path] = self.manifest_mod(manifest, mtime)17111712return self[path].manifest171317141715class ZipProvider(EggProvider):1716"""Resource support for zips and eggs"""17171718eagers = None1719_zip_manifests = MemoizedZipManifests()17201721def __init__(self, module):1722super().__init__(module)1723self.zip_pre = self.loader.archive + os.sep17241725def _zipinfo_name(self, fspath):1726# Convert a virtual filename (full path to file) into a zipfile subpath1727# usable with the zipimport directory cache for our target archive1728fspath = fspath.rstrip(os.sep)1729if fspath == self.loader.archive:1730return ''1731if fspath.startswith(self.zip_pre):1732return fspath[len(self.zip_pre):]1733raise AssertionError(1734"%s is not a subpath of %s" % (fspath, self.zip_pre)1735)17361737def _parts(self, zip_path):1738# Convert a zipfile subpath into an egg-relative path part list.1739# pseudo-fs path1740fspath = self.zip_pre + zip_path1741if fspath.startswith(self.egg_root + os.sep):1742return fspath[len(self.egg_root) + 1:].split(os.sep)1743raise AssertionError(1744"%s is not a subpath of %s" % (fspath, self.egg_root)1745)17461747@property1748def zipinfo(self):1749return self._zip_manifests.load(self.loader.archive)17501751def get_resource_filename(self, manager, resource_name):1752if not self.egg_name:1753raise NotImplementedError(1754"resource_filename() only supported for .egg, not .zip"1755)1756# no need to lock for extraction, since we use temp names1757zip_path = self._resource_to_zip(resource_name)1758eagers = self._get_eager_resources()1759if '/'.join(self._parts(zip_path)) in eagers:1760for name in eagers:1761self._extract_resource(manager, self._eager_to_zip(name))1762return self._extract_resource(manager, zip_path)17631764@staticmethod1765def _get_date_and_size(zip_stat):1766size = zip_stat.file_size1767# ymdhms+wday, yday, dst1768date_time = zip_stat.date_time + (0, 0, -1)1769# 1980 offset already done1770timestamp = time.mktime(date_time)1771return timestamp, size17721773# FIXME: 'ZipProvider._extract_resource' is too complex (12)1774def _extract_resource(self, manager, zip_path): # noqa: C90117751776if zip_path in self._index():1777for name in self._index()[zip_path]:1778last = self._extract_resource(1779manager, os.path.join(zip_path, name)1780)1781# return the extracted directory name1782return os.path.dirname(last)17831784timestamp, size = self._get_date_and_size(self.zipinfo[zip_path])17851786if not WRITE_SUPPORT:1787raise IOError('"os.rename" and "os.unlink" are not supported '1788'on this platform')1789try:17901791real_path = manager.get_cache_path(1792self.egg_name, self._parts(zip_path)1793)17941795if self._is_current(real_path, zip_path):1796return real_path17971798outf, tmpnam = _mkstemp(1799".$extract",1800dir=os.path.dirname(real_path),1801)1802os.write(outf, self.loader.get_data(zip_path))1803os.close(outf)1804utime(tmpnam, (timestamp, timestamp))1805manager.postprocess(tmpnam, real_path)18061807try:1808rename(tmpnam, real_path)18091810except os.error:1811if os.path.isfile(real_path):1812if self._is_current(real_path, zip_path):1813# the file became current since it was checked above,1814# so proceed.1815return real_path1816# Windows, del old file and retry1817elif os.name == 'nt':1818unlink(real_path)1819rename(tmpnam, real_path)1820return real_path1821raise18221823except os.error:1824# report a user-friendly error1825manager.extraction_error()18261827return real_path18281829def _is_current(self, file_path, zip_path):1830"""1831Return True if the file_path is current for this zip_path1832"""1833timestamp, size = self._get_date_and_size(self.zipinfo[zip_path])1834if not os.path.isfile(file_path):1835return False1836stat = os.stat(file_path)1837if stat.st_size != size or stat.st_mtime != timestamp:1838return False1839# check that the contents match1840zip_contents = self.loader.get_data(zip_path)1841with open(file_path, 'rb') as f:1842file_contents = f.read()1843return zip_contents == file_contents18441845def _get_eager_resources(self):1846if self.eagers is None:1847eagers = []1848for name in ('native_libs.txt', 'eager_resources.txt'):1849if self.has_metadata(name):1850eagers.extend(self.get_metadata_lines(name))1851self.eagers = eagers1852return self.eagers18531854def _index(self):1855try:1856return self._dirindex1857except AttributeError:1858ind = {}1859for path in self.zipinfo:1860parts = path.split(os.sep)1861while parts:1862parent = os.sep.join(parts[:-1])1863if parent in ind:1864ind[parent].append(parts[-1])1865break1866else:1867ind[parent] = [parts.pop()]1868self._dirindex = ind1869return ind18701871def _has(self, fspath):1872zip_path = self._zipinfo_name(fspath)1873return zip_path in self.zipinfo or zip_path in self._index()18741875def _isdir(self, fspath):1876return self._zipinfo_name(fspath) in self._index()18771878def _listdir(self, fspath):1879return list(self._index().get(self._zipinfo_name(fspath), ()))18801881def _eager_to_zip(self, resource_name):1882return self._zipinfo_name(self._fn(self.egg_root, resource_name))18831884def _resource_to_zip(self, resource_name):1885return self._zipinfo_name(self._fn(self.module_path, resource_name))188618871888register_loader_type(zipimport.zipimporter, ZipProvider)188918901891class FileMetadata(EmptyProvider):1892"""Metadata handler for standalone PKG-INFO files18931894Usage::18951896metadata = FileMetadata("/path/to/PKG-INFO")18971898This provider rejects all data and metadata requests except for PKG-INFO,1899which is treated as existing, and will be the contents of the file at1900the provided location.1901"""19021903def __init__(self, path):1904self.path = path19051906def _get_metadata_path(self, name):1907return self.path19081909def has_metadata(self, name):1910return name == 'PKG-INFO' and os.path.isfile(self.path)19111912def get_metadata(self, name):1913if name != 'PKG-INFO':1914raise KeyError("No metadata except PKG-INFO is available")19151916with io.open(self.path, encoding='utf-8', errors="replace") as f:1917metadata = f.read()1918self._warn_on_replacement(metadata)1919return metadata19201921def _warn_on_replacement(self, metadata):1922replacement_char = '�'1923if replacement_char in metadata:1924tmpl = "{self.path} could not be properly decoded in UTF-8"1925msg = tmpl.format(**locals())1926warnings.warn(msg)19271928def get_metadata_lines(self, name):1929return yield_lines(self.get_metadata(name))193019311932class PathMetadata(DefaultProvider):1933"""Metadata provider for egg directories19341935Usage::19361937# Development eggs:19381939egg_info = "/path/to/PackageName.egg-info"1940base_dir = os.path.dirname(egg_info)1941metadata = PathMetadata(base_dir, egg_info)1942dist_name = os.path.splitext(os.path.basename(egg_info))[0]1943dist = Distribution(basedir, project_name=dist_name, metadata=metadata)19441945# Unpacked egg directories:19461947egg_path = "/path/to/PackageName-ver-pyver-etc.egg"1948metadata = PathMetadata(egg_path, os.path.join(egg_path,'EGG-INFO'))1949dist = Distribution.from_filename(egg_path, metadata=metadata)1950"""19511952def __init__(self, path, egg_info):1953self.module_path = path1954self.egg_info = egg_info195519561957class EggMetadata(ZipProvider):1958"""Metadata provider for .egg files"""19591960def __init__(self, importer):1961"""Create a metadata provider from a zipimporter"""19621963self.zip_pre = importer.archive + os.sep1964self.loader = importer1965if importer.prefix:1966self.module_path = os.path.join(importer.archive, importer.prefix)1967else:1968self.module_path = importer.archive1969self._setup_prefix()197019711972_declare_state('dict', _distribution_finders={})197319741975def register_finder(importer_type, distribution_finder):1976"""Register `distribution_finder` to find distributions in sys.path items19771978`importer_type` is the type or class of a PEP 302 "Importer" (sys.path item1979handler), and `distribution_finder` is a callable that, passed a path1980item and the importer instance, yields ``Distribution`` instances found on1981that path item. See ``pkg_resources.find_on_path`` for an example."""1982_distribution_finders[importer_type] = distribution_finder198319841985def find_distributions(path_item, only=False):1986"""Yield distributions accessible via `path_item`"""1987importer = get_importer(path_item)1988finder = _find_adapter(_distribution_finders, importer)1989return finder(importer, path_item, only)199019911992def find_eggs_in_zip(importer, path_item, only=False):1993"""1994Find eggs in zip files; possibly multiple nested eggs.1995"""1996if importer.archive.endswith('.whl'):1997# wheels are not supported with this finder1998# they don't have PKG-INFO metadata, and won't ever contain eggs1999return2000metadata = EggMetadata(importer)2001if metadata.has_metadata('PKG-INFO'):2002yield Distribution.from_filename(path_item, metadata=metadata)2003if only:2004# don't yield nested distros2005return2006for subitem in metadata.resource_listdir(''):2007if _is_egg_path(subitem):2008subpath = os.path.join(path_item, subitem)2009dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath)2010for dist in dists:2011yield dist2012elif subitem.lower().endswith(('.dist-info', '.egg-info')):2013subpath = os.path.join(path_item, subitem)2014submeta = EggMetadata(zipimport.zipimporter(subpath))2015submeta.egg_info = subpath2016yield Distribution.from_location(path_item, subitem, submeta)201720182019register_finder(zipimport.zipimporter, find_eggs_in_zip)202020212022def find_nothing(importer, path_item, only=False):2023return ()202420252026register_finder(object, find_nothing)202720282029def _by_version_descending(names):2030"""2031Given a list of filenames, return them in descending order2032by version number.20332034>>> names = 'bar', 'foo', 'Python-2.7.10.egg', 'Python-2.7.2.egg'2035>>> _by_version_descending(names)2036['Python-2.7.10.egg', 'Python-2.7.2.egg', 'bar', 'foo']2037>>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.egg'2038>>> _by_version_descending(names)2039['Setuptools-1.2.3.egg', 'Setuptools-1.2.3b1.egg']2040>>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.post1.egg'2041>>> _by_version_descending(names)2042['Setuptools-1.2.3.post1.egg', 'Setuptools-1.2.3b1.egg']2043"""2044def try_parse(name):2045"""2046Attempt to parse as a version or return a null version.2047"""2048try:2049return packaging.version.Version(name)2050except Exception:2051return packaging.version.Version('0')20522053def _by_version(name):2054"""2055Parse each component of the filename2056"""2057name, ext = os.path.splitext(name)2058parts = itertools.chain(name.split('-'), [ext])2059return [try_parse(part) for part in parts]20602061return sorted(names, key=_by_version, reverse=True)206220632064def find_on_path(importer, path_item, only=False):2065"""Yield distributions accessible on a sys.path directory"""2066path_item = _normalize_cached(path_item)20672068if _is_unpacked_egg(path_item):2069yield Distribution.from_filename(2070path_item, metadata=PathMetadata(2071path_item, os.path.join(path_item, 'EGG-INFO')2072)2073)2074return20752076entries = (2077os.path.join(path_item, child)2078for child in safe_listdir(path_item)2079)20802081# for performance, before sorting by version,2082# screen entries for only those that will yield2083# distributions2084filtered = (2085entry2086for entry in entries2087if dist_factory(path_item, entry, only)2088)20892090# scan for .egg and .egg-info in directory2091path_item_entries = _by_version_descending(filtered)2092for entry in path_item_entries:2093fullpath = os.path.join(path_item, entry)2094factory = dist_factory(path_item, entry, only)2095for dist in factory(fullpath):2096yield dist209720982099def dist_factory(path_item, entry, only):2100"""Return a dist_factory for the given entry."""2101lower = entry.lower()2102is_egg_info = lower.endswith('.egg-info')2103is_dist_info = (2104lower.endswith('.dist-info') and2105os.path.isdir(os.path.join(path_item, entry))2106)2107is_meta = is_egg_info or is_dist_info2108return (2109distributions_from_metadata2110if is_meta else2111find_distributions2112if not only and _is_egg_path(entry) else2113resolve_egg_link2114if not only and lower.endswith('.egg-link') else2115NoDists()2116)211721182119class NoDists:2120"""2121>>> bool(NoDists())2122False21232124>>> list(NoDists()('anything'))2125[]2126"""2127def __bool__(self):2128return False21292130def __call__(self, fullpath):2131return iter(())213221332134def safe_listdir(path):2135"""2136Attempt to list contents of path, but suppress some exceptions.2137"""2138try:2139return os.listdir(path)2140except (PermissionError, NotADirectoryError):2141pass2142except OSError as e:2143# Ignore the directory if does not exist, not a directory or2144# permission denied2145if e.errno not in (errno.ENOTDIR, errno.EACCES, errno.ENOENT):2146raise2147return ()214821492150def distributions_from_metadata(path):2151root = os.path.dirname(path)2152if os.path.isdir(path):2153if len(os.listdir(path)) == 0:2154# empty metadata dir; skip2155return2156metadata = PathMetadata(root, path)2157else:2158metadata = FileMetadata(path)2159entry = os.path.basename(path)2160yield Distribution.from_location(2161root, entry, metadata, precedence=DEVELOP_DIST,2162)216321642165def non_empty_lines(path):2166"""2167Yield non-empty lines from file at path2168"""2169with open(path) as f:2170for line in f:2171line = line.strip()2172if line:2173yield line217421752176def resolve_egg_link(path):2177"""2178Given a path to an .egg-link, resolve distributions2179present in the referenced path.2180"""2181referenced_paths = non_empty_lines(path)2182resolved_paths = (2183os.path.join(os.path.dirname(path), ref)2184for ref in referenced_paths2185)2186dist_groups = map(find_distributions, resolved_paths)2187return next(dist_groups, ())218821892190register_finder(pkgutil.ImpImporter, find_on_path)21912192if hasattr(importlib_machinery, 'FileFinder'):2193register_finder(importlib_machinery.FileFinder, find_on_path)21942195_declare_state('dict', _namespace_handlers={})2196_declare_state('dict', _namespace_packages={})219721982199def register_namespace_handler(importer_type, namespace_handler):2200"""Register `namespace_handler` to declare namespace packages22012202`importer_type` is the type or class of a PEP 302 "Importer" (sys.path item2203handler), and `namespace_handler` is a callable like this::22042205def namespace_handler(importer, path_entry, moduleName, module):2206# return a path_entry to use for child packages22072208Namespace handlers are only called if the importer object has already2209agreed that it can handle the relevant path item, and they should only2210return a subpath if the module __path__ does not already contain an2211equivalent subpath. For an example namespace handler, see2212``pkg_resources.file_ns_handler``.2213"""2214_namespace_handlers[importer_type] = namespace_handler221522162217def _handle_ns(packageName, path_item):2218"""Ensure that named package includes a subpath of path_item (if needed)"""22192220importer = get_importer(path_item)2221if importer is None:2222return None22232224# use find_spec (PEP 451) and fall-back to find_module (PEP 302)2225try:2226spec = importer.find_spec(packageName)2227except AttributeError:2228# capture warnings due to #11112229with warnings.catch_warnings():2230warnings.simplefilter("ignore")2231loader = importer.find_module(packageName)2232else:2233loader = spec.loader if spec else None22342235if loader is None:2236return None2237module = sys.modules.get(packageName)2238if module is None:2239module = sys.modules[packageName] = types.ModuleType(packageName)2240module.__path__ = []2241_set_parent_ns(packageName)2242elif not hasattr(module, '__path__'):2243raise TypeError("Not a package:", packageName)2244handler = _find_adapter(_namespace_handlers, importer)2245subpath = handler(importer, path_item, packageName, module)2246if subpath is not None:2247path = module.__path__2248path.append(subpath)2249importlib.import_module(packageName)2250_rebuild_mod_path(path, packageName, module)2251return subpath225222532254def _rebuild_mod_path(orig_path, package_name, module):2255"""2256Rebuild module.__path__ ensuring that all entries are ordered2257corresponding to their sys.path order2258"""2259sys_path = [_normalize_cached(p) for p in sys.path]22602261def safe_sys_path_index(entry):2262"""2263Workaround for #520 and #513.2264"""2265try:2266return sys_path.index(entry)2267except ValueError:2268return float('inf')22692270def position_in_sys_path(path):2271"""2272Return the ordinal of the path based on its position in sys.path2273"""2274path_parts = path.split(os.sep)2275module_parts = package_name.count('.') + 12276parts = path_parts[:-module_parts]2277return safe_sys_path_index(_normalize_cached(os.sep.join(parts)))22782279new_path = sorted(orig_path, key=position_in_sys_path)2280new_path = [_normalize_cached(p) for p in new_path]22812282if isinstance(module.__path__, list):2283module.__path__[:] = new_path2284else:2285module.__path__ = new_path228622872288def declare_namespace(packageName):2289"""Declare that package 'packageName' is a namespace package"""22902291_imp.acquire_lock()2292try:2293if packageName in _namespace_packages:2294return22952296path = sys.path2297parent, _, _ = packageName.rpartition('.')22982299if parent:2300declare_namespace(parent)2301if parent not in _namespace_packages:2302__import__(parent)2303try:2304path = sys.modules[parent].__path__2305except AttributeError as e:2306raise TypeError("Not a package:", parent) from e23072308# Track what packages are namespaces, so when new path items are added,2309# they can be updated2310_namespace_packages.setdefault(parent or None, []).append(packageName)2311_namespace_packages.setdefault(packageName, [])23122313for path_item in path:2314# Ensure all the parent's path items are reflected in the child,2315# if they apply2316_handle_ns(packageName, path_item)23172318finally:2319_imp.release_lock()232023212322def fixup_namespace_packages(path_item, parent=None):2323"""Ensure that previously-declared namespace packages include path_item"""2324_imp.acquire_lock()2325try:2326for package in _namespace_packages.get(parent, ()):2327subpath = _handle_ns(package, path_item)2328if subpath:2329fixup_namespace_packages(subpath, package)2330finally:2331_imp.release_lock()233223332334def file_ns_handler(importer, path_item, packageName, module):2335"""Compute an ns-package subpath for a filesystem or zipfile importer"""23362337subpath = os.path.join(path_item, packageName.split('.')[-1])2338normalized = _normalize_cached(subpath)2339for item in module.__path__:2340if _normalize_cached(item) == normalized:2341break2342else:2343# Only return the path if it's not already there2344return subpath234523462347register_namespace_handler(pkgutil.ImpImporter, file_ns_handler)2348register_namespace_handler(zipimport.zipimporter, file_ns_handler)23492350if hasattr(importlib_machinery, 'FileFinder'):2351register_namespace_handler(importlib_machinery.FileFinder, file_ns_handler)235223532354def null_ns_handler(importer, path_item, packageName, module):2355return None235623572358register_namespace_handler(object, null_ns_handler)235923602361def normalize_path(filename):2362"""Normalize a file/dir name for comparison purposes"""2363return os.path.normcase(os.path.realpath(os.path.normpath(2364_cygwin_patch(filename))))236523662367def _cygwin_patch(filename): # pragma: nocover2368"""2369Contrary to POSIX 2008, on Cygwin, getcwd (3) contains2370symlink components. Using2371os.path.abspath() works around this limitation. A fix in os.getcwd()2372would probably better, in Cygwin even more so, except2373that this seems to be by design...2374"""2375return os.path.abspath(filename) if sys.platform == 'cygwin' else filename237623772378def _normalize_cached(filename, _cache={}):2379try:2380return _cache[filename]2381except KeyError:2382_cache[filename] = result = normalize_path(filename)2383return result238423852386def _is_egg_path(path):2387"""2388Determine if given path appears to be an egg.2389"""2390return _is_zip_egg(path) or _is_unpacked_egg(path)239123922393def _is_zip_egg(path):2394return (2395path.lower().endswith('.egg') and2396os.path.isfile(path) and2397zipfile.is_zipfile(path)2398)239924002401def _is_unpacked_egg(path):2402"""2403Determine if given path appears to be an unpacked egg.2404"""2405return (2406path.lower().endswith('.egg') and2407os.path.isfile(os.path.join(path, 'EGG-INFO', 'PKG-INFO'))2408)240924102411def _set_parent_ns(packageName):2412parts = packageName.split('.')2413name = parts.pop()2414if parts:2415parent = '.'.join(parts)2416setattr(sys.modules[parent], name, sys.modules[packageName])241724182419MODULE = re.compile(r"\w+(\.\w+)*$").match2420EGG_NAME = re.compile(2421r"""2422(?P<name>[^-]+) (2423-(?P<ver>[^-]+) (2424-py(?P<pyver>[^-]+) (2425-(?P<plat>.+)2426)?2427)?2428)?2429""",2430re.VERBOSE | re.IGNORECASE,2431).match243224332434class EntryPoint:2435"""Object representing an advertised importable object"""24362437def __init__(self, name, module_name, attrs=(), extras=(), dist=None):2438if not MODULE(module_name):2439raise ValueError("Invalid module name", module_name)2440self.name = name2441self.module_name = module_name2442self.attrs = tuple(attrs)2443self.extras = tuple(extras)2444self.dist = dist24452446def __str__(self):2447s = "%s = %s" % (self.name, self.module_name)2448if self.attrs:2449s += ':' + '.'.join(self.attrs)2450if self.extras:2451s += ' [%s]' % ','.join(self.extras)2452return s24532454def __repr__(self):2455return "EntryPoint.parse(%r)" % str(self)24562457def load(self, require=True, *args, **kwargs):2458"""2459Require packages for this EntryPoint, then resolve it.2460"""2461if not require or args or kwargs:2462warnings.warn(2463"Parameters to load are deprecated. Call .resolve and "2464".require separately.",2465PkgResourcesDeprecationWarning,2466stacklevel=2,2467)2468if require:2469self.require(*args, **kwargs)2470return self.resolve()24712472def resolve(self):2473"""2474Resolve the entry point from its module and attrs.2475"""2476module = __import__(self.module_name, fromlist=['__name__'], level=0)2477try:2478return functools.reduce(getattr, self.attrs, module)2479except AttributeError as exc:2480raise ImportError(str(exc)) from exc24812482def require(self, env=None, installer=None):2483if self.extras and not self.dist:2484raise UnknownExtra("Can't require() without a distribution", self)24852486# Get the requirements for this entry point with all its extras and2487# then resolve them. We have to pass `extras` along when resolving so2488# that the working set knows what extras we want. Otherwise, for2489# dist-info distributions, the working set will assume that the2490# requirements for that extra are purely optional and skip over them.2491reqs = self.dist.requires(self.extras)2492items = working_set.resolve(reqs, env, installer, extras=self.extras)2493list(map(working_set.add, items))24942495pattern = re.compile(2496r'\s*'2497r'(?P<name>.+?)\s*'2498r'=\s*'2499r'(?P<module>[\w.]+)\s*'2500r'(:\s*(?P<attr>[\w.]+))?\s*'2501r'(?P<extras>\[.*\])?\s*$'2502)25032504@classmethod2505def parse(cls, src, dist=None):2506"""Parse a single entry point from string `src`25072508Entry point syntax follows the form::25092510name = some.module:some.attr [extra1, extra2]25112512The entry name and module name are required, but the ``:attrs`` and2513``[extras]`` parts are optional2514"""2515m = cls.pattern.match(src)2516if not m:2517msg = "EntryPoint must be in 'name=module:attrs [extras]' format"2518raise ValueError(msg, src)2519res = m.groupdict()2520extras = cls._parse_extras(res['extras'])2521attrs = res['attr'].split('.') if res['attr'] else ()2522return cls(res['name'], res['module'], attrs, extras, dist)25232524@classmethod2525def _parse_extras(cls, extras_spec):2526if not extras_spec:2527return ()2528req = Requirement.parse('x' + extras_spec)2529if req.specs:2530raise ValueError()2531return req.extras25322533@classmethod2534def parse_group(cls, group, lines, dist=None):2535"""Parse an entry point group"""2536if not MODULE(group):2537raise ValueError("Invalid group name", group)2538this = {}2539for line in yield_lines(lines):2540ep = cls.parse(line, dist)2541if ep.name in this:2542raise ValueError("Duplicate entry point", group, ep.name)2543this[ep.name] = ep2544return this25452546@classmethod2547def parse_map(cls, data, dist=None):2548"""Parse a map of entry point groups"""2549if isinstance(data, dict):2550data = data.items()2551else:2552data = split_sections(data)2553maps = {}2554for group, lines in data:2555if group is None:2556if not lines:2557continue2558raise ValueError("Entry points must be listed in groups")2559group = group.strip()2560if group in maps:2561raise ValueError("Duplicate group name", group)2562maps[group] = cls.parse_group(group, lines, dist)2563return maps256425652566def _version_from_file(lines):2567"""2568Given an iterable of lines from a Metadata file, return2569the value of the Version field, if present, or None otherwise.2570"""2571def is_version_line(line):2572return line.lower().startswith('version:')2573version_lines = filter(is_version_line, lines)2574line = next(iter(version_lines), '')2575_, _, value = line.partition(':')2576return safe_version(value.strip()) or None257725782579class Distribution:2580"""Wrap an actual or potential sys.path entry w/metadata"""2581PKG_INFO = 'PKG-INFO'25822583def __init__(2584self, location=None, metadata=None, project_name=None,2585version=None, py_version=PY_MAJOR, platform=None,2586precedence=EGG_DIST):2587self.project_name = safe_name(project_name or 'Unknown')2588if version is not None:2589self._version = safe_version(version)2590self.py_version = py_version2591self.platform = platform2592self.location = location2593self.precedence = precedence2594self._provider = metadata or empty_provider25952596@classmethod2597def from_location(cls, location, basename, metadata=None, **kw):2598project_name, version, py_version, platform = [None] * 42599basename, ext = os.path.splitext(basename)2600if ext.lower() in _distributionImpl:2601cls = _distributionImpl[ext.lower()]26022603match = EGG_NAME(basename)2604if match:2605project_name, version, py_version, platform = match.group(2606'name', 'ver', 'pyver', 'plat'2607)2608return cls(2609location, metadata, project_name=project_name, version=version,2610py_version=py_version, platform=platform, **kw2611)._reload_version()26122613def _reload_version(self):2614return self26152616@property2617def hashcmp(self):2618return (2619self.parsed_version,2620self.precedence,2621self.key,2622self.location,2623self.py_version or '',2624self.platform or '',2625)26262627def __hash__(self):2628return hash(self.hashcmp)26292630def __lt__(self, other):2631return self.hashcmp < other.hashcmp26322633def __le__(self, other):2634return self.hashcmp <= other.hashcmp26352636def __gt__(self, other):2637return self.hashcmp > other.hashcmp26382639def __ge__(self, other):2640return self.hashcmp >= other.hashcmp26412642def __eq__(self, other):2643if not isinstance(other, self.__class__):2644# It's not a Distribution, so they are not equal2645return False2646return self.hashcmp == other.hashcmp26472648def __ne__(self, other):2649return not self == other26502651# These properties have to be lazy so that we don't have to load any2652# metadata until/unless it's actually needed. (i.e., some distributions2653# may not know their name or version without loading PKG-INFO)26542655@property2656def key(self):2657try:2658return self._key2659except AttributeError:2660self._key = key = self.project_name.lower()2661return key26622663@property2664def parsed_version(self):2665if not hasattr(self, "_parsed_version"):2666self._parsed_version = parse_version(self.version)26672668return self._parsed_version26692670def _warn_legacy_version(self):2671LV = packaging.version.LegacyVersion2672is_legacy = isinstance(self._parsed_version, LV)2673if not is_legacy:2674return26752676# While an empty version is technically a legacy version and2677# is not a valid PEP 440 version, it's also unlikely to2678# actually come from someone and instead it is more likely that2679# it comes from setuptools attempting to parse a filename and2680# including it in the list. So for that we'll gate this warning2681# on if the version is anything at all or not.2682if not self.version:2683return26842685tmpl = textwrap.dedent("""2686'{project_name} ({version})' is being parsed as a legacy,2687non PEP 440,2688version. You may find odd behavior and sort order.2689In particular it will be sorted as less than 0.0. It2690is recommended to migrate to PEP 440 compatible2691versions.2692""").strip().replace('\n', ' ')26932694warnings.warn(tmpl.format(**vars(self)), PEP440Warning)26952696@property2697def version(self):2698try:2699return self._version2700except AttributeError as e:2701version = self._get_version()2702if version is None:2703path = self._get_metadata_path_for_display(self.PKG_INFO)2704msg = (2705"Missing 'Version:' header and/or {} file at path: {}"2706).format(self.PKG_INFO, path)2707raise ValueError(msg, self) from e27082709return version27102711@property2712def _dep_map(self):2713"""2714A map of extra to its list of (direct) requirements2715for this distribution, including the null extra.2716"""2717try:2718return self.__dep_map2719except AttributeError:2720self.__dep_map = self._filter_extras(self._build_dep_map())2721return self.__dep_map27222723@staticmethod2724def _filter_extras(dm):2725"""2726Given a mapping of extras to dependencies, strip off2727environment markers and filter out any dependencies2728not matching the markers.2729"""2730for extra in list(filter(None, dm)):2731new_extra = extra2732reqs = dm.pop(extra)2733new_extra, _, marker = extra.partition(':')2734fails_marker = marker and (2735invalid_marker(marker)2736or not evaluate_marker(marker)2737)2738if fails_marker:2739reqs = []2740new_extra = safe_extra(new_extra) or None27412742dm.setdefault(new_extra, []).extend(reqs)2743return dm27442745def _build_dep_map(self):2746dm = {}2747for name in 'requires.txt', 'depends.txt':2748for extra, reqs in split_sections(self._get_metadata(name)):2749dm.setdefault(extra, []).extend(parse_requirements(reqs))2750return dm27512752def requires(self, extras=()):2753"""List of Requirements needed for this distro if `extras` are used"""2754dm = self._dep_map2755deps = []2756deps.extend(dm.get(None, ()))2757for ext in extras:2758try:2759deps.extend(dm[safe_extra(ext)])2760except KeyError as e:2761raise UnknownExtra(2762"%s has no such extra feature %r" % (self, ext)2763) from e2764return deps27652766def _get_metadata_path_for_display(self, name):2767"""2768Return the path to the given metadata file, if available.2769"""2770try:2771# We need to access _get_metadata_path() on the provider object2772# directly rather than through this class's __getattr__()2773# since _get_metadata_path() is marked private.2774path = self._provider._get_metadata_path(name)27752776# Handle exceptions e.g. in case the distribution's metadata2777# provider doesn't support _get_metadata_path().2778except Exception:2779return '[could not detect]'27802781return path27822783def _get_metadata(self, name):2784if self.has_metadata(name):2785for line in self.get_metadata_lines(name):2786yield line27872788def _get_version(self):2789lines = self._get_metadata(self.PKG_INFO)2790version = _version_from_file(lines)27912792return version27932794def activate(self, path=None, replace=False):2795"""Ensure distribution is importable on `path` (default=sys.path)"""2796if path is None:2797path = sys.path2798self.insert_on(path, replace=replace)2799if path is sys.path:2800fixup_namespace_packages(self.location)2801for pkg in self._get_metadata('namespace_packages.txt'):2802if pkg in sys.modules:2803declare_namespace(pkg)28042805def egg_name(self):2806"""Return what this distribution's standard .egg filename should be"""2807filename = "%s-%s-py%s" % (2808to_filename(self.project_name), to_filename(self.version),2809self.py_version or PY_MAJOR2810)28112812if self.platform:2813filename += '-' + self.platform2814return filename28152816def __repr__(self):2817if self.location:2818return "%s (%s)" % (self, self.location)2819else:2820return str(self)28212822def __str__(self):2823try:2824version = getattr(self, 'version', None)2825except ValueError:2826version = None2827version = version or "[unknown version]"2828return "%s %s" % (self.project_name, version)28292830def __getattr__(self, attr):2831"""Delegate all unrecognized public attributes to .metadata provider"""2832if attr.startswith('_'):2833raise AttributeError(attr)2834return getattr(self._provider, attr)28352836def __dir__(self):2837return list(2838set(super(Distribution, self).__dir__())2839| set(2840attr for attr in self._provider.__dir__()2841if not attr.startswith('_')2842)2843)28442845@classmethod2846def from_filename(cls, filename, metadata=None, **kw):2847return cls.from_location(2848_normalize_cached(filename), os.path.basename(filename), metadata,2849**kw2850)28512852def as_requirement(self):2853"""Return a ``Requirement`` that matches this distribution exactly"""2854if isinstance(self.parsed_version, packaging.version.Version):2855spec = "%s==%s" % (self.project_name, self.parsed_version)2856else:2857spec = "%s===%s" % (self.project_name, self.parsed_version)28582859return Requirement.parse(spec)28602861def load_entry_point(self, group, name):2862"""Return the `name` entry point of `group` or raise ImportError"""2863ep = self.get_entry_info(group, name)2864if ep is None:2865raise ImportError("Entry point %r not found" % ((group, name),))2866return ep.load()28672868def get_entry_map(self, group=None):2869"""Return the entry point map for `group`, or the full entry map"""2870try:2871ep_map = self._ep_map2872except AttributeError:2873ep_map = self._ep_map = EntryPoint.parse_map(2874self._get_metadata('entry_points.txt'), self2875)2876if group is not None:2877return ep_map.get(group, {})2878return ep_map28792880def get_entry_info(self, group, name):2881"""Return the EntryPoint object for `group`+`name`, or ``None``"""2882return self.get_entry_map(group).get(name)28832884# FIXME: 'Distribution.insert_on' is too complex (13)2885def insert_on(self, path, loc=None, replace=False): # noqa: C9012886"""Ensure self.location is on path28872888If replace=False (default):2889- If location is already in path anywhere, do nothing.2890- Else:2891- If it's an egg and its parent directory is on path,2892insert just ahead of the parent.2893- Else: add to the end of path.2894If replace=True:2895- If location is already on path anywhere (not eggs)2896or higher priority than its parent (eggs)2897do nothing.2898- Else:2899- If it's an egg and its parent directory is on path,2900insert just ahead of the parent,2901removing any lower-priority entries.2902- Else: add it to the front of path.2903"""29042905loc = loc or self.location2906if not loc:2907return29082909nloc = _normalize_cached(loc)2910bdir = os.path.dirname(nloc)2911npath = [(p and _normalize_cached(p) or p) for p in path]29122913for p, item in enumerate(npath):2914if item == nloc:2915if replace:2916break2917else:2918# don't modify path (even removing duplicates) if2919# found and not replace2920return2921elif item == bdir and self.precedence == EGG_DIST:2922# if it's an .egg, give it precedence over its directory2923# UNLESS it's already been added to sys.path and replace=False2924if (not replace) and nloc in npath[p:]:2925return2926if path is sys.path:2927self.check_version_conflict()2928path.insert(p, loc)2929npath.insert(p, nloc)2930break2931else:2932if path is sys.path:2933self.check_version_conflict()2934if replace:2935path.insert(0, loc)2936else:2937path.append(loc)2938return29392940# p is the spot where we found or inserted loc; now remove duplicates2941while True:2942try:2943np = npath.index(nloc, p + 1)2944except ValueError:2945break2946else:2947del npath[np], path[np]2948# ha!2949p = np29502951return29522953def check_version_conflict(self):2954if self.key == 'setuptools':2955# ignore the inevitable setuptools self-conflicts :(2956return29572958nsp = dict.fromkeys(self._get_metadata('namespace_packages.txt'))2959loc = normalize_path(self.location)2960for modname in self._get_metadata('top_level.txt'):2961if (modname not in sys.modules or modname in nsp2962or modname in _namespace_packages):2963continue2964if modname in ('pkg_resources', 'setuptools', 'site'):2965continue2966fn = getattr(sys.modules[modname], '__file__', None)2967if fn and (normalize_path(fn).startswith(loc) or2968fn.startswith(self.location)):2969continue2970issue_warning(2971"Module %s was already imported from %s, but %s is being added"2972" to sys.path" % (modname, fn, self.location),2973)29742975def has_version(self):2976try:2977self.version2978except ValueError:2979issue_warning("Unbuilt egg for " + repr(self))2980return False2981return True29822983def clone(self, **kw):2984"""Copy this distribution, substituting in any changed keyword args"""2985names = 'project_name version py_version platform location precedence'2986for attr in names.split():2987kw.setdefault(attr, getattr(self, attr, None))2988kw.setdefault('metadata', self._provider)2989return self.__class__(**kw)29902991@property2992def extras(self):2993return [dep for dep in self._dep_map if dep]299429952996class EggInfoDistribution(Distribution):2997def _reload_version(self):2998"""2999Packages installed by distutils (e.g. numpy or scipy),3000which uses an old safe_version, and so3001their version numbers can get mangled when3002converted to filenames (e.g., 1.11.0.dev0+2329eae to30031.11.0.dev0_2329eae). These distributions will not be3004parsed properly3005downstream by Distribution and safe_version, so3006take an extra step and try to get the version number from3007the metadata file itself instead of the filename.3008"""3009md_version = self._get_version()3010if md_version:3011self._version = md_version3012return self301330143015class DistInfoDistribution(Distribution):3016"""3017Wrap an actual or potential sys.path entry3018w/metadata, .dist-info style.3019"""3020PKG_INFO = 'METADATA'3021EQEQ = re.compile(r"([\(,])\s*(\d.*?)\s*([,\)])")30223023@property3024def _parsed_pkg_info(self):3025"""Parse and cache metadata"""3026try:3027return self._pkg_info3028except AttributeError:3029metadata = self.get_metadata(self.PKG_INFO)3030self._pkg_info = email.parser.Parser().parsestr(metadata)3031return self._pkg_info30323033@property3034def _dep_map(self):3035try:3036return self.__dep_map3037except AttributeError:3038self.__dep_map = self._compute_dependencies()3039return self.__dep_map30403041def _compute_dependencies(self):3042"""Recompute this distribution's dependencies."""3043dm = self.__dep_map = {None: []}30443045reqs = []3046# Including any condition expressions3047for req in self._parsed_pkg_info.get_all('Requires-Dist') or []:3048reqs.extend(parse_requirements(req))30493050def reqs_for_extra(extra):3051for req in reqs:3052if not req.marker or req.marker.evaluate({'extra': extra}):3053yield req30543055common = types.MappingProxyType(dict.fromkeys(reqs_for_extra(None)))3056dm[None].extend(common)30573058for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []:3059s_extra = safe_extra(extra.strip())3060dm[s_extra] = [r for r in reqs_for_extra(extra) if r not in common]30613062return dm306330643065_distributionImpl = {3066'.egg': Distribution,3067'.egg-info': EggInfoDistribution,3068'.dist-info': DistInfoDistribution,3069}307030713072def issue_warning(*args, **kw):3073level = 13074g = globals()3075try:3076# find the first stack frame that is *not* code in3077# the pkg_resources module, to use for the warning3078while sys._getframe(level).f_globals is g:3079level += 13080except ValueError:3081pass3082warnings.warn(stacklevel=level + 1, *args, **kw)308330843085def parse_requirements(strs):3086"""3087Yield ``Requirement`` objects for each specification in `strs`.30883089`strs` must be a string, or a (possibly-nested) iterable thereof.3090"""3091return map(Requirement, join_continuation(map(drop_comment, yield_lines(strs))))309230933094class RequirementParseError(packaging.requirements.InvalidRequirement):3095"Compatibility wrapper for InvalidRequirement"309630973098class Requirement(packaging.requirements.Requirement):3099def __init__(self, requirement_string):3100"""DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!"""3101super(Requirement, self).__init__(requirement_string)3102self.unsafe_name = self.name3103project_name = safe_name(self.name)3104self.project_name, self.key = project_name, project_name.lower()3105self.specs = [3106(spec.operator, spec.version) for spec in self.specifier]3107self.extras = tuple(map(safe_extra, self.extras))3108self.hashCmp = (3109self.key,3110self.url,3111self.specifier,3112frozenset(self.extras),3113str(self.marker) if self.marker else None,3114)3115self.__hash = hash(self.hashCmp)31163117def __eq__(self, other):3118return (3119isinstance(other, Requirement) and3120self.hashCmp == other.hashCmp3121)31223123def __ne__(self, other):3124return not self == other31253126def __contains__(self, item):3127if isinstance(item, Distribution):3128if item.key != self.key:3129return False31303131item = item.version31323133# Allow prereleases always in order to match the previous behavior of3134# this method. In the future this should be smarter and follow PEP 4403135# more accurately.3136return self.specifier.contains(item, prereleases=True)31373138def __hash__(self):3139return self.__hash31403141def __repr__(self):3142return "Requirement.parse(%r)" % str(self)31433144@staticmethod3145def parse(s):3146req, = parse_requirements(s)3147return req314831493150def _always_object(classes):3151"""3152Ensure object appears in the mro even3153for old-style classes.3154"""3155if object not in classes:3156return classes + (object,)3157return classes315831593160def _find_adapter(registry, ob):3161"""Return an adapter factory for `ob` from `registry`"""3162types = _always_object(inspect.getmro(getattr(ob, '__class__', type(ob))))3163for t in types:3164if t in registry:3165return registry[t]316631673168def ensure_directory(path):3169"""Ensure that the parent directory of `path` exists"""3170dirname = os.path.dirname(path)3171os.makedirs(dirname, exist_ok=True)317231733174def _bypass_ensure_directory(path):3175"""Sandbox-bypassing version of ensure_directory()"""3176if not WRITE_SUPPORT:3177raise IOError('"os.mkdir" not supported on this platform.')3178dirname, filename = split(path)3179if dirname and filename and not isdir(dirname):3180_bypass_ensure_directory(dirname)3181try:3182mkdir(dirname, 0o755)3183except FileExistsError:3184pass318531863187def split_sections(s):3188"""Split a string or iterable thereof into (section, content) pairs31893190Each ``section`` is a stripped version of the section header ("[section]")3191and each ``content`` is a list of stripped lines excluding blank lines and3192comment-only lines. If there are any such lines before the first section3193header, they're returned in a first ``section`` of ``None``.3194"""3195section = None3196content = []3197for line in yield_lines(s):3198if line.startswith("["):3199if line.endswith("]"):3200if section or content:3201yield section, content3202section = line[1:-1].strip()3203content = []3204else:3205raise ValueError("Invalid section heading", line)3206else:3207content.append(line)32083209# wrap up last segment3210yield section, content321132123213def _mkstemp(*args, **kw):3214old_open = os.open3215try:3216# temporarily bypass sandboxing3217os.open = os_open3218return tempfile.mkstemp(*args, **kw)3219finally:3220# and then put it back3221os.open = old_open322232233224# Silence the PEP440Warning by default, so that end users don't get hit by it3225# randomly just because they use pkg_resources. We want to append the rule3226# because we want earlier uses of filterwarnings to take precedence over this3227# one.3228warnings.filterwarnings("ignore", category=PEP440Warning, append=True)322932303231# from jaraco.functools 1.33232def _call_aside(f, *args, **kwargs):3233f(*args, **kwargs)3234return f323532363237@_call_aside3238def _initialize(g=globals()):3239"Set up global resource manager (deliberately not state-saved)"3240manager = ResourceManager()3241g['_manager'] = manager3242g.update(3243(name, getattr(manager, name))3244for name in dir(manager)3245if not name.startswith('_')3246)324732483249class PkgResourcesDeprecationWarning(Warning):3250"""3251Base class for warning about deprecations in ``pkg_resources``32523253This class is not derived from ``DeprecationWarning``, and as such is3254visible by default.3255"""325632573258@_call_aside3259def _initialize_master_working_set():3260"""3261Prepare the master working set and make the ``require()``3262API available.32633264This function has explicit effects on the global state3265of pkg_resources. It is intended to be invoked once at3266the initialization of this module.32673268Invocation by other packages is unsupported and done3269at their own risk.3270"""3271working_set = WorkingSet._build_master()3272_declare_state('object', working_set=working_set)32733274require = working_set.require3275iter_entry_points = working_set.iter_entry_points3276add_activation_listener = working_set.subscribe3277run_script = working_set.run_script3278# backward compatibility3279run_main = run_script3280# Activate all distributions already on sys.path with replace=False and3281# ensure that all distributions added to the working set in the future3282# (e.g. by calling ``require()``) will get activated as well,3283# with higher priority (replace=True).3284tuple(3285dist.activate(replace=False)3286for dist in working_set3287)3288add_activation_listener(3289lambda dist: dist.activate(replace=True),3290existing=False,3291)3292working_set.entries = []3293# match order3294list(map(working_set.add_entry, sys.path))3295globals().update(locals())329632973298