codekingpro/portable-devtools
114k
1"""2Package resource API3--------------------4 5A resource is a logical file contained within a package, or a logical6subdirectory thereof. The package resource API expects resource names7to have their path parts separated with ``/``, *not* whatever the local8path separator is. Do not use os.path operations to manipulate resource9names being passed into the API.10 11The package resource API is designed to work with normal filesystem packages,12.egg files, and unpacked .egg files. It can also work in a limited way with13.zip files and with custom PEP 302 loaders that support the ``get_data()``14method.15 16This module is deprecated. Users are directed to :mod:`importlib.resources`,17:mod:`importlib.metadata` and :pypi:`packaging` instead.18"""19 20import sys21 22if sys.version_info < (3, 8):23 raise RuntimeError("Python 3.8 or later is required")24 25import os26import io27import time28import re29import types30from typing import List, Protocol31import zipfile32import zipimport33import warnings34import stat35import functools36import pkgutil37import operator38import platform39import collections40import plistlib41import email.parser42import errno43import tempfile44import textwrap45import inspect46import ntpath47import posixpath48import importlib49import importlib.machinery50from pkgutil import get_importer51 52import _imp53 54# capture these to bypass sandboxing55from os import utime56 57try:58 from os import mkdir, rename, unlink59 60 WRITE_SUPPORT = True61except ImportError:62 # no write support, probably under GAE63 WRITE_SUPPORT = False64 65from os import open as os_open66from os.path import isdir, split67 68from pkg_resources.extern.jaraco.text import (69 yield_lines,70 drop_comment,71 join_continuation,72)73 74from pkg_resources.extern import platformdirs75from pkg_resources.extern import packaging76 77__import__('pkg_resources.extern.packaging.version')78__import__('pkg_resources.extern.packaging.specifiers')79__import__('pkg_resources.extern.packaging.requirements')80__import__('pkg_resources.extern.packaging.markers')81__import__('pkg_resources.extern.packaging.utils')82 83# declare some globals that will be defined later to84# satisfy the linters.85require = None86working_set = None87add_activation_listener = None88cleanup_resources = None89resource_stream = None90set_extraction_path = None91resource_isdir = None92resource_string = None93iter_entry_points = None94resource_listdir = None95resource_filename = None96resource_exists = None97_distribution_finders = None98_namespace_handlers = None99_namespace_packages = None100 101 102warnings.warn(103 "pkg_resources is deprecated as an API. "104 "See https://setuptools.pypa.io/en/latest/pkg_resources.html",105 DeprecationWarning,106 stacklevel=2,107)108 109 110_PEP440_FALLBACK = re.compile(r"^v?(?P<safe>(?:[0-9]+!)?[0-9]+(?:\.[0-9]+)*)", re.I)111 112 113class PEP440Warning(RuntimeWarning):114 """115 Used when there is an issue with a version or specifier not complying with116 PEP 440.117 """118 119 120parse_version = packaging.version.Version121 122 123_state_vars = {}124 125 126def _declare_state(vartype, **kw):127 globals().update(kw)128 _state_vars.update(dict.fromkeys(kw, vartype))129 130 131def __getstate__():132 state = {}133 g = globals()134 for k, v in _state_vars.items():135 state[k] = g['_sget_' + v](g[k])136 return state137 138 139def __setstate__(state):140 g = globals()141 for k, v in state.items():142 g['_sset_' + _state_vars[k]](k, g[k], v)143 return state144 145 146def _sget_dict(val):147 return val.copy()148 149 150def _sset_dict(key, ob, state):151 ob.clear()152 ob.update(state)153 154 155def _sget_object(val):156 return val.__getstate__()157 158 159def _sset_object(key, ob, state):160 ob.__setstate__(state)161 162 163_sget_none = _sset_none = lambda *args: None164 165 166def get_supported_platform():167 """Return this platform's maximum compatible version.168 169 distutils.util.get_platform() normally reports the minimum version170 of macOS that would be required to *use* extensions produced by171 distutils. But what we want when checking compatibility is to know the172 version of macOS that we are *running*. To allow usage of packages that173 explicitly require a newer version of macOS, we must also know the174 current version of the OS.175 176 If this condition occurs for any other platform with a version in its177 platform strings, this function should be extended accordingly.178 """179 plat = get_build_platform()180 m = macosVersionString.match(plat)181 if m is not None and sys.platform == "darwin":182 try:183 plat = 'macosx-%s-%s' % ('.'.join(_macos_vers()[:2]), m.group(3))184 except ValueError:185 # not macOS186 pass187 return plat188 189 190__all__ = [191 # Basic resource access and distribution/entry point discovery192 'require',193 'run_script',194 'get_provider',195 'get_distribution',196 'load_entry_point',197 'get_entry_map',198 'get_entry_info',199 'iter_entry_points',200 'resource_string',201 'resource_stream',202 'resource_filename',203 'resource_listdir',204 'resource_exists',205 'resource_isdir',206 # Environmental control207 'declare_namespace',208 'working_set',209 'add_activation_listener',210 'find_distributions',211 'set_extraction_path',212 'cleanup_resources',213 'get_default_cache',214 # Primary implementation classes215 'Environment',216 'WorkingSet',217 'ResourceManager',218 'Distribution',219 'Requirement',220 'EntryPoint',221 # Exceptions222 'ResolutionError',223 'VersionConflict',224 'DistributionNotFound',225 'UnknownExtra',226 'ExtractionError',227 # Warnings228 'PEP440Warning',229 # Parsing functions and string utilities230 'parse_requirements',231 'parse_version',232 'safe_name',233 'safe_version',234 'get_platform',235 'compatible_platforms',236 'yield_lines',237 'split_sections',238 'safe_extra',239 'to_filename',240 'invalid_marker',241 'evaluate_marker',242 # filesystem utilities243 'ensure_directory',244 'normalize_path',245 # Distribution "precedence" constants246 'EGG_DIST',247 'BINARY_DIST',248 'SOURCE_DIST',249 'CHECKOUT_DIST',250 'DEVELOP_DIST',251 # "Provider" interfaces, implementations, and registration/lookup APIs252 'IMetadataProvider',253 'IResourceProvider',254 'FileMetadata',255 'PathMetadata',256 'EggMetadata',257 'EmptyProvider',258 'empty_provider',259 'NullProvider',260 'EggProvider',261 'DefaultProvider',262 'ZipProvider',263 'register_finder',264 'register_namespace_handler',265 'register_loader_type',266 'fixup_namespace_packages',267 'get_importer',268 # Warnings269 'PkgResourcesDeprecationWarning',270 # Deprecated/backward compatibility only271 'run_main',272 'AvailableDistributions',273]274 275 276class ResolutionError(Exception):277 """Abstract base for dependency resolution errors"""278 279 def __repr__(self):280 return self.__class__.__name__ + repr(self.args)281 282 283class VersionConflict(ResolutionError):284 """285 An already-installed version conflicts with the requested version.286 287 Should be initialized with the installed Distribution and the requested288 Requirement.289 """290 291 _template = "{self.dist} is installed but {self.req} is required"292 293 @property294 def dist(self):295 return self.args[0]296 297 @property298 def req(self):299 return self.args[1]300 301 def report(self):302 return self._template.format(**locals())303 304 def with_context(self, required_by):305 """306 If required_by is non-empty, return a version of self that is a307 ContextualVersionConflict.308 """309 if not required_by:310 return self311 args = self.args + (required_by,)312 return ContextualVersionConflict(*args)313 314 315class ContextualVersionConflict(VersionConflict):316 """317 A VersionConflict that accepts a third parameter, the set of the318 requirements that required the installed Distribution.319 """320 321 _template = VersionConflict._template + ' by {self.required_by}'322 323 @property324 def required_by(self):325 return self.args[2]326 327 328class DistributionNotFound(ResolutionError):329 """A requested distribution was not found"""330 331 _template = (332 "The '{self.req}' distribution was not found "333 "and is required by {self.requirers_str}"334 )335 336 @property337 def req(self):338 return self.args[0]339 340 @property341 def requirers(self):342 return self.args[1]343 344 @property345 def requirers_str(self):346 if not self.requirers:347 return 'the application'348 return ', '.join(self.requirers)349 350 def report(self):351 return self._template.format(**locals())352 353 def __str__(self):354 return self.report()355 356 357class UnknownExtra(ResolutionError):358 """Distribution doesn't have an "extra feature" of the given name"""359 360 361_provider_factories = {}362 363PY_MAJOR = '{}.{}'.format(*sys.version_info)364EGG_DIST = 3365BINARY_DIST = 2366SOURCE_DIST = 1367CHECKOUT_DIST = 0368DEVELOP_DIST = -1369 370 371def register_loader_type(loader_type, provider_factory):372 """Register `provider_factory` to make providers for `loader_type`373 374 `loader_type` is the type or class of a PEP 302 ``module.__loader__``,375 and `provider_factory` is a function that, passed a *module* object,376 returns an ``IResourceProvider`` for that module.377 """378 _provider_factories[loader_type] = provider_factory379 380 381def get_provider(moduleOrReq):382 """Return an IResourceProvider for the named module or requirement"""383 if isinstance(moduleOrReq, Requirement):384 return working_set.find(moduleOrReq) or require(str(moduleOrReq))[0]385 try:386 module = sys.modules[moduleOrReq]387 except KeyError:388 __import__(moduleOrReq)389 module = sys.modules[moduleOrReq]390 loader = getattr(module, '__loader__', None)391 return _find_adapter(_provider_factories, loader)(module)392 393 394@functools.lru_cache(maxsize=None)395def _macos_vers():396 version = platform.mac_ver()[0]397 # fallback for MacPorts398 if version == '':399 plist = '/System/Library/CoreServices/SystemVersion.plist'400 if os.path.exists(plist):401 with open(plist, 'rb') as fh:402 plist_content = plistlib.load(fh)403 if 'ProductVersion' in plist_content:404 version = plist_content['ProductVersion']405 return version.split('.')406 407 408def _macos_arch(machine):409 return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine)410 411 412def get_build_platform():413 """Return this platform's string for platform-specific distributions414 415 XXX Currently this is the same as ``distutils.util.get_platform()``, but it416 needs some hacks for Linux and macOS.417 """418 from sysconfig import get_platform419 420 plat = get_platform()421 if sys.platform == "darwin" and not plat.startswith('macosx-'):422 try:423 version = _macos_vers()424 machine = os.uname()[4].replace(" ", "_")425 return "macosx-%d.%d-%s" % (426 int(version[0]),427 int(version[1]),428 _macos_arch(machine),429 )430 except ValueError:431 # if someone is running a non-Mac darwin system, this will fall432 # through to the default implementation433 pass434 return plat435 436 437macosVersionString = re.compile(r"macosx-(\d+)\.(\d+)-(.*)")438darwinVersionString = re.compile(r"darwin-(\d+)\.(\d+)\.(\d+)-(.*)")439# XXX backward compat440get_platform = get_build_platform441 442 443def compatible_platforms(provided, required):444 """Can code for the `provided` platform run on the `required` platform?445 446 Returns true if either platform is ``None``, or the platforms are equal.447 448 XXX Needs compatibility checks for Linux and other unixy OSes.449 """450 if provided is None or required is None or provided == required:451 # easy case452 return True453 454 # macOS special cases455 reqMac = macosVersionString.match(required)456 if reqMac:457 provMac = macosVersionString.match(provided)458 459 # is this a Mac package?460 if not provMac:461 # this is backwards compatibility for packages built before462 # setuptools 0.6. All packages built after this point will463 # use the new macOS designation.464 provDarwin = darwinVersionString.match(provided)465 if provDarwin:466 dversion = int(provDarwin.group(1))467 macosversion = "%s.%s" % (reqMac.group(1), reqMac.group(2))468 if (469 dversion == 7470 and macosversion >= "10.3"471 or dversion == 8472 and macosversion >= "10.4"473 ):474 return True475 # egg isn't macOS or legacy darwin476 return False477 478 # are they the same major version and machine type?479 if provMac.group(1) != reqMac.group(1) or provMac.group(3) != reqMac.group(3):480 return False481 482 # is the required OS major update >= the provided one?483 if int(provMac.group(2)) > int(reqMac.group(2)):484 return False485 486 return True487 488 # XXX Linux and other platforms' special cases should go here489 return False490 491 492def get_distribution(dist):493 """Return a current distribution object for a Requirement or string"""494 if isinstance(dist, str):495 dist = Requirement.parse(dist)496 if isinstance(dist, Requirement):497 dist = get_provider(dist)498 if not isinstance(dist, Distribution):499 raise TypeError("Expected string, Requirement, or Distribution", dist)500 return dist501 502 503def load_entry_point(dist, group, name):504 """Return `name` entry point of `group` for `dist` or raise ImportError"""505 return get_distribution(dist).load_entry_point(group, name)506 507 508def get_entry_map(dist, group=None):509 """Return the entry point map for `group`, or the full entry map"""510 return get_distribution(dist).get_entry_map(group)511 512 513def get_entry_info(dist, group, name):514 """Return the EntryPoint object for `group`+`name`, or ``None``"""515 return get_distribution(dist).get_entry_info(group, name)516 517 518class IMetadataProvider(Protocol):519 def has_metadata(self, name) -> bool:520 """Does the package's distribution contain the named metadata?"""521 522 def get_metadata(self, name):523 """The named metadata resource as a string"""524 525 def get_metadata_lines(self, name):526 """Yield named metadata resource as list of non-blank non-comment lines527 528 Leading and trailing whitespace is stripped from each line, and lines529 with ``#`` as the first non-blank character are omitted."""530 531 def metadata_isdir(self, name) -> bool:532 """Is the named metadata a directory? (like ``os.path.isdir()``)"""533 534 def metadata_listdir(self, name):535 """List of metadata names in the directory (like ``os.listdir()``)"""536 537 def run_script(self, script_name, namespace):538 """Execute the named script in the supplied namespace dictionary"""539 540 541class IResourceProvider(IMetadataProvider, Protocol):542 """An object that provides access to package resources"""543 544 def get_resource_filename(self, manager, resource_name):545 """Return a true filesystem path for `resource_name`546 547 `manager` must be an ``IResourceManager``"""548 549 def get_resource_stream(self, manager, resource_name):550 """Return a readable file-like object for `resource_name`551 552 `manager` must be an ``IResourceManager``"""553 554 def get_resource_string(self, manager, resource_name) -> bytes:555 """Return the contents of `resource_name` as :obj:`bytes`556 557 `manager` must be an ``IResourceManager``"""558 559 def has_resource(self, resource_name):560 """Does the package contain the named resource?"""561 562 def resource_isdir(self, resource_name):563 """Is the named resource a directory? (like ``os.path.isdir()``)"""564 565 def resource_listdir(self, resource_name):566 """List of resource names in the directory (like ``os.listdir()``)"""567 568 569class WorkingSet:570 """A collection of active distributions on sys.path (or a similar list)"""571 572 def __init__(self, entries=None):573 """Create working set from list of path entries (default=sys.path)"""574 self.entries = []575 self.entry_keys = {}576 self.by_key = {}577 self.normalized_to_canonical_keys = {}578 self.callbacks = []579 580 if entries is None:581 entries = sys.path582 583 for entry in entries:584 self.add_entry(entry)585 586 @classmethod587 def _build_master(cls):588 """589 Prepare the master working set.590 """591 ws = cls()592 try:593 from __main__ import __requires__594 except ImportError:595 # The main program does not list any requirements596 return ws597 598 # ensure the requirements are met599 try:600 ws.require(__requires__)601 except VersionConflict:602 return cls._build_from_requirements(__requires__)603 604 return ws605 606 @classmethod607 def _build_from_requirements(cls, req_spec):608 """609 Build a working set from a requirement spec. Rewrites sys.path.610 """611 # try it without defaults already on sys.path612 # by starting with an empty path613 ws = cls([])614 reqs = parse_requirements(req_spec)615 dists = ws.resolve(reqs, Environment())616 for dist in dists:617 ws.add(dist)618 619 # add any missing entries from sys.path620 for entry in sys.path:621 if entry not in ws.entries:622 ws.add_entry(entry)623 624 # then copy back to sys.path625 sys.path[:] = ws.entries626 return ws627 628 def add_entry(self, entry):629 """Add a path item to ``.entries``, finding any distributions on it630 631 ``find_distributions(entry, True)`` is used to find distributions632 corresponding to the path entry, and they are added. `entry` is633 always appended to ``.entries``, even if it is already present.634 (This is because ``sys.path`` can contain the same value more than635 once, and the ``.entries`` of the ``sys.path`` WorkingSet should always636 equal ``sys.path``.)637 """638 self.entry_keys.setdefault(entry, [])639 self.entries.append(entry)640 for dist in find_distributions(entry, True):641 self.add(dist, entry, False)642 643 def __contains__(self, dist):644 """True if `dist` is the active distribution for its project"""645 return self.by_key.get(dist.key) == dist646 647 def find(self, req):648 """Find a distribution matching requirement `req`649 650 If there is an active distribution for the requested project, this651 returns it as long as it meets the version requirement specified by652 `req`. But, if there is an active distribution for the project and it653 does *not* meet the `req` requirement, ``VersionConflict`` is raised.654 If there is no active distribution for the requested project, ``None``655 is returned.656 """657 dist = self.by_key.get(req.key)658 659 if dist is None:660 canonical_key = self.normalized_to_canonical_keys.get(req.key)661 662 if canonical_key is not None:663 req.key = canonical_key664 dist = self.by_key.get(canonical_key)665 666 if dist is not None and dist not in req:667 # XXX add more info668 raise VersionConflict(dist, req)669 return dist670 671 def iter_entry_points(self, group, name=None):672 """Yield entry point objects from `group` matching `name`673 674 If `name` is None, yields all entry points in `group` from all675 distributions in the working set, otherwise only ones matching676 both `group` and `name` are yielded (in distribution order).677 """678 return (679 entry680 for dist in self681 for entry in dist.get_entry_map(group).values()682 if name is None or name == entry.name683 )684 685 def run_script(self, requires, script_name):686 """Locate distribution for `requires` and run `script_name` script"""687 ns = sys._getframe(1).f_globals688 name = ns['__name__']689 ns.clear()690 ns['__name__'] = name691 self.require(requires)[0].run_script(script_name, ns)692 693 def __iter__(self):694 """Yield distributions for non-duplicate projects in the working set695 696 The yield order is the order in which the items' path entries were697 added to the working set.698 """699 seen = {}700 for item in self.entries:701 if item not in self.entry_keys:702 # workaround a cache issue703 continue704 705 for key in self.entry_keys[item]:706 if key not in seen:707 seen[key] = 1708 yield self.by_key[key]709 710 def add(self, dist, entry=None, insert=True, replace=False):711 """Add `dist` to working set, associated with `entry`712 713 If `entry` is unspecified, it defaults to the ``.location`` of `dist`.714 On exit from this routine, `entry` is added to the end of the working715 set's ``.entries`` (if it wasn't already present).716 717 `dist` is only added to the working set if it's for a project that718 doesn't already have a distribution in the set, unless `replace=True`.719 If it's added, any callbacks registered with the ``subscribe()`` method720 will be called.721 """722 if insert:723 dist.insert_on(self.entries, entry, replace=replace)724 725 if entry is None:726 entry = dist.location727 keys = self.entry_keys.setdefault(entry, [])728 keys2 = self.entry_keys.setdefault(dist.location, [])729 if not replace and dist.key in self.by_key:730 # ignore hidden distros731 return732 733 self.by_key[dist.key] = dist734 normalized_name = packaging.utils.canonicalize_name(dist.key)735 self.normalized_to_canonical_keys[normalized_name] = dist.key736 if dist.key not in keys:737 keys.append(dist.key)738 if dist.key not in keys2:739 keys2.append(dist.key)740 self._added_new(dist)741 742 def resolve(743 self,744 requirements,745 env=None,746 installer=None,747 replace_conflicting=False,748 extras=None,749 ):750 """List all distributions needed to (recursively) meet `requirements`751 752 `requirements` must be a sequence of ``Requirement`` objects. `env`,753 if supplied, should be an ``Environment`` instance. If754 not supplied, it defaults to all distributions available within any755 entry or distribution in the working set. `installer`, if supplied,756 will be invoked with each requirement that cannot be met by an757 already-installed distribution; it should return a ``Distribution`` or758 ``None``.759 760 Unless `replace_conflicting=True`, raises a VersionConflict exception761 if762 any requirements are found on the path that have the correct name but763 the wrong version. Otherwise, if an `installer` is supplied it will be764 invoked to obtain the correct version of the requirement and activate765 it.766 767 `extras` is a list of the extras to be used with these requirements.768 This is important because extra requirements may look like `my_req;769 extra = "my_extra"`, which would otherwise be interpreted as a purely770 optional requirement. Instead, we want to be able to assert that these771 requirements are truly required.772 """773 774 # set up the stack775 requirements = list(requirements)[::-1]776 # set of processed requirements777 processed = {}778 # key -> dist779 best = {}780 to_activate = []781 782 req_extras = _ReqExtras()783 784 # Mapping of requirement to set of distributions that required it;785 # useful for reporting info about conflicts.786 required_by = collections.defaultdict(set)787 788 while requirements:789 # process dependencies breadth-first790 req = requirements.pop(0)791 if req in processed:792 # Ignore cyclic or redundant dependencies793 continue794 795 if not req_extras.markers_pass(req, extras):796 continue797 798 dist = self._resolve_dist(799 req, best, replace_conflicting, env, installer, required_by, to_activate800 )801 802 # push the new requirements onto the stack803 new_requirements = dist.requires(req.extras)[::-1]804 requirements.extend(new_requirements)805 806 # Register the new requirements needed by req807 for new_requirement in new_requirements:808 required_by[new_requirement].add(req.project_name)809 req_extras[new_requirement] = req.extras810 811 processed[req] = True812 813 # return list of distros to activate814 return to_activate815 816 def _resolve_dist(817 self, req, best, replace_conflicting, env, installer, required_by, to_activate818 ):819 dist = best.get(req.key)820 if dist is None:821 # Find the best distribution and add it to the map822 dist = self.by_key.get(req.key)823 if dist is None or (dist not in req and replace_conflicting):824 ws = self825 if env is None:826 if dist is None:827 env = Environment(self.entries)828 else:829 # Use an empty environment and workingset to avoid830 # any further conflicts with the conflicting831 # distribution832 env = Environment([])833 ws = WorkingSet([])834 dist = best[req.key] = env.best_match(835 req, ws, installer, replace_conflicting=replace_conflicting836 )837 if dist is None:838 requirers = required_by.get(req, None)839 raise DistributionNotFound(req, requirers)840 to_activate.append(dist)841 if dist not in req:842 # Oops, the "best" so far conflicts with a dependency843 dependent_req = required_by[req]844 raise VersionConflict(dist, req).with_context(dependent_req)845 return dist846 847 def find_plugins(self, plugin_env, full_env=None, installer=None, fallback=True):848 """Find all activatable distributions in `plugin_env`849 850 Example usage::851 852 distributions, errors = working_set.find_plugins(853 Environment(plugin_dirlist)854 )855 # add plugins+libs to sys.path856 map(working_set.add, distributions)857 # display errors858 print('Could not load', errors)859 860 The `plugin_env` should be an ``Environment`` instance that contains861 only distributions that are in the project's "plugin directory" or862 directories. The `full_env`, if supplied, should be an ``Environment``863 contains all currently-available distributions. If `full_env` is not864 supplied, one is created automatically from the ``WorkingSet`` this865 method is called on, which will typically mean that every directory on866 ``sys.path`` will be scanned for distributions.867 868 `installer` is a standard installer callback as used by the869 ``resolve()`` method. The `fallback` flag indicates whether we should870 attempt to resolve older versions of a plugin if the newest version871 cannot be resolved.872 873 This method returns a 2-tuple: (`distributions`, `error_info`), where874 `distributions` is a list of the distributions found in `plugin_env`875 that were loadable, along with any other distributions that are needed876 to resolve their dependencies. `error_info` is a dictionary mapping877 unloadable plugin distributions to an exception instance describing the878 error that occurred. Usually this will be a ``DistributionNotFound`` or879 ``VersionConflict`` instance.880 """881 882 plugin_projects = list(plugin_env)883 # scan project names in alphabetic order884 plugin_projects.sort()885 886 error_info = {}887 distributions = {}888 889 if full_env is None:890 env = Environment(self.entries)891 env += plugin_env892 else:893 env = full_env + plugin_env894 895 shadow_set = self.__class__([])896 # put all our entries in shadow_set897 list(map(shadow_set.add, self))898 899 for project_name in plugin_projects:900 for dist in plugin_env[project_name]:901 req = [dist.as_requirement()]902 903 try:904 resolvees = shadow_set.resolve(req, env, installer)905 906 except ResolutionError as v:907 # save error info908 error_info[dist] = v909 if fallback:910 # try the next older version of project911 continue912 else:913 # give up on this project, keep going914 break915 916 else:917 list(map(shadow_set.add, resolvees))918 distributions.update(dict.fromkeys(resolvees))919 920 # success, no need to try any more versions of this project921 break922 923 distributions = list(distributions)924 distributions.sort()925 926 return distributions, error_info927 928 def require(self, *requirements):929 """Ensure that distributions matching `requirements` are activated930 931 `requirements` must be a string or a (possibly-nested) sequence932 thereof, specifying the distributions and versions required. The933 return value is a sequence of the distributions that needed to be934 activated to fulfill the requirements; all relevant distributions are935 included, even if they were already activated in this working set.936 """937 needed = self.resolve(parse_requirements(requirements))938 939 for dist in needed:940 self.add(dist)941 942 return needed943 944 def subscribe(self, callback, existing=True):945 """Invoke `callback` for all distributions946 947 If `existing=True` (default),948 call on all existing ones, as well.949 """950 if callback in self.callbacks:951 return952 self.callbacks.append(callback)953 if not existing:954 return955 for dist in self:956 callback(dist)957 958 def _added_new(self, dist):959 for callback in self.callbacks:960 callback(dist)961 962 def __getstate__(self):963 return (964 self.entries[:],965 self.entry_keys.copy(),966 self.by_key.copy(),967 self.normalized_to_canonical_keys.copy(),968 self.callbacks[:],969 )970 971 def __setstate__(self, e_k_b_n_c):972 entries, keys, by_key, normalized_to_canonical_keys, callbacks = e_k_b_n_c973 self.entries = entries[:]974 self.entry_keys = keys.copy()975 self.by_key = by_key.copy()976 self.normalized_to_canonical_keys = normalized_to_canonical_keys.copy()977 self.callbacks = callbacks[:]978 979 980class _ReqExtras(dict):981 """982 Map each requirement to the extras that demanded it.983 """984 985 def markers_pass(self, req, extras=None):986 """987 Evaluate markers for req against each extra that988 demanded it.989 990 Return False if the req has a marker and fails991 evaluation. Otherwise, return True.992 """993 extra_evals = (994 req.marker.evaluate({'extra': extra})995 for extra in self.get(req, ()) + (extras or (None,))996 )997 return not req.marker or any(extra_evals)998 999 1000class Environment:1001 """Searchable snapshot of distributions on a search path"""1002 1003 def __init__(1004 self, search_path=None, platform=get_supported_platform(), python=PY_MAJOR1005 ):1006 """Snapshot distributions available on a search path1007 1008 Any distributions found on `search_path` are added to the environment.1009 `search_path` should be a sequence of ``sys.path`` items. If not1010 supplied, ``sys.path`` is used.1011 1012 `platform` is an optional string specifying the name of the platform1013 that platform-specific distributions must be compatible with. If1014 unspecified, it defaults to the current platform. `python` is an1015 optional string naming the desired version of Python (e.g. ``'3.6'``);1016 it defaults to the current version.1017 1018 You may explicitly set `platform` (and/or `python`) to ``None`` if you1019 wish to map *all* distributions, not just those compatible with the1020 running platform or Python version.1021 """1022 self._distmap = {}1023 self.platform = platform1024 self.python = python1025 self.scan(search_path)1026 1027 def can_add(self, dist):1028 """Is distribution `dist` acceptable for this environment?1029 1030 The distribution must match the platform and python version1031 requirements specified when this environment was created, or False1032 is returned.1033 """1034 py_compat = (1035 self.python is None1036 or dist.py_version is None1037 or dist.py_version == self.python1038 )1039 return py_compat and compatible_platforms(dist.platform, self.platform)1040 1041 def remove(self, dist):1042 """Remove `dist` from the environment"""1043 self._distmap[dist.key].remove(dist)1044 1045 def scan(self, search_path=None):1046 """Scan `search_path` for distributions usable in this environment1047 1048 Any distributions found are added to the environment.1049 `search_path` should be a sequence of ``sys.path`` items. If not1050 supplied, ``sys.path`` is used. Only distributions conforming to1051 the platform/python version defined at initialization are added.1052 """1053 if search_path is None:1054 search_path = sys.path1055 1056 for item in search_path:1057 for dist in find_distributions(item):1058 self.add(dist)1059 1060 def __getitem__(self, project_name):1061 """Return a newest-to-oldest list of distributions for `project_name`1062 1063 Uses case-insensitive `project_name` comparison, assuming all the1064 project's distributions use their project's name converted to all1065 lowercase as their key.1066 1067 """1068 distribution_key = project_name.lower()1069 return self._distmap.get(distribution_key, [])1070 1071 def add(self, dist):1072 """Add `dist` if we ``can_add()`` it and it has not already been added"""1073 if self.can_add(dist) and dist.has_version():1074 dists = self._distmap.setdefault(dist.key, [])1075 if dist not in dists:1076 dists.append(dist)1077 dists.sort(key=operator.attrgetter('hashcmp'), reverse=True)1078 1079 def best_match(self, req, working_set, installer=None, replace_conflicting=False):1080 """Find distribution best matching `req` and usable on `working_set`1081 1082 This calls the ``find(req)`` method of the `working_set` to see if a1083 suitable distribution is already active. (This may raise1084 ``VersionConflict`` if an unsuitable version of the project is already1085 active in the specified `working_set`.) If a suitable distribution1086 isn't active, this method returns the newest distribution in the1087 environment that meets the ``Requirement`` in `req`. If no suitable1088 distribution is found, and `installer` is supplied, then the result of1089 calling the environment's ``obtain(req, installer)`` method will be1090 returned.1091 """1092 try:1093 dist = working_set.find(req)1094 except VersionConflict:1095 if not replace_conflicting:1096 raise1097 dist = None1098 if dist is not None:1099 return dist1100 for dist in self[req.key]:1101 if dist in req:1102 return dist1103 # try to download/install1104 return self.obtain(req, installer)1105 1106 def obtain(self, requirement, installer=None):1107 """Obtain a distribution matching `requirement` (e.g. via download)1108 1109 Obtain a distro that matches requirement (e.g. via download). In the1110 base ``Environment`` class, this routine just returns1111 ``installer(requirement)``, unless `installer` is None, in which case1112 None is returned instead. This method is a hook that allows subclasses1113 to attempt other ways of obtaining a distribution before falling back1114 to the `installer` argument."""1115 return installer(requirement) if installer else None1116 1117 def __iter__(self):1118 """Yield the unique project names of the available distributions"""1119 for key in self._distmap.keys():1120 if self[key]:1121 yield key1122 1123 def __iadd__(self, other):1124 """In-place addition of a distribution or environment"""1125 if isinstance(other, Distribution):1126 self.add(other)1127 elif isinstance(other, Environment):1128 for project in other:1129 for dist in other[project]:1130 self.add(dist)1131 else:1132 raise TypeError("Can't add %r to environment" % (other,))1133 return self1134 1135 def __add__(self, other):1136 """Add an environment or distribution to an environment"""1137 new = self.__class__([], platform=None, python=None)1138 for env in self, other:1139 new += env1140 return new1141 1142 1143# XXX backward compatibility1144AvailableDistributions = Environment1145 1146 1147class ExtractionError(RuntimeError):1148 """An error occurred extracting a resource1149 1150 The following attributes are available from instances of this exception:1151 1152 manager1153 The resource manager that raised this exception1154 1155 cache_path1156 The base directory for resource extraction1157 1158 original_error1159 The exception instance that caused extraction to fail1160 """1161 1162 1163class ResourceManager:1164 """Manage resource extraction and packages"""1165 1166 extraction_path = None1167 1168 def __init__(self):1169 self.cached_files = {}1170 1171 def resource_exists(self, package_or_requirement, resource_name):1172 """Does the named resource exist?"""1173 return get_provider(package_or_requirement).has_resource(resource_name)1174 1175 def resource_isdir(self, package_or_requirement, resource_name):1176 """Is the named resource an existing directory?"""1177 return get_provider(package_or_requirement).resource_isdir(resource_name)1178 1179 def resource_filename(self, package_or_requirement, resource_name):1180 """Return a true filesystem path for specified resource"""1181 return get_provider(package_or_requirement).get_resource_filename(1182 self, resource_name1183 )1184 1185 def resource_stream(self, package_or_requirement, resource_name):1186 """Return a readable file-like object for specified resource"""1187 return get_provider(package_or_requirement).get_resource_stream(1188 self, resource_name1189 )1190 1191 def resource_string(self, package_or_requirement, resource_name) -> bytes:1192 """Return specified resource as :obj:`bytes`"""1193 return get_provider(package_or_requirement).get_resource_string(1194 self, resource_name1195 )1196 1197 def resource_listdir(self, package_or_requirement, resource_name):1198 """List the contents of the named resource directory"""1199 return get_provider(package_or_requirement).resource_listdir(resource_name)1200 