1"""
2Package resource API
3--------------------
4
5A resource is a logical file contained within a package, or a logical
6subdirectory thereof.  The package resource API expects resource names
7to have their path parts separated with ``/``, *not* whatever the local
8path separator is.  Do not use os.path operations to manipulate resource
9names 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 with
13.zip files and with custom PEP 302 loaders that support the ``get_data()``
14method.
15"""
16
17import sys
18import os
19import io
20import time
21import re
22import types
23import zipfile
24import zipimport
25import warnings
26import stat
27import functools
28import pkgutil
29import operator
30import platform
31import collections
32import plistlib
33import email.parser
34import errno
35import tempfile
36import textwrap
37import itertools
38import inspect
39import ntpath
40import posixpath
41import importlib
42from pkgutil import get_importer
43
44try:
45    import _imp
46except ImportError:
47    # Python 3.2 compatibility
48    import imp as _imp
49
50try:
51    FileExistsError
52except NameError:
53    FileExistsError = OSError
54
55# capture these to bypass sandboxing
56from os import utime
57try:
58    from os import mkdir, rename, unlink
59    WRITE_SUPPORT = True
60except ImportError:
61    # no write support, probably under GAE
62    WRITE_SUPPORT = False
63
64from os import open as os_open
65from os.path import isdir, split
66
67try:
68    import importlib.machinery as importlib_machinery
69    # access attribute to force import under delayed import mechanisms.
70    importlib_machinery.__name__
71except ImportError:
72    importlib_machinery = None
73
74from pkg_resources.extern import appdirs
75from pkg_resources.extern import packaging
76__import__('pkg_resources.extern.packaging.version')
77__import__('pkg_resources.extern.packaging.specifiers')
78__import__('pkg_resources.extern.packaging.requirements')
79__import__('pkg_resources.extern.packaging.markers')
80
81if sys.version_info < (3, 5):
82    raise RuntimeError("Python 3.5 or later is required")
83
84# declare some globals that will be defined later to
85# satisfy the linters.
86require = None
87working_set = None
88add_activation_listener = None
89resources_stream = None
90cleanup_resources = None
91resource_dir = None
92resource_stream = None
93set_extraction_path = None
94resource_isdir = None
95resource_string = None
96iter_entry_points = None
97resource_listdir = None
98resource_filename = None
99resource_exists = None
100_distribution_finders = None
101_namespace_handlers = None
102_namespace_packages = None
103
104
105class PEP440Warning(RuntimeWarning):
106    """
107    Used when there is an issue with a version or specifier not complying with
108    PEP 440.
109    """
110
111
112def parse_version(v):
113    try:
114        return packaging.version.Version(v)
115    except packaging.version.InvalidVersion:
116        return packaging.version.LegacyVersion(v)
117
118
119_state_vars = {}
120
121
122def _declare_state(vartype, **kw):
123    globals().update(kw)
124    _state_vars.update(dict.fromkeys(kw, vartype))
125
126
127def __getstate__():
128    state = {}
129    g = globals()
130    for k, v in _state_vars.items():
131        state[k] = g['_sget_' + v](g[k])
132    return state
133
134
135def __setstate__(state):
136    g = globals()
137    for k, v in state.items():
138        g['_sset_' + _state_vars[k]](k, g[k], v)
139    return state
140
141
142def _sget_dict(val):
143    return val.copy()
144
145
146def _sset_dict(key, ob, state):
147    ob.clear()
148    ob.update(state)
149
150
151def _sget_object(val):
152    return val.__getstate__()
153
154
155def _sset_object(key, ob, state):
156    ob.__setstate__(state)
157
158
159_sget_none = _sset_none = lambda *args: None
160
161
162def get_supported_platform():
163    """Return this platform's maximum compatible version.
164
165    distutils.util.get_platform() normally reports the minimum version
166    of macOS that would be required to *use* extensions produced by
167    distutils.  But what we want when checking compatibility is to know the
168    version of macOS that we are *running*.  To allow usage of packages that
169    explicitly require a newer version of macOS, we must also know the
170    current version of the OS.
171
172    If this condition occurs for any other platform with a version in its
173    platform strings, this function should be extended accordingly.
174    """
175    plat = get_build_platform()
176    m = macosVersionString.match(plat)
177    if m is not None and sys.platform == "darwin":
178        try:
179            plat = 'macosx-%s-%s' % ('.'.join(_macos_vers()[:2]), m.group(3))
180        except ValueError:
181            # not macOS
182            pass
183    return plat
184
185
186__all__ = [
187    # Basic resource access and distribution/entry point discovery
188    'require', 'run_script', 'get_provider', 'get_distribution',
189    'load_entry_point', 'get_entry_map', 'get_entry_info',
190    'iter_entry_points',
191    'resource_string', 'resource_stream', 'resource_filename',
192    'resource_listdir', 'resource_exists', 'resource_isdir',
193
194    # Environmental control
195    'declare_namespace', 'working_set', 'add_activation_listener',
196    'find_distributions', 'set_extraction_path', 'cleanup_resources',
197    'get_default_cache',
198
199    # Primary implementation classes
200    'Environment', 'WorkingSet', 'ResourceManager',
201    'Distribution', 'Requirement', 'EntryPoint',
202
203    # Exceptions
204    'ResolutionError', 'VersionConflict', 'DistributionNotFound',
205    'UnknownExtra', 'ExtractionError',
206
207    # Warnings
208    'PEP440Warning',
209
210    # Parsing functions and string utilities
211    'parse_requirements', 'parse_version', 'safe_name', 'safe_version',
212    'get_platform', 'compatible_platforms', 'yield_lines', 'split_sections',
213    'safe_extra', 'to_filename', 'invalid_marker', 'evaluate_marker',
214
215    # filesystem utilities
216    'ensure_directory', 'normalize_path',
217
218    # Distribution "precedence" constants
219    'EGG_DIST', 'BINARY_DIST', 'SOURCE_DIST', 'CHECKOUT_DIST', 'DEVELOP_DIST',
220
221    # "Provider" interfaces, implementations, and registration/lookup APIs
222    'IMetadataProvider', 'IResourceProvider', 'FileMetadata',
223    'PathMetadata', 'EggMetadata', 'EmptyProvider', 'empty_provider',
224    'NullProvider', 'EggProvider', 'DefaultProvider', 'ZipProvider',
225    'register_finder', 'register_namespace_handler', 'register_loader_type',
226    'fixup_namespace_packages', 'get_importer',
227
228    # Warnings
229    'PkgResourcesDeprecationWarning',
230
231    # Deprecated/backward compatibility only
232    'run_main', 'AvailableDistributions',
233]
234
235
236class ResolutionError(Exception):
237    """Abstract base for dependency resolution errors"""
238
239    def __repr__(self):
240        return self.__class__.__name__ + repr(self.args)
241
242
243class VersionConflict(ResolutionError):
244    """
245    An already-installed version conflicts with the requested version.
246
247    Should be initialized with the installed Distribution and the requested
248    Requirement.
249    """
250
251    _template = "{self.dist} is installed but {self.req} is required"
252
253    @property
254    def dist(self):
255        return self.args[0]
256
257    @property
258    def req(self):
259        return self.args[1]
260
261    def report(self):
262        return self._template.format(**locals())
263
264    def with_context(self, required_by):
265        """
266        If required_by is non-empty, return a version of self that is a
267        ContextualVersionConflict.
268        """
269        if not required_by:
270            return self
271        args = self.args + (required_by,)
272        return ContextualVersionConflict(*args)
273
274
275class ContextualVersionConflict(VersionConflict):
276    """
277    A VersionConflict that accepts a third parameter, the set of the
278    requirements that required the installed Distribution.
279    """
280
281    _template = VersionConflict._template + ' by {self.required_by}'
282
283    @property
284    def required_by(self):
285        return self.args[2]
286
287
288class DistributionNotFound(ResolutionError):
289    """A requested distribution was not found"""
290
291    _template = ("The '{self.req}' distribution was not found "
292                 "and is required by {self.requirers_str}")
293
294    @property
295    def req(self):
296        return self.args[0]
297
298    @property
299    def requirers(self):
300        return self.args[1]
301
302    @property
303    def requirers_str(self):
304        if not self.requirers:
305            return 'the application'
306        return ', '.join(self.requirers)
307
308    def report(self):
309        return self._template.format(**locals())
310
311    def __str__(self):
312        return self.report()
313
314
315class UnknownExtra(ResolutionError):
316    """Distribution doesn't have an "extra feature" of the given name"""
317
318
319_provider_factories = {}
320
321PY_MAJOR = '{}.{}'.format(*sys.version_info)
322EGG_DIST = 3
323BINARY_DIST = 2
324SOURCE_DIST = 1
325CHECKOUT_DIST = 0
326DEVELOP_DIST = -1
327
328
329def register_loader_type(loader_type, provider_factory):
330    """Register `provider_factory` to make providers for `loader_type`
331
332    `loader_type` is the type or class of a PEP 302 ``module.__loader__``,
333    and `provider_factory` is a function that, passed a *module* object,
334    returns an ``IResourceProvider`` for that module.
335    """
336    _provider_factories[loader_type] = provider_factory
337
338
339def get_provider(moduleOrReq):
340    """Return an IResourceProvider for the named module or requirement"""
341    if isinstance(moduleOrReq, Requirement):
342        return working_set.find(moduleOrReq) or require(str(moduleOrReq))[0]
343    try:
344        module = sys.modules[moduleOrReq]
345    except KeyError:
346        __import__(moduleOrReq)
347        module = sys.modules[moduleOrReq]
348    loader = getattr(module, '__loader__', None)
349    return _find_adapter(_provider_factories, loader)(module)
350
351
352def _macos_vers(_cache=[]):
353    if not _cache:
354        version = platform.mac_ver()[0]
355        # fallback for MacPorts
356        if version == '':
357            plist = '/System/Library/CoreServices/SystemVersion.plist'
358            if os.path.exists(plist):
359                if hasattr(plistlib, 'readPlist'):
360                    plist_content = plistlib.readPlist(plist)
361                    if 'ProductVersion' in plist_content:
362                        version = plist_content['ProductVersion']
363
364        _cache.append(version.split('.'))
365    return _cache[0]
366
367
368def _macos_arch(machine):
369    return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine)
370
371
372def get_build_platform():
373    """Return this platform's string for platform-specific distributions
374
375    XXX Currently this is the same as ``distutils.util.get_platform()``, but it
376    needs some hacks for Linux and macOS.
377    """
378    from sysconfig import get_platform
379
380    plat = get_platform()
381    if sys.platform == "darwin" and not plat.startswith('macosx-'):
382        try:
383            version = _macos_vers()
384            machine = os.uname()[4].replace(" ", "_")
385            return "macosx-%d.%d-%s" % (
386                int(version[0]), int(version[1]),
387                _macos_arch(machine),
388            )
389        except ValueError:
390            # if someone is running a non-Mac darwin system, this will fall
391            # through to the default implementation
392            pass
393    return plat
394
395
396macosVersionString = re.compile(r"macosx-(\d+)\.(\d+)-(.*)")
397darwinVersionString = re.compile(r"darwin-(\d+)\.(\d+)\.(\d+)-(.*)")
398# XXX backward compat
399get_platform = get_build_platform
400
401
402def compatible_platforms(provided, required):
403    """Can code for the `provided` platform run on the `required` platform?
404
405    Returns true if either platform is ``None``, or the platforms are equal.
406
407    XXX Needs compatibility checks for Linux and other unixy OSes.
408    """
409    if provided is None or required is None or provided == required:
410        # easy case
411        return True
412
413    # macOS special cases
414    reqMac = macosVersionString.match(required)
415    if reqMac:
416        provMac = macosVersionString.match(provided)
417
418        # is this a Mac package?
419        if not provMac:
420            # this is backwards compatibility for packages built before
421            # setuptools 0.6. All packages built after this point will
422            # use the new macOS designation.
423            provDarwin = darwinVersionString.match(provided)
424            if provDarwin:
425                dversion = int(provDarwin.group(1))
426                macosversion = "%s.%s" % (reqMac.group(1), reqMac.group(2))
427                if dversion == 7 and macosversion >= "10.3" or \
428                        dversion == 8 and macosversion >= "10.4":
429                    return True
430            # egg isn't macOS or legacy darwin
431            return False
432
433        # are they the same major version and machine type?
434        if provMac.group(1) != reqMac.group(1) or \
435                provMac.group(3) != reqMac.group(3):
436            return False
437
438        # is the required OS major update >= the provided one?
439        if int(provMac.group(2)) > int(reqMac.group(2)):
440            return False
441
442        return True
443
444    # XXX Linux and other platforms' special cases should go here
445    return False
446
447
448def run_script(dist_spec, script_name):
449    """Locate distribution `dist_spec` and run its `script_name` script"""
450    ns = sys._getframe(1).f_globals
451    name = ns['__name__']
452    ns.clear()
453    ns['__name__'] = name
454    require(dist_spec)[0].run_script(script_name, ns)
455
456
457# backward compatibility
458run_main = run_script
459
460
461def get_distribution(dist):
462    """Return a current distribution object for a Requirement or string"""
463    if isinstance(dist, str):
464        dist = Requirement.parse(dist)
465    if isinstance(dist, Requirement):
466        dist = get_provider(dist)
467    if not isinstance(dist, Distribution):
468        raise TypeError("Expected string, Requirement, or Distribution", dist)
469    return dist
470
471
472def load_entry_point(dist, group, name):
473    """Return `name` entry point of `group` for `dist` or raise ImportError"""
474    return get_distribution(dist).load_entry_point(group, name)
475
476
477def get_entry_map(dist, group=None):
478    """Return the entry point map for `group`, or the full entry map"""
479    return get_distribution(dist).get_entry_map(group)
480
481
482def get_entry_info(dist, group, name):
483    """Return the EntryPoint object for `group`+`name`, or ``None``"""
484    return get_distribution(dist).get_entry_info(group, name)
485
486
487class IMetadataProvider:
488    def has_metadata(name):
489        """Does the package's distribution contain the named metadata?"""
490
491    def get_metadata(name):
492        """The named metadata resource as a string"""
493
494    def get_metadata_lines(name):
495        """Yield named metadata resource as list of non-blank non-comment lines
496
497       Leading and trailing whitespace is stripped from each line, and lines
498       with ``#`` as the first non-blank character are omitted."""
499
500    def metadata_isdir(name):
501        """Is the named metadata a directory?  (like ``os.path.isdir()``)"""
502
503    def metadata_listdir(name):
504        """List of metadata names in the directory (like ``os.listdir()``)"""
505
506    def run_script(script_name, namespace):
507        """Execute the named script in the supplied namespace dictionary"""
508
509
510class IResourceProvider(IMetadataProvider):
511    """An object that provides access to package resources"""
512
513    def get_resource_filename(manager, resource_name):
514        """Return a true filesystem path for `resource_name`
515
516        `manager` must be an ``IResourceManager``"""
517
518    def get_resource_stream(manager, resource_name):
519        """Return a readable file-like object for `resource_name`
520
521        `manager` must be an ``IResourceManager``"""
522
523    def get_resource_string(manager, resource_name):
524        """Return a string containing the contents of `resource_name`
525
526        `manager` must be an ``IResourceManager``"""
527
528    def has_resource(resource_name):
529        """Does the package contain the named resource?"""
530
531    def resource_isdir(resource_name):
532        """Is the named resource a directory?  (like ``os.path.isdir()``)"""
533
534    def resource_listdir(resource_name):
535        """List of resource names in the directory (like ``os.listdir()``)"""
536
537
538class WorkingSet:
539    """A collection of active distributions on sys.path (or a similar list)"""
540
541    def __init__(self, entries=None):
542        """Create working set from list of path entries (default=sys.path)"""
543        self.entries = []
544        self.entry_keys = {}
545        self.by_key = {}
546        self.callbacks = []
547
548        if entries is None:
549            entries = sys.path
550
551        for entry in entries:
552            self.add_entry(entry)
553
554    @classmethod
555    def _build_master(cls):
556        """
557        Prepare the master working set.
558        """
559        ws = cls()
560        try:
561            from __main__ import __requires__
562        except ImportError:
563            # The main program does not list any requirements
564            return ws
565
566        # ensure the requirements are met
567        try:
568            ws.require(__requires__)
569        except VersionConflict:
570            return cls._build_from_requirements(__requires__)
571
572        return ws
573
574    @classmethod
575    def _build_from_requirements(cls, req_spec):
576        """
577        Build a working set from a requirement spec. Rewrites sys.path.
578        """
579        # try it without defaults already on sys.path
580        # by starting with an empty path
581        ws = cls([])
582        reqs = parse_requirements(req_spec)
583        dists = ws.resolve(reqs, Environment())
584        for dist in dists:
585            ws.add(dist)
586
587        # add any missing entries from sys.path
588        for entry in sys.path:
589            if entry not in ws.entries:
590                ws.add_entry(entry)
591
592        # then copy back to sys.path
593        sys.path[:] = ws.entries
594        return ws
595
596    def add_entry(self, entry):
597        """Add a path item to ``.entries``, finding any distributions on it
598
599        ``find_distributions(entry, True)`` is used to find distributions
600        corresponding to the path entry, and they are added.  `entry` is
601        always appended to ``.entries``, even if it is already present.
602        (This is because ``sys.path`` can contain the same value more than
603        once, and the ``.entries`` of the ``sys.path`` WorkingSet should always
604        equal ``sys.path``.)
605        """
606        self.entry_keys.setdefault(entry, [])
607        self.entries.append(entry)
608        for dist in find_distributions(entry, True):
609            self.add(dist, entry, False)
610
611    def __contains__(self, dist):
612        """True if `dist` is the active distribution for its project"""
613        return self.by_key.get(dist.key) == dist
614
615    def find(self, req):
616        """Find a distribution matching requirement `req`
617
618        If there is an active distribution for the requested project, this
619        returns it as long as it meets the version requirement specified by
620        `req`.  But, if there is an active distribution for the project and it
621        does *not* meet the `req` requirement, ``VersionConflict`` is raised.
622        If there is no active distribution for the requested project, ``None``
623        is returned.
624        """
625        dist = self.by_key.get(req.key)
626        if dist is not None and dist not in req:
627            # XXX add more info
628            raise VersionConflict(dist, req)
629        return dist
630
631    def iter_entry_points(self, group, name=None):
632        """Yield entry point objects from `group` matching `name`
633
634        If `name` is None, yields all entry points in `group` from all
635        distributions in the working set, otherwise only ones matching
636        both `group` and `name` are yielded (in distribution order).
637        """
638        return (
639            entry
640            for dist in self
641            for entry in dist.get_entry_map(group).values()
642            if name is None or name == entry.name
643        )
644
645    def run_script(self, requires, script_name):
646        """Locate distribution for `requires` and run `script_name` script"""
647        ns = sys._getframe(1).f_globals
648        name = ns['__name__']
649        ns.clear()
650        ns['__name__'] = name
651        self.require(requires)[0].run_script(script_name, ns)
652
653    def __iter__(self):
654        """Yield distributions for non-duplicate projects in the working set
655
656        The yield order is the order in which the items' path entries were
657        added to the working set.
658        """
659        seen = {}
660        for item in self.entries:
661            if item not in self.entry_keys:
662                # workaround a cache issue
663                continue
664
665            for key in self.entry_keys[item]:
666                if key not in seen:
667                    seen[key] = 1
668                    yield self.by_key[key]
669
670    def add(self, dist, entry=None, insert=True, replace=False):
671        """Add `dist` to working set, associated with `entry`
672
673        If `entry` is unspecified, it defaults to the ``.location`` of `dist`.
674        On exit from this routine, `entry` is added to the end of the working
675        set's ``.entries`` (if it wasn't already present).
676
677        `dist` is only added to the working set if it's for a project that
678        doesn't already have a distribution in the set, unless `replace=True`.
679        If it's added, any callbacks registered with the ``subscribe()`` method
680        will be called.
681        """
682        if insert:
683            dist.insert_on(self.entries, entry, replace=replace)
684
685        if entry is None:
686            entry = dist.location
687        keys = self.entry_keys.setdefault(entry, [])
688        keys2 = self.entry_keys.setdefault(dist.location, [])
689        if not replace and dist.key in self.by_key:
690            # ignore hidden distros
691            return
692
693        self.by_key[dist.key] = dist
694        if dist.key not in keys:
695            keys.append(dist.key)
696        if dist.key not in keys2:
697            keys2.append(dist.key)
698        self._added_new(dist)
699
700    # FIXME: 'WorkingSet.resolve' is too complex (11)
701    def resolve(self, requirements, env=None, installer=None,  # noqa: C901
702                replace_conflicting=False, extras=None):
703        """List all distributions needed to (recursively) meet `requirements`
704
705        `requirements` must be a sequence of ``Requirement`` objects.  `env`,
706        if supplied, should be an ``Environment`` instance.  If
707        not supplied, it defaults to all distributions available within any
708        entry or distribution in the working set.  `installer`, if supplied,
709        will be invoked with each requirement that cannot be met by an
710        already-installed distribution; it should return a ``Distribution`` or
711        ``None``.
712
713        Unless `replace_conflicting=True`, raises a VersionConflict exception
714        if
715        any requirements are found on the path that have the correct name but
716        the wrong version.  Otherwise, if an `installer` is supplied it will be
717        invoked to obtain the correct version of the requirement and activate
718        it.
719
720        `extras` is a list of the extras to be used with these requirements.
721        This is important because extra requirements may look like `my_req;
722        extra = "my_extra"`, which would otherwise be interpreted as a purely
723        optional requirement.  Instead, we want to be able to assert that these
724        requirements are truly required.
725        """
726
727        # set up the stack
728        requirements = list(requirements)[::-1]
729        # set of processed requirements
730        processed = {}
731        # key -> dist
732        best = {}
733        to_activate = []
734
735        req_extras = _ReqExtras()
736
737        # Mapping of requirement to set of distributions that required it;
738        # useful for reporting info about conflicts.
739        required_by = collections.defaultdict(set)
740
741        while requirements:
742            # process dependencies breadth-first
743            req = requirements.pop(0)
744            if req in processed:
745                # Ignore cyclic or redundant dependencies
746                continue
747
748            if not req_extras.markers_pass(req, extras):
749                continue
750
751            dist = best.get(req.key)
752            if dist is None:
753                # Find the best distribution and add it to the map
754                dist = self.by_key.get(req.key)
755                if dist is None or (dist not in req and replace_conflicting):
756                    ws = self
757                    if env is None:
758                        if dist is None:
759                            env = Environment(self.entries)
760                        else:
761                            # Use an empty environment and workingset to avoid
762                            # any further conflicts with the conflicting
763                            # distribution
764                            env = Environment([])
765                            ws = WorkingSet([])
766                    dist = best[req.key] = env.best_match(
767                        req, ws, installer,
768                        replace_conflicting=replace_conflicting
769                    )
770                    if dist is None:
771                        requirers = required_by.get(req, None)
772                        raise DistributionNotFound(req, requirers)
773                to_activate.append(dist)
774            if dist not in req:
775                # Oops, the "best" so far conflicts with a dependency
776                dependent_req = required_by[req]
777                raise VersionConflict(dist, req).with_context(dependent_req)
778
779            # push the new requirements onto the stack
780            new_requirements = dist.requires(req.extras)[::-1]
781            requirements.extend(new_requirements)
782
783            # Register the new requirements needed by req
784            for new_requirement in new_requirements:
785                required_by[new_requirement].add(req.project_name)
786                req_extras[new_requirement] = req.extras
787
788            processed[req] = True
789
790        # return list of distros to activate
791        return to_activate
792
793    def find_plugins(
794            self, plugin_env, full_env=None, installer=None, fallback=True):
795        """Find all activatable distributions in `plugin_env`
796
797        Example usage::
798
799            distributions, errors = working_set.find_plugins(
800                Environment(plugin_dirlist)
801            )
802            # add plugins+libs to sys.path
803            map(working_set.add, distributions)
804            # display errors
805            print('Could not load', errors)
806
807        The `plugin_env` should be an ``Environment`` instance that contains
808        only distributions that are in the project's "plugin directory" or
809        directories. The `full_env`, if supplied, should be an ``Environment``
810        contains all currently-available distributions.  If `full_env` is not
811        supplied, one is created automatically from the ``WorkingSet`` this
812        method is called on, which will typically mean that every directory on
813        ``sys.path`` will be scanned for distributions.
814
815        `installer` is a standard installer callback as used by the
816        ``resolve()`` method. The `fallback` flag indicates whether we should
817        attempt to resolve older versions of a plugin if the newest version
818        cannot be resolved.
819
820        This method returns a 2-tuple: (`distributions`, `error_info`), where
821        `distributions` is a list of the distributions found in `plugin_env`
822        that were loadable, along with any other distributions that are needed
823        to resolve their dependencies.  `error_info` is a dictionary mapping
824        unloadable plugin distributions to an exception instance describing the
825        error that occurred. Usually this will be a ``DistributionNotFound`` or
826        ``VersionConflict`` instance.
827        """
828
829        plugin_projects = list(plugin_env)
830        # scan project names in alphabetic order
831        plugin_projects.sort()
832
833        error_info = {}
834        distributions = {}
835
836        if full_env is None:
837            env = Environment(self.entries)
838            env += plugin_env
839        else:
840            env = full_env + plugin_env
841
842        shadow_set = self.__class__([])
843        # put all our entries in shadow_set
844        list(map(shadow_set.add, self))
845
846        for project_name in plugin_projects:
847
848            for dist in plugin_env[project_name]:
849
850                req = [dist.as_requirement()]
851
852                try:
853                    resolvees = shadow_set.resolve(req, env, installer)
854
855                except ResolutionError as v:
856                    # save error info
857                    error_info[dist] = v
858                    if fallback:
859                        # try the next older version of project
860                        continue
861                    else:
862                        # give up on this project, keep going
863                        break
864
865                else:
866                    list(map(shadow_set.add, resolvees))
867                    distributions.update(dict.fromkeys(resolvees))
868
869                    # success, no need to try any more versions of this project
870                    break
871
872        distributions = list(distributions)
873        distributions.sort()
874
875        return distributions, error_info
876
877    def require(self, *requirements):
878        """Ensure that distributions matching `requirements` are activated
879
880        `requirements` must be a string or a (possibly-nested) sequence
881        thereof, specifying the distributions and versions required.  The
882        return value is a sequence of the distributions that needed to be
883        activated to fulfill the requirements; all relevant distributions are
884        included, even if they were already activated in this working set.
885        """
886        needed = self.resolve(parse_requirements(requirements))
887
888        for dist in needed:
889            self.add(dist)
890
891        return needed
892
893    def subscribe(self, callback, existing=True):
894        """Invoke `callback` for all distributions
895
896        If `existing=True` (default),
897        call on all existing ones, as well.
898        """
899        if callback in self.callbacks:
900            return
901        self.callbacks.append(callback)
902        if not existing:
903            return
904        for dist in self:
905            callback(dist)
906
907    def _added_new(self, dist):
908        for callback in self.callbacks:
909            callback(dist)
910
911    def __getstate__(self):
912        return (
913            self.entries[:], self.entry_keys.copy(), self.by_key.copy(),
914            self.callbacks[:]
915        )
916
917    def __setstate__(self, e_k_b_c):
918        entries, keys, by_key, callbacks = e_k_b_c
919        self.entries = entries[:]
920        self.entry_keys = keys.copy()
921        self.by_key = by_key.copy()
922        self.callbacks = callbacks[:]
923
924
925class _ReqExtras(dict):
926    """
927    Map each requirement to the extras that demanded it.
928    """
929
930    def markers_pass(self, req, extras=None):
931        """
932        Evaluate markers for req against each extra that
933        demanded it.
934
935        Return False if the req has a marker and fails
936        evaluation. Otherwise, return True.
937        """
938        extra_evals = (
939            req.marker.evaluate({'extra': extra})
940            for extra in self.get(req, ()) + (extras or (None,))
941        )
942        return not req.marker or any(extra_evals)
943
944
945class Environment:
946    """Searchable snapshot of distributions on a search path"""
947
948    def __init__(
949            self, search_path=None, platform=get_supported_platform(),
950            python=PY_MAJOR):
951        """Snapshot distributions available on a search path
952
953        Any distributions found on `search_path` are added to the environment.
954        `search_path` should be a sequence of ``sys.path`` items.  If not
955        supplied, ``sys.path`` is used.
956
957        `platform` is an optional string specifying the name of the platform
958        that platform-specific distributions must be compatible with.  If
959        unspecified, it defaults to the current platform.  `python` is an
960        optional string naming the desired version of Python (e.g. ``'3.6'``);
961        it defaults to the current version.
962
963        You may explicitly set `platform` (and/or `python`) to ``None`` if you
964        wish to map *all* distributions, not just those compatible with the
965        running platform or Python version.
966        """
967        self._distmap = {}
968        self.platform = platform
969        self.python = python
970        self.scan(search_path)
971
972    def can_add(self, dist):
973        """Is distribution `dist` acceptable for this environment?
974
975        The distribution must match the platform and python version
976        requirements specified when this environment was created, or False
977        is returned.
978        """
979        py_compat = (
980            self.python is None
981            or dist.py_version is None
982            or dist.py_version == self.python
983        )
984        return py_compat and compatible_platforms(dist.platform, self.platform)
985
986    def remove(self, dist):
987        """Remove `dist` from the environment"""
988        self._distmap[dist.key].remove(dist)
989
990    def scan(self, search_path=None):
991        """Scan `search_path` for distributions usable in this environment
992
993        Any distributions found are added to the environment.
994        `search_path` should be a sequence of ``sys.path`` items.  If not
995        supplied, ``sys.path`` is used.  Only distributions conforming to
996        the platform/python version defined at initialization are added.
997        """
998        if search_path is None:
999            search_path = sys.path
1000
1001        for item in search_path:
1002            for dist in find_distributions(item):
1003                self.add(dist)
1004
1005    def __getitem__(self, project_name):
1006        """Return a newest-to-oldest list of distributions for `project_name`
1007
1008        Uses case-insensitive `project_name` comparison, assuming all the
1009        project's distributions use their project's name converted to all
1010        lowercase as their key.
1011
1012        """
1013        distribution_key = project_name.lower()
1014        return self._distmap.get(distribution_key, [])
1015
1016    def add(self, dist):
1017        """Add `dist` if we ``can_add()`` it and it has not already been added
1018        """
1019        if self.can_add(dist) and dist.has_version():
1020            dists = self._distmap.setdefault(dist.key, [])
1021            if dist not in dists:
1022                dists.append(dist)
1023                dists.sort(key=operator.attrgetter('hashcmp'), reverse=True)
1024
1025    def best_match(
1026            self, req, working_set, installer=None, replace_conflicting=False):
1027        """Find distribution best matching `req` and usable on `working_set`
1028
1029        This calls the ``find(req)`` method of the `working_set` to see if a
1030        suitable distribution is already active.  (This may raise
1031        ``VersionConflict`` if an unsuitable version of the project is already
1032        active in the specified `working_set`.)  If a suitable distribution
1033        isn't active, this method returns the newest distribution in the
1034        environment that meets the ``Requirement`` in `req`.  If no suitable
1035        distribution is found, and `installer` is supplied, then the result of
1036        calling the environment's ``obtain(req, installer)`` method will be
1037        returned.
1038        """
1039        try:
1040            dist = working_set.find(req)
1041        except VersionConflict:
1042            if not replace_conflicting:
1043                raise
1044            dist = None
1045        if dist is not None:
1046            return dist
1047        for dist in self[req.key]:
1048            if dist in req:
1049                return dist
1050        # try to download/install
1051        return self.obtain(req, installer)
1052
1053    def obtain(self, requirement, installer=None):
1054        """Obtain a distribution matching `requirement` (e.g. via download)
1055
1056        Obtain a distro that matches requirement (e.g. via download).  In the
1057        base ``Environment`` class, this routine just returns
1058        ``installer(requirement)``, unless `installer` is None, in which case
1059        None is returned instead.  This method is a hook that allows subclasses
1060        to attempt other ways of obtaining a distribution before falling back
1061        to the `installer` argument."""
1062        if installer is not None:
1063            return installer(requirement)
1064
1065    def __iter__(self):
1066        """Yield the unique project names of the available distributions"""
1067        for key in self._distmap.keys():
1068            if self[key]:
1069                yield key
1070
1071    def __iadd__(self, other):
1072        """In-place addition of a distribution or environment"""
1073        if isinstance(other, Distribution):
1074            self.add(other)
1075        elif isinstance(other, Environment):
1076            for project in other:
1077                for dist in other[project]:
1078                    self.add(dist)
1079        else:
1080            raise TypeError("Can't add %r to environment" % (other,))
1081        return self
1082
1083    def __add__(self, other):
1084        """Add an environment or distribution to an environment"""
1085        new = self.__class__([], platform=None, python=None)
1086        for env in self, other:
1087            new += env
1088        return new
1089
1090
1091# XXX backward compatibility
1092AvailableDistributions = Environment
1093
1094
1095class ExtractionError(RuntimeError):
1096    """An error occurred extracting a resource
1097
1098    The following attributes are available from instances of this exception:
1099
1100    manager
1101        The resource manager that raised this exception
1102
1103    cache_path
1104        The base directory for resource extraction
1105
1106    original_error
1107        The exception instance that caused extraction to fail
1108    """
1109
1110
1111class ResourceManager:
1112    """Manage resource extraction and packages"""
1113    extraction_path = None
1114
1115    def __init__(self):
1116        self.cached_files = {}
1117
1118    def resource_exists(self, package_or_requirement, resource_name):
1119        """Does the named resource exist?"""
1120        return get_provider(package_or_requirement).has_resource(resource_name)
1121
1122    def resource_isdir(self, package_or_requirement, resource_name):
1123        """Is the named resource an existing directory?"""
1124        return get_provider(package_or_requirement).resource_isdir(
1125            resource_name
1126        )
1127
1128    def resource_filename(self, package_or_requirement, resource_name):
1129        """Return a true filesystem path for specified resource"""
1130        return get_provider(package_or_requirement).get_resource_filename(
1131            self, resource_name
1132        )
1133
1134    def resource_stream(self, package_or_requirement, resource_name):
1135        """Return a readable file-like object for specified resource"""
1136        return get_provider(package_or_requirement).get_resource_stream(
1137            self, resource_name
1138        )
1139
1140    def resource_string(self, package_or_requirement, resource_name):
1141        """Return specified resource as a string"""
1142        return get_provider(package_or_requirement).get_resource_string(
1143            self, resource_name
1144        )
1145
1146    def resource_listdir(self, package_or_requirement, resource_name):
1147        """List the contents of the named resource directory"""
1148        return get_provider(package_or_requirement).resource_listdir(
1149            resource_name
1150        )
1151
1152    def extraction_error(self):
1153        """Give an error message for problems extracting file(s)"""
1154
1155        old_exc = sys.exc_info()[1]
1156        cache_path = self.extraction_path or get_default_cache()
1157
1158        tmpl = textwrap.dedent("""
1159            Can't extract file(s) to egg cache
1160
1161            The following error occurred while trying to extract file(s)
1162            to the Python egg cache:
1163
1164              {old_exc}
1165
1166            The Python egg cache directory is currently set to:
1167
1168              {cache_path}
1169
1170            Perhaps your account does not have write access to this directory?
1171            You can change the cache directory by setting the PYTHON_EGG_CACHE
1172            environment variable to point to an accessible directory.
1173            """).lstrip()
1174        err = ExtractionError(tmpl.format(**locals()))
1175        err.manager = self
1176        err.cache_path = cache_path
1177        err.original_error = old_exc
1178        raise err
1179
1180    def get_cache_path(self, archive_name, names=()):
1181        """Return absolute location in cache for `archive_name` and `names`
1182
1183        The parent directory of the resulting path will be created if it does
1184        not already exist.  `archive_name` should be the base filename of the
1185        enclosing egg (which may not be the name of the enclosing zipfile!),
1186        including its ".egg" extension.  `names`, if provided, should be a
1187        sequence of path name parts "under" the egg's extraction location.
1188
1189        This method should only be called by resource providers that need to
1190        obtain an extraction location, and only for names they intend to
1191        extract, as it tracks the generated names for possible cleanup later.
1192        """
1193        extract_path = self.extraction_path or get_default_cache()
1194        target_path = os.path.join(extract_path, archive_name + '-tmp', *names)
1195        try:
1196            _bypass_ensure_directory(target_path)
1197        except Exception:
1198            self.extraction_error()
1199
1200        self._warn_unsafe_extraction_path(extract_path)
1201
1202        self.cached_files[target_path] = 1
1203        return target_path
1204
1205    @staticmethod
1206    def _warn_unsafe_extraction_path(path):
1207        """
1208        If the default extraction path is overridden and set to an insecure
1209        location, such as /tmp, it opens up an opportunity for an attacker to
1210        replace an extracted file with an unauthorized payload. Warn the user
1211        if a known insecure location is used.
1212
1213        See Distribute #375 for more details.
1214        """
1215        if os.name == 'nt' and not path.startswith(os.environ['windir']):
1216            # On Windows, permissions are generally restrictive by default
1217            #  and temp directories are not writable by other users, so
1218            #  bypass the warning.
1219            return
1220        mode = os.stat(path).st_mode
1221        if mode & stat.S_IWOTH or mode & stat.S_IWGRP:
1222            msg = (
1223                "Extraction path is writable by group/others "
1224                "and vulnerable to attack when "
1225                "used with get_resource_filename ({path}). "
1226                "Consider a more secure "
1227                "location (set with .set_extraction_path or the "
1228                "PYTHON_EGG_CACHE environment variable)."
1229            ).format(**locals())
1230            warnings.warn(msg, UserWarning)
1231
1232    def postprocess(self, tempname, filename):
1233        """Perform any platform-specific postprocessing of `tempname`
1234
1235        This is where Mac header rewrites should be done; other platforms don't
1236        have anything special they should do.
1237
1238        Resource providers should call this method ONLY after successfully
1239        extracting a compressed resource.  They must NOT call it on resources
1240        that are already in the filesystem.
1241
1242        `tempname` is the current (temporary) name of the file, and `filename`
1243        is the name it will be renamed to by the caller after this routine
1244        returns.
1245        """
1246
1247        if os.name == 'posix':
1248            # Make the resource executable
1249            mode = ((os.stat(tempname).st_mode) | 0o555) & 0o7777
1250            os.chmod(tempname, mode)
1251
1252    def set_extraction_path(self, path):
1253        """Set the base path where resources will be extracted to, if needed.
1254
1255        If you do not call this routine before any extractions take place, the
1256        path defaults to the return value of ``get_default_cache()``.  (Which
1257        is based on the ``PYTHON_EGG_CACHE`` environment variable, with various
1258        platform-specific fallbacks.  See that routine's documentation for more
1259        details.)
1260
1261        Resources are extracted to subdirectories of this path based upon
1262        information given by the ``IResourceProvider``.  You may set this to a
1263        temporary directory, but then you must call ``cleanup_resources()`` to
1264        delete the extracted files when done.  There is no guarantee that
1265        ``cleanup_resources()`` will be able to remove all extracted files.
1266
1267        (Note: you may not change the extraction path for a given resource
1268        manager once resources have been extracted, unless you first call
1269        ``cleanup_resources()``.)
1270        """
1271        if self.cached_files:
1272            raise ValueError(
1273                "Can't change extraction path, files already extracted"
1274            )
1275
1276        self.extraction_path = path
1277
1278    def cleanup_resources(self, force=False):
1279        """
1280        Delete all extracted resource files and directories, returning a list
1281        of the file and directory names that could not be successfully removed.
1282        This function does not have any concurrency protection, so it should
1283        generally only be called when the extraction path is a temporary
1284        directory exclusive to a single process.  This method is not
1285        automatically called; you must call it explicitly or register it as an
1286        ``atexit`` function if you wish to ensure cleanup of a temporary
1287        directory used for extractions.
1288        """
1289        # XXX
1290
1291
1292def get_default_cache():
1293    """
1294    Return the ``PYTHON_EGG_CACHE`` environment variable
1295    or a platform-relevant user cache dir for an app
1296    named "Python-Eggs".
1297    """
1298    return (
1299        os.environ.get('PYTHON_EGG_CACHE')
1300        or appdirs.user_cache_dir(appname='Python-Eggs')
1301    )
1302
1303
1304def safe_name(name):
1305    """Convert an arbitrary string to a standard distribution name
1306
1307    Any runs of non-alphanumeric/. characters are replaced with a single '-'.
1308    """
1309    return re.sub('[^A-Za-z0-9.]+', '-', name)
1310
1311
1312def safe_version(version):
1313    """
1314    Convert an arbitrary string to a standard version string
1315    """
1316    try:
1317        # normalize the version
1318        return str(packaging.version.Version(version))
1319    except packaging.version.InvalidVersion:
1320        version = version.replace(' ', '.')
1321        return re.sub('[^A-Za-z0-9.]+', '-', version)
1322
1323
1324def safe_extra(extra):
1325    """Convert an arbitrary string to a standard 'extra' name
1326
1327    Any runs of non-alphanumeric characters are replaced with a single '_',
1328    and the result is always lowercased.
1329    """
1330    return re.sub('[^A-Za-z0-9.-]+', '_', extra).lower()
1331
1332
1333def to_filename(name):
1334    """Convert a project or version name to its filename-escaped form
1335
1336    Any '-' characters are currently replaced with '_'.
1337    """
1338    return name.replace('-', '_')
1339
1340
1341def invalid_marker(text):
1342    """
1343    Validate text as a PEP 508 environment marker; return an exception
1344    if invalid or False otherwise.
1345    """
1346    try:
1347        evaluate_marker(text)
1348    except SyntaxError as e:
1349        e.filename = None
1350        e.lineno = None
1351        return e
1352    return False
1353
1354
1355def evaluate_marker(text, extra=None):
1356    """
1357    Evaluate a PEP 508 environment marker.
1358    Return a boolean indicating the marker result in this environment.
1359    Raise SyntaxError if marker is invalid.
1360
1361    This implementation uses the 'pyparsing' module.
1362    """
1363    try:
1364        marker = packaging.markers.Marker(text)
1365        return marker.evaluate()
1366    except packaging.markers.InvalidMarker as e:
1367        raise SyntaxError(e) from e
1368
1369
1370class NullProvider:
1371    """Try to implement resources and metadata for arbitrary PEP 302 loaders"""
1372
1373    egg_name = None
1374    egg_info = None
1375    loader = None
1376
1377    def __init__(self, module):
1378        self.loader = getattr(module, '__loader__', None)
1379        self.module_path = os.path.dirname(getattr(module, '__file__', ''))
1380
1381    def get_resource_filename(self, manager, resource_name):
1382        return self._fn(self.module_path, resource_name)
1383
1384    def get_resource_stream(self, manager, resource_name):
1385        return io.BytesIO(self.get_resource_string(manager, resource_name))
1386
1387    def get_resource_string(self, manager, resource_name):
1388        return self._get(self._fn(self.module_path, resource_name))
1389
1390    def has_resource(self, resource_name):
1391        return self._has(self._fn(self.module_path, resource_name))
1392
1393    def _get_metadata_path(self, name):
1394        return self._fn(self.egg_info, name)
1395
1396    def has_metadata(self, name):
1397        if not self.egg_info:
1398            return self.egg_info
1399
1400        path = self._get_metadata_path(name)
1401        return self._has(path)
1402
1403    def get_metadata(self, name):
1404        if not self.egg_info:
1405            return ""
1406        path = self._get_metadata_path(name)
1407        value = self._get(path)
1408        try:
1409            return value.decode('utf-8')
1410        except UnicodeDecodeError as exc:
1411            # Include the path in the error message to simplify
1412            # troubleshooting, and without changing the exception type.
1413            exc.reason += ' in {} file at path: {}'.format(name, path)
1414            raise
1415
1416    def get_metadata_lines(self, name):
1417        return yield_lines(self.get_metadata(name))
1418
1419    def resource_isdir(self, resource_name):
1420        return self._isdir(self._fn(self.module_path, resource_name))
1421
1422    def metadata_isdir(self, name):
1423        return self.egg_info and self._isdir(self._fn(self.egg_info, name))
1424
1425    def resource_listdir(self, resource_name):
1426        return self._listdir(self._fn(self.module_path, resource_name))
1427
1428    def metadata_listdir(self, name):
1429        if self.egg_info:
1430            return self._listdir(self._fn(self.egg_info, name))
1431        return []
1432
1433    def run_script(self, script_name, namespace):
1434        script = 'scripts/' + script_name
1435        if not self.has_metadata(script):
1436            raise ResolutionError(
1437                "Script {script!r} not found in metadata at {self.egg_info!r}"
1438                .format(**locals()),
1439            )
1440        script_text = self.get_metadata(script).replace('\r\n', '\n')
1441        script_text = script_text.replace('\r', '\n')
1442        script_filename = self._fn(self.egg_info, script)
1443        namespace['__file__'] = script_filename
1444        if os.path.exists(script_filename):
1445            with open(script_filename) as fid:
1446                source = fid.read()
1447            code = compile(source, script_filename, 'exec')
1448            exec(code, namespace, namespace)
1449        else:
1450            from linecache import cache
1451            cache[script_filename] = (
1452                len(script_text), 0, script_text.split('\n'), script_filename
1453            )
1454            script_code = compile(script_text, script_filename, 'exec')
1455            exec(script_code, namespace, namespace)
1456
1457    def _has(self, path):
1458        raise NotImplementedError(
1459            "Can't perform this operation for unregistered loader type"
1460        )
1461
1462    def _isdir(self, path):
1463        raise NotImplementedError(
1464            "Can't perform this operation for unregistered loader type"
1465        )
1466
1467    def _listdir(self, path):
1468        raise NotImplementedError(
1469            "Can't perform this operation for unregistered loader type"
1470        )
1471
1472    def _fn(self, base, resource_name):
1473        self._validate_resource_path(resource_name)
1474        if resource_name:
1475            return os.path.join(base, *resource_name.split('/'))
1476        return base
1477
1478    @staticmethod
1479    def _validate_resource_path(path):
1480        """
1481        Validate the resource paths according to the docs.
1482        https://setuptools.readthedocs.io/en/latest/pkg_resources.html#basic-resource-access
1483
1484        >>> warned = getfixture('recwarn')
1485        >>> warnings.simplefilter('always')
1486        >>> vrp = NullProvider._validate_resource_path
1487        >>> vrp('foo/bar.txt')
1488        >>> bool(warned)
1489        False
1490        >>> vrp('../foo/bar.txt')
1491        >>> bool(warned)
1492        True
1493        >>> warned.clear()
1494        >>> vrp('/foo/bar.txt')
1495        >>> bool(warned)
1496        True
1497        >>> vrp('foo/../../bar.txt')
1498        >>> bool(warned)
1499        True
1500        >>> warned.clear()
1501        >>> vrp('foo/f../bar.txt')
1502        >>> bool(warned)
1503        False
1504
1505        Windows path separators are straight-up disallowed.
1506        >>> vrp(r'\\foo/bar.txt')
1507        Traceback (most recent call last):
1508        ...
1509        ValueError: Use of .. or absolute path in a resource path \
1510is not allowed.
1511
1512        >>> vrp(r'C:\\foo/bar.txt')
1513        Traceback (most recent call last):
1514        ...
1515        ValueError: Use of .. or absolute path in a resource path \
1516is not allowed.
1517
1518        Blank values are allowed
1519
1520        >>> vrp('')
1521        >>> bool(warned)
1522        False
1523
1524        Non-string values are not.
1525
1526        >>> vrp(None)
1527        Traceback (most recent call last):
1528        ...
1529        AttributeError: ...
1530        """
1531        invalid = (
1532            os.path.pardir in path.split(posixpath.sep) or
1533            posixpath.isabs(path) or
1534            ntpath.isabs(path)
1535        )
1536        if not invalid:
1537            return
1538
1539        msg = "Use of .. or absolute path in a resource path is not allowed."
1540
1541        # Aggressively disallow Windows absolute paths
1542        if ntpath.isabs(path) and not posixpath.isabs(path):
1543            raise ValueError(msg)
1544
1545        # for compatibility, warn; in future
1546        # raise ValueError(msg)
1547        warnings.warn(
1548            msg[:-1] + " and will raise exceptions in a future release.",
1549            DeprecationWarning,
1550            stacklevel=4,
1551        )
1552
1553    def _get(self, path):
1554        if hasattr(self.loader, 'get_data'):
1555            return self.loader.get_data(path)
1556        raise NotImplementedError(
1557            "Can't perform this operation for loaders without 'get_data()'"
1558        )
1559
1560
1561register_loader_type(object, NullProvider)
1562
1563
1564def _parents(path):
1565    """
1566    yield all parents of path including path
1567    """
1568    last = None
1569    while path != last:
1570        yield path
1571        last = path
1572        path, _ = os.path.split(path)
1573
1574
1575class EggProvider(NullProvider):
1576    """Provider based on a virtual filesystem"""
1577
1578    def __init__(self, module):
1579        NullProvider.__init__(self, module)
1580        self._setup_prefix()
1581
1582    def _setup_prefix(self):
1583        # Assume that metadata may be nested inside a "basket"
1584        # of multiple eggs and use module_path instead of .archive.
1585        eggs = filter(_is_egg_path, _parents(self.module_path))
1586        egg = next(eggs, None)
1587        egg and self._set_egg(egg)
1588
1589    def _set_egg(self, path):
1590        self.egg_name = os.path.basename(path)
1591        self.egg_info = os.path.join(path, 'EGG-INFO')
1592        self.egg_root = path
1593
1594
1595class DefaultProvider(EggProvider):
1596    """Provides access to package resources in the filesystem"""
1597
1598    def _has(self, path):
1599        return os.path.exists(path)
1600
1601    def _isdir(self, path):
1602        return os.path.isdir(path)
1603
1604    def _listdir(self, path):
1605        return os.listdir(path)
1606
1607    def get_resource_stream(self, manager, resource_name):
1608        return open(self._fn(self.module_path, resource_name), 'rb')
1609
1610    def _get(self, path):
1611        with open(path, 'rb') as stream:
1612            return stream.read()
1613
1614    @classmethod
1615    def _register(cls):
1616        loader_names = 'SourceFileLoader', 'SourcelessFileLoader',
1617        for name in loader_names:
1618            loader_cls = getattr(importlib_machinery, name, type(None))
1619            register_loader_type(loader_cls, cls)
1620
1621
1622DefaultProvider._register()
1623
1624
1625class EmptyProvider(NullProvider):
1626    """Provider that returns nothing for all requests"""
1627
1628    module_path = None
1629
1630    _isdir = _has = lambda self, path: False
1631
1632    def _get(self, path):
1633        return ''
1634
1635    def _listdir(self, path):
1636        return []
1637
1638    def __init__(self):
1639        pass
1640
1641
1642empty_provider = EmptyProvider()
1643
1644
1645class ZipManifests(dict):
1646    """
1647    zip manifest builder
1648    """
1649
1650    @classmethod
1651    def build(cls, path):
1652        """
1653        Build a dictionary similar to the zipimport directory
1654        caches, except instead of tuples, store ZipInfo objects.
1655
1656        Use a platform-specific path separator (os.sep) for the path keys
1657        for compatibility with pypy on Windows.
1658        """
1659        with zipfile.ZipFile(path) as zfile:
1660            items = (
1661                (
1662                    name.replace('/', os.sep),
1663                    zfile.getinfo(name),
1664                )
1665                for name in zfile.namelist()
1666            )
1667            return dict(items)
1668
1669    load = build
1670
1671
1672class MemoizedZipManifests(ZipManifests):
1673    """
1674    Memoized zipfile manifests.
1675    """
1676    manifest_mod = collections.namedtuple('manifest_mod', 'manifest mtime')
1677
1678    def load(self, path):
1679        """
1680        Load a manifest at path or return a suitable manifest already loaded.
1681        """
1682        path = os.path.normpath(path)
1683        mtime = os.stat(path).st_mtime
1684
1685        if path not in self or self[path].mtime != mtime:
1686            manifest = self.build(path)
1687            self[path] = self.manifest_mod(manifest, mtime)
1688
1689        return self[path].manifest
1690
1691
1692class ZipProvider(EggProvider):
1693    """Resource support for zips and eggs"""
1694
1695    eagers = None
1696    _zip_manifests = MemoizedZipManifests()
1697
1698    def __init__(self, module):
1699        EggProvider.__init__(self, module)
1700        self.zip_pre = self.loader.archive + os.sep
1701
1702    def _zipinfo_name(self, fspath):
1703        # Convert a virtual filename (full path to file) into a zipfile subpath
1704        # usable with the zipimport directory cache for our target archive
1705        fspath = fspath.rstrip(os.sep)
1706        if fspath == self.loader.archive:
1707            return ''
1708        if fspath.startswith(self.zip_pre):
1709            return fspath[len(self.zip_pre):]
1710        raise AssertionError(
1711            "%s is not a subpath of %s" % (fspath, self.zip_pre)
1712        )
1713
1714    def _parts(self, zip_path):
1715        # Convert a zipfile subpath into an egg-relative path part list.
1716        # pseudo-fs path
1717        fspath = self.zip_pre + zip_path
1718        if fspath.startswith(self.egg_root + os.sep):
1719            return fspath[len(self.egg_root) + 1:].split(os.sep)
1720        raise AssertionError(
1721            "%s is not a subpath of %s" % (fspath, self.egg_root)
1722        )
1723
1724    @property
1725    def zipinfo(self):
1726        return self._zip_manifests.load(self.loader.archive)
1727
1728    def get_resource_filename(self, manager, resource_name):
1729        if not self.egg_name:
1730            raise NotImplementedError(
1731                "resource_filename() only supported for .egg, not .zip"
1732            )
1733        # no need to lock for extraction, since we use temp names
1734        zip_path = self._resource_to_zip(resource_name)
1735        eagers = self._get_eager_resources()
1736        if '/'.join(self._parts(zip_path)) in eagers:
1737            for name in eagers:
1738                self._extract_resource(manager, self._eager_to_zip(name))
1739        return self._extract_resource(manager, zip_path)
1740
1741    @staticmethod
1742    def _get_date_and_size(zip_stat):
1743        size = zip_stat.file_size
1744        # ymdhms+wday, yday, dst
1745        date_time = zip_stat.date_time + (0, 0, -1)
1746        # 1980 offset already done
1747        timestamp = time.mktime(date_time)
1748        return timestamp, size
1749
1750    # FIXME: 'ZipProvider._extract_resource' is too complex (12)
1751    def _extract_resource(self, manager, zip_path):  # noqa: C901
1752
1753        if zip_path in self._index():
1754            for name in self._index()[zip_path]:
1755                last = self._extract_resource(
1756                    manager, os.path.join(zip_path, name)
1757                )
1758            # return the extracted directory name
1759            return os.path.dirname(last)
1760
1761        timestamp, size = self._get_date_and_size(self.zipinfo[zip_path])
1762
1763        if not WRITE_SUPPORT:
1764            raise IOError('"os.rename" and "os.unlink" are not supported '
1765                          'on this platform')
1766        try:
1767
1768            real_path = manager.get_cache_path(
1769                self.egg_name, self._parts(zip_path)
1770            )
1771
1772            if self._is_current(real_path, zip_path):
1773                return real_path
1774
1775            outf, tmpnam = _mkstemp(
1776                ".$extract",
1777                dir=os.path.dirname(real_path),
1778            )
1779            os.write(outf, self.loader.get_data(zip_path))
1780            os.close(outf)
1781            utime(tmpnam, (timestamp, timestamp))
1782            manager.postprocess(tmpnam, real_path)
1783
1784            try:
1785                rename(tmpnam, real_path)
1786
1787            except os.error:
1788                if os.path.isfile(real_path):
1789                    if self._is_current(real_path, zip_path):
1790                        # the file became current since it was checked above,
1791                        #  so proceed.
1792                        return real_path
1793                    # Windows, del old file and retry
1794                    elif os.name == 'nt':
1795                        unlink(real_path)
1796                        rename(tmpnam, real_path)
1797                        return real_path
1798                raise
1799
1800        except os.error:
1801            # report a user-friendly error
1802            manager.extraction_error()
1803
1804        return real_path
1805
1806    def _is_current(self, file_path, zip_path):
1807        """
1808        Return True if the file_path is current for this zip_path
1809        """
1810        timestamp, size = self._get_date_and_size(self.zipinfo[zip_path])
1811        if not os.path.isfile(file_path):
1812            return False
1813        stat = os.stat(file_path)
1814        if stat.st_size != size or stat.st_mtime != timestamp:
1815            return False
1816        # check that the contents match
1817        zip_contents = self.loader.get_data(zip_path)
1818        with open(file_path, 'rb') as f:
1819            file_contents = f.read()
1820        return zip_contents == file_contents
1821
1822    def _get_eager_resources(self):
1823        if self.eagers is None:
1824            eagers = []
1825            for name in ('native_libs.txt', 'eager_resources.txt'):
1826                if self.has_metadata(name):
1827                    eagers.extend(self.get_metadata_lines(name))
1828            self.eagers = eagers
1829        return self.eagers
1830
1831    def _index(self):
1832        try:
1833            return self._dirindex
1834        except AttributeError:
1835            ind = {}
1836            for path in self.zipinfo:
1837                parts = path.split(os.sep)
1838                while parts:
1839                    parent = os.sep.join(parts[:-1])
1840                    if parent in ind:
1841                        ind[parent].append(parts[-1])
1842                        break
1843                    else:
1844                        ind[parent] = [parts.pop()]
1845            self._dirindex = ind
1846            return ind
1847
1848    def _has(self, fspath):
1849        zip_path = self._zipinfo_name(fspath)
1850        return zip_path in self.zipinfo or zip_path in self._index()
1851
1852    def _isdir(self, fspath):
1853        return self._zipinfo_name(fspath) in self._index()
1854
1855    def _listdir(self, fspath):
1856        return list(self._index().get(self._zipinfo_name(fspath), ()))
1857
1858    def _eager_to_zip(self, resource_name):
1859        return self._zipinfo_name(self._fn(self.egg_root, resource_name))
1860
1861    def _resource_to_zip(self, resource_name):
1862        return self._zipinfo_name(self._fn(self.module_path, resource_name))
1863
1864
1865register_loader_type(zipimport.zipimporter, ZipProvider)
1866
1867
1868class FileMetadata(EmptyProvider):
1869    """Metadata handler for standalone PKG-INFO files
1870
1871    Usage::
1872
1873        metadata = FileMetadata("/path/to/PKG-INFO")
1874
1875    This provider rejects all data and metadata requests except for PKG-INFO,
1876    which is treated as existing, and will be the contents of the file at
1877    the provided location.
1878    """
1879
1880    def __init__(self, path):
1881        self.path = path
1882
1883    def _get_metadata_path(self, name):
1884        return self.path
1885
1886    def has_metadata(self, name):
1887        return name == 'PKG-INFO' and os.path.isfile(self.path)
1888
1889    def get_metadata(self, name):
1890        if name != 'PKG-INFO':
1891            raise KeyError("No metadata except PKG-INFO is available")
1892
1893        with io.open(self.path, encoding='utf-8', errors="replace") as f:
1894            metadata = f.read()
1895        self._warn_on_replacement(metadata)
1896        return metadata
1897
1898    def _warn_on_replacement(self, metadata):
1899        replacement_char = '�'
1900        if replacement_char in metadata:
1901            tmpl = "{self.path} could not be properly decoded in UTF-8"
1902            msg = tmpl.format(**locals())
1903            warnings.warn(msg)
1904
1905    def get_metadata_lines(self, name):
1906        return yield_lines(self.get_metadata(name))
1907
1908
1909class PathMetadata(DefaultProvider):
1910    """Metadata provider for egg directories
1911
1912    Usage::
1913
1914        # Development eggs:
1915
1916        egg_info = "/path/to/PackageName.egg-info"
1917        base_dir = os.path.dirname(egg_info)
1918        metadata = PathMetadata(base_dir, egg_info)
1919        dist_name = os.path.splitext(os.path.basename(egg_info))[0]
1920        dist = Distribution(basedir, project_name=dist_name, metadata=metadata)
1921
1922        # Unpacked egg directories:
1923
1924        egg_path = "/path/to/PackageName-ver-pyver-etc.egg"
1925        metadata = PathMetadata(egg_path, os.path.join(egg_path,'EGG-INFO'))
1926        dist = Distribution.from_filename(egg_path, metadata=metadata)
1927    """
1928
1929    def __init__(self, path, egg_info):
1930        self.module_path = path
1931        self.egg_info = egg_info
1932
1933
1934class EggMetadata(ZipProvider):
1935    """Metadata provider for .egg files"""
1936
1937    def __init__(self, importer):
1938        """Create a metadata provider from a zipimporter"""
1939
1940        self.zip_pre = importer.archive + os.sep
1941        self.loader = importer
1942        if importer.prefix:
1943            self.module_path = os.path.join(importer.archive, importer.prefix)
1944        else:
1945            self.module_path = importer.archive
1946        self._setup_prefix()
1947
1948
1949_declare_state('dict', _distribution_finders={})
1950
1951
1952def register_finder(importer_type, distribution_finder):
1953    """Register `distribution_finder` to find distributions in sys.path items
1954
1955    `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item
1956    handler), and `distribution_finder` is a callable that, passed a path
1957    item and the importer instance, yields ``Distribution`` instances found on
1958    that path item.  See ``pkg_resources.find_on_path`` for an example."""
1959    _distribution_finders[importer_type] = distribution_finder
1960
1961
1962def find_distributions(path_item, only=False):
1963    """Yield distributions accessible via `path_item`"""
1964    importer = get_importer(path_item)
1965    finder = _find_adapter(_distribution_finders, importer)
1966    return finder(importer, path_item, only)
1967
1968
1969def find_eggs_in_zip(importer, path_item, only=False):
1970    """
1971    Find eggs in zip files; possibly multiple nested eggs.
1972    """
1973    if importer.archive.endswith('.whl'):
1974        # wheels are not supported with this finder
1975        # they don't have PKG-INFO metadata, and won't ever contain eggs
1976        return
1977    metadata = EggMetadata(importer)
1978    if metadata.has_metadata('PKG-INFO'):
1979        yield Distribution.from_filename(path_item, metadata=metadata)
1980    if only:
1981        # don't yield nested distros
1982        return
1983    for subitem in metadata.resource_listdir(''):
1984        if _is_egg_path(subitem):
1985            subpath = os.path.join(path_item, subitem)
1986            dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath)
1987            for dist in dists:
1988                yield dist
1989        elif subitem.lower().endswith(('.dist-info', '.egg-info')):
1990            subpath = os.path.join(path_item, subitem)
1991            submeta = EggMetadata(zipimport.zipimporter(subpath))
1992            submeta.egg_info = subpath
1993            yield Distribution.from_location(path_item, subitem, submeta)
1994
1995
1996register_finder(zipimport.zipimporter, find_eggs_in_zip)
1997
1998
1999def find_nothing(importer, path_item, only=False):
2000    return ()
2001
2002
2003register_finder(object, find_nothing)
2004
2005
2006def _by_version_descending(names):
2007    """
2008    Given a list of filenames, return them in descending order
2009    by version number.
2010
2011    >>> names = 'bar', 'foo', 'Python-2.7.10.egg', 'Python-2.7.2.egg'
2012    >>> _by_version_descending(names)
2013    ['Python-2.7.10.egg', 'Python-2.7.2.egg', 'foo', 'bar']
2014    >>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.egg'
2015    >>> _by_version_descending(names)
2016    ['Setuptools-1.2.3.egg', 'Setuptools-1.2.3b1.egg']
2017    >>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.post1.egg'
2018    >>> _by_version_descending(names)
2019    ['Setuptools-1.2.3.post1.egg', 'Setuptools-1.2.3b1.egg']
2020    """
2021    def _by_version(name):
2022        """
2023        Parse each component of the filename
2024        """
2025        name, ext = os.path.splitext(name)
2026        parts = itertools.chain(name.split('-'), [ext])
2027        return [packaging.version.parse(part) for part in parts]
2028
2029    return sorted(names, key=_by_version, reverse=True)
2030
2031
2032def find_on_path(importer, path_item, only=False):
2033    """Yield distributions accessible on a sys.path directory"""
2034    path_item = _normalize_cached(path_item)
2035
2036    if _is_unpacked_egg(path_item):
2037        yield Distribution.from_filename(
2038            path_item, metadata=PathMetadata(
2039                path_item, os.path.join(path_item, 'EGG-INFO')
2040            )
2041        )
2042        return
2043
2044    entries = (
2045        os.path.join(path_item, child)
2046        for child in safe_listdir(path_item)
2047    )
2048
2049    # for performance, before sorting by version,
2050    # screen entries for only those that will yield
2051    # distributions
2052    filtered = (
2053        entry
2054        for entry in entries
2055        if dist_factory(path_item, entry, only)
2056    )
2057
2058    # scan for .egg and .egg-info in directory
2059    path_item_entries = _by_version_descending(filtered)
2060    for entry in path_item_entries:
2061        fullpath = os.path.join(path_item, entry)
2062        factory = dist_factory(path_item, entry, only)
2063        for dist in factory(fullpath):
2064            yield dist
2065
2066
2067def dist_factory(path_item, entry, only):
2068    """Return a dist_factory for the given entry."""
2069    lower = entry.lower()
2070    is_egg_info = lower.endswith('.egg-info')
2071    is_dist_info = (
2072        lower.endswith('.dist-info') and
2073        os.path.isdir(os.path.join(path_item, entry))
2074    )
2075    is_meta = is_egg_info or is_dist_info
2076    return (
2077        distributions_from_metadata
2078        if is_meta else
2079        find_distributions
2080        if not only and _is_egg_path(entry) else
2081        resolve_egg_link
2082        if not only and lower.endswith('.egg-link') else
2083        NoDists()
2084    )
2085
2086
2087class NoDists:
2088    """
2089    >>> bool(NoDists())
2090    False
2091
2092    >>> list(NoDists()('anything'))
2093    []
2094    """
2095    def __bool__(self):
2096        return False
2097
2098    def __call__(self, fullpath):
2099        return iter(())
2100
2101
2102def safe_listdir(path):
2103    """
2104    Attempt to list contents of path, but suppress some exceptions.
2105    """
2106    try:
2107        return os.listdir(path)
2108    except (PermissionError, NotADirectoryError):
2109        pass
2110    except OSError as e:
2111        # Ignore the directory if does not exist, not a directory or
2112        # permission denied
2113        if e.errno not in (errno.ENOTDIR, errno.EACCES, errno.ENOENT):
2114            raise
2115    return ()
2116
2117
2118def distributions_from_metadata(path):
2119    root = os.path.dirname(path)
2120    if os.path.isdir(path):
2121        if len(os.listdir(path)) == 0:
2122            # empty metadata dir; skip
2123            return
2124        metadata = PathMetadata(root, path)
2125    else:
2126        metadata = FileMetadata(path)
2127    entry = os.path.basename(path)
2128    yield Distribution.from_location(
2129        root, entry, metadata, precedence=DEVELOP_DIST,
2130    )
2131
2132
2133def non_empty_lines(path):
2134    """
2135    Yield non-empty lines from file at path
2136    """
2137    with open(path) as f:
2138        for line in f:
2139            line = line.strip()
2140            if line:
2141                yield line
2142
2143
2144def resolve_egg_link(path):
2145    """
2146    Given a path to an .egg-link, resolve distributions
2147    present in the referenced path.
2148    """
2149    referenced_paths = non_empty_lines(path)
2150    resolved_paths = (
2151        os.path.join(os.path.dirname(path), ref)
2152        for ref in referenced_paths
2153    )
2154    dist_groups = map(find_distributions, resolved_paths)
2155    return next(dist_groups, ())
2156
2157
2158register_finder(pkgutil.ImpImporter, find_on_path)
2159
2160if hasattr(importlib_machinery, 'FileFinder'):
2161    register_finder(importlib_machinery.FileFinder, find_on_path)
2162
2163_declare_state('dict', _namespace_handlers={})
2164_declare_state('dict', _namespace_packages={})
2165
2166
2167def register_namespace_handler(importer_type, namespace_handler):
2168    """Register `namespace_handler` to declare namespace packages
2169
2170    `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item
2171    handler), and `namespace_handler` is a callable like this::
2172
2173        def namespace_handler(importer, path_entry, moduleName, module):
2174            # return a path_entry to use for child packages
2175
2176    Namespace handlers are only called if the importer object has already
2177    agreed that it can handle the relevant path item, and they should only
2178    return a subpath if the module __path__ does not already contain an
2179    equivalent subpath.  For an example namespace handler, see
2180    ``pkg_resources.file_ns_handler``.
2181    """
2182    _namespace_handlers[importer_type] = namespace_handler
2183
2184
2185def _handle_ns(packageName, path_item):
2186    """Ensure that named package includes a subpath of path_item (if needed)"""
2187
2188    importer = get_importer(path_item)
2189    if importer is None:
2190        return None
2191
2192    # use find_spec (PEP 451) and fall-back to find_module (PEP 302)
2193    try:
2194        loader = importer.find_spec(packageName).loader
2195    except AttributeError:
2196        # capture warnings due to #1111
2197        with warnings.catch_warnings():
2198            warnings.simplefilter("ignore")
2199            loader = importer.find_module(packageName)
2200
2201    if loader is None:
2202        return None
2203    module = sys.modules.get(packageName)
2204    if module is None:
2205        module = sys.modules[packageName] = types.ModuleType(packageName)
2206        module.__path__ = []
2207        _set_parent_ns(packageName)
2208    elif not hasattr(module, '__path__'):
2209        raise TypeError("Not a package:", packageName)
2210    handler = _find_adapter(_namespace_handlers, importer)
2211    subpath = handler(importer, path_item, packageName, module)
2212    if subpath is not None:
2213        path = module.__path__
2214        path.append(subpath)
2215        importlib.import_module(packageName)
2216        _rebuild_mod_path(path, packageName, module)
2217    return subpath
2218
2219
2220def _rebuild_mod_path(orig_path, package_name, module):
2221    """
2222    Rebuild module.__path__ ensuring that all entries are ordered
2223    corresponding to their sys.path order
2224    """
2225    sys_path = [_normalize_cached(p) for p in sys.path]
2226
2227    def safe_sys_path_index(entry):
2228        """
2229        Workaround for #520 and #513.
2230        """
2231        try:
2232            return sys_path.index(entry)
2233        except ValueError:
2234            return float('inf')
2235
2236    def position_in_sys_path(path):
2237        """
2238        Return the ordinal of the path based on its position in sys.path
2239        """
2240        path_parts = path.split(os.sep)
2241        module_parts = package_name.count('.') + 1
2242        parts = path_parts[:-module_parts]
2243        return safe_sys_path_index(_normalize_cached(os.sep.join(parts)))
2244
2245    new_path = sorted(orig_path, key=position_in_sys_path)
2246    new_path = [_normalize_cached(p) for p in new_path]
2247
2248    if isinstance(module.__path__, list):
2249        module.__path__[:] = new_path
2250    else:
2251        module.__path__ = new_path
2252
2253
2254def declare_namespace(packageName):
2255    """Declare that package 'packageName' is a namespace package"""
2256
2257    _imp.acquire_lock()
2258    try:
2259        if packageName in _namespace_packages:
2260            return
2261
2262        path = sys.path
2263        parent, _, _ = packageName.rpartition('.')
2264
2265        if parent:
2266            declare_namespace(parent)
2267            if parent not in _namespace_packages:
2268                __import__(parent)
2269            try:
2270                path = sys.modules[parent].__path__
2271            except AttributeError as e:
2272                raise TypeError("Not a package:", parent) from e
2273
2274        # Track what packages are namespaces, so when new path items are added,
2275        # they can be updated
2276        _namespace_packages.setdefault(parent or None, []).append(packageName)
2277        _namespace_packages.setdefault(packageName, [])
2278
2279        for path_item in path:
2280            # Ensure all the parent's path items are reflected in the child,
2281            # if they apply
2282            _handle_ns(packageName, path_item)
2283
2284    finally:
2285        _imp.release_lock()
2286
2287
2288def fixup_namespace_packages(path_item, parent=None):
2289    """Ensure that previously-declared namespace packages include path_item"""
2290    _imp.acquire_lock()
2291    try:
2292        for package in _namespace_packages.get(parent, ()):
2293            subpath = _handle_ns(package, path_item)
2294            if subpath:
2295                fixup_namespace_packages(subpath, package)
2296    finally:
2297        _imp.release_lock()
2298
2299
2300def file_ns_handler(importer, path_item, packageName, module):
2301    """Compute an ns-package subpath for a filesystem or zipfile importer"""
2302
2303    subpath = os.path.join(path_item, packageName.split('.')[-1])
2304    normalized = _normalize_cached(subpath)
2305    for item in module.__path__:
2306        if _normalize_cached(item) == normalized:
2307            break
2308    else:
2309        # Only return the path if it's not already there
2310        return subpath
2311
2312
2313register_namespace_handler(pkgutil.ImpImporter, file_ns_handler)
2314register_namespace_handler(zipimport.zipimporter, file_ns_handler)
2315
2316if hasattr(importlib_machinery, 'FileFinder'):
2317    register_namespace_handler(importlib_machinery.FileFinder, file_ns_handler)
2318
2319
2320def null_ns_handler(importer, path_item, packageName, module):
2321    return None
2322
2323
2324register_namespace_handler(object, null_ns_handler)
2325
2326
2327def normalize_path(filename):
2328    """Normalize a file/dir name for comparison purposes"""
2329    return os.path.normcase(os.path.realpath(os.path.normpath(
2330        _cygwin_patch(filename))))
2331
2332
2333def _cygwin_patch(filename):  # pragma: nocover
2334    """
2335    Contrary to POSIX 2008, on Cygwin, getcwd (3) contains
2336    symlink components. Using
2337    os.path.abspath() works around this limitation. A fix in os.getcwd()
2338    would probably better, in Cygwin even more so, except
2339    that this seems to be by design...
2340    """
2341    return os.path.abspath(filename) if sys.platform == 'cygwin' else filename
2342
2343
2344def _normalize_cached(filename, _cache={}):
2345    try:
2346        return _cache[filename]
2347    except KeyError:
2348        _cache[filename] = result = normalize_path(filename)
2349        return result
2350
2351
2352def _is_egg_path(path):
2353    """
2354    Determine if given path appears to be an egg.
2355    """
2356    return _is_zip_egg(path) or _is_unpacked_egg(path)
2357
2358
2359def _is_zip_egg(path):
2360    return (
2361        path.lower().endswith('.egg') and
2362        os.path.isfile(path) and
2363        zipfile.is_zipfile(path)
2364    )
2365
2366
2367def _is_unpacked_egg(path):
2368    """
2369    Determine if given path appears to be an unpacked egg.
2370    """
2371    return (
2372        path.lower().endswith('.egg') and
2373        os.path.isfile(os.path.join(path, 'EGG-INFO', 'PKG-INFO'))
2374    )
2375
2376
2377def _set_parent_ns(packageName):
2378    parts = packageName.split('.')
2379    name = parts.pop()
2380    if parts:
2381        parent = '.'.join(parts)
2382        setattr(sys.modules[parent], name, sys.modules[packageName])
2383
2384
2385def yield_lines(strs):
2386    """Yield non-empty/non-comment lines of a string or sequence"""
2387    if isinstance(strs, str):
2388        for s in strs.splitlines():
2389            s = s.strip()
2390            # skip blank lines/comments
2391            if s and not s.startswith('#'):
2392                yield s
2393    else:
2394        for ss in strs:
2395            for s in yield_lines(ss):
2396                yield s
2397
2398
2399MODULE = re.compile(r"\w+(\.\w+)*$").match
2400EGG_NAME = re.compile(
2401    r"""
2402    (?P<name>[^-]+) (
2403        -(?P<ver>[^-]+) (
2404            -py(?P<pyver>[^-]+) (
2405                -(?P<plat>.+)
2406            )?
2407        )?
2408    )?
2409    """,
2410    re.VERBOSE | re.IGNORECASE,
2411).match
2412
2413
2414class EntryPoint:
2415    """Object representing an advertised importable object"""
2416
2417    def __init__(self, name, module_name, attrs=(), extras=(), dist=None):
2418        if not MODULE(module_name):
2419            raise ValueError("Invalid module name", module_name)
2420        self.name = name
2421        self.module_name = module_name
2422        self.attrs = tuple(attrs)
2423        self.extras = tuple(extras)
2424        self.dist = dist
2425
2426    def __str__(self):
2427        s = "%s = %s" % (self.name, self.module_name)
2428        if self.attrs:
2429            s += ':' + '.'.join(self.attrs)
2430        if self.extras:
2431            s += ' [%s]' % ','.join(self.extras)
2432        return s
2433
2434    def __repr__(self):
2435        return "EntryPoint.parse(%r)" % str(self)
2436
2437    def load(self, require=True, *args, **kwargs):
2438        """
2439        Require packages for this EntryPoint, then resolve it.
2440        """
2441        if not require or args or kwargs:
2442            warnings.warn(
2443                "Parameters to load are deprecated.  Call .resolve and "
2444                ".require separately.",
2445                PkgResourcesDeprecationWarning,
2446                stacklevel=2,
2447            )
2448        if require:
2449            self.require(*args, **kwargs)
2450        return self.resolve()
2451
2452    def resolve(self):
2453        """
2454        Resolve the entry point from its module and attrs.
2455        """
2456        module = __import__(self.module_name, fromlist=['__name__'], level=0)
2457        try:
2458            return functools.reduce(getattr, self.attrs, module)
2459        except AttributeError as exc:
2460            raise ImportError(str(exc)) from exc
2461
2462    def require(self, env=None, installer=None):
2463        if self.extras and not self.dist:
2464            raise UnknownExtra("Can't require() without a distribution", self)
2465
2466        # Get the requirements for this entry point with all its extras and
2467        # then resolve them. We have to pass `extras` along when resolving so
2468        # that the working set knows what extras we want. Otherwise, for
2469        # dist-info distributions, the working set will assume that the
2470        # requirements for that extra are purely optional and skip over them.
2471        reqs = self.dist.requires(self.extras)
2472        items = working_set.resolve(reqs, env, installer, extras=self.extras)
2473        list(map(working_set.add, items))
2474
2475    pattern = re.compile(
2476        r'\s*'
2477        r'(?P<name>.+?)\s*'
2478        r'=\s*'
2479        r'(?P<module>[\w.]+)\s*'
2480        r'(:\s*(?P<attr>[\w.]+))?\s*'
2481        r'(?P<extras>\[.*\])?\s*$'
2482    )
2483
2484    @classmethod
2485    def parse(cls, src, dist=None):
2486        """Parse a single entry point from string `src`
2487
2488        Entry point syntax follows the form::
2489
2490            name = some.module:some.attr [extra1, extra2]
2491
2492        The entry name and module name are required, but the ``:attrs`` and
2493        ``[extras]`` parts are optional
2494        """
2495        m = cls.pattern.match(src)
2496        if not m:
2497            msg = "EntryPoint must be in 'name=module:attrs [extras]' format"
2498            raise ValueError(msg, src)
2499        res = m.groupdict()
2500        extras = cls._parse_extras(res['extras'])
2501        attrs = res['attr'].split('.') if res['attr'] else ()
2502        return cls(res['name'], res['module'], attrs, extras, dist)
2503
2504    @classmethod
2505    def _parse_extras(cls, extras_spec):
2506        if not extras_spec:
2507            return ()
2508        req = Requirement.parse('x' + extras_spec)
2509        if req.specs:
2510            raise ValueError()
2511        return req.extras
2512
2513    @classmethod
2514    def parse_group(cls, group, lines, dist=None):
2515        """Parse an entry point group"""
2516        if not MODULE(group):
2517            raise ValueError("Invalid group name", group)
2518        this = {}
2519        for line in yield_lines(lines):
2520            ep = cls.parse(line, dist)
2521            if ep.name in this:
2522                raise ValueError("Duplicate entry point", group, ep.name)
2523            this[ep.name] = ep
2524        return this
2525
2526    @classmethod
2527    def parse_map(cls, data, dist=None):
2528        """Parse a map of entry point groups"""
2529        if isinstance(data, dict):
2530            data = data.items()
2531        else:
2532            data = split_sections(data)
2533        maps = {}
2534        for group, lines in data:
2535            if group is None:
2536                if not lines:
2537                    continue
2538                raise ValueError("Entry points must be listed in groups")
2539            group = group.strip()
2540            if group in maps:
2541                raise ValueError("Duplicate group name", group)
2542            maps[group] = cls.parse_group(group, lines, dist)
2543        return maps
2544
2545
2546def _version_from_file(lines):
2547    """
2548    Given an iterable of lines from a Metadata file, return
2549    the value of the Version field, if present, or None otherwise.
2550    """
2551    def is_version_line(line):
2552        return line.lower().startswith('version:')
2553    version_lines = filter(is_version_line, lines)
2554    line = next(iter(version_lines), '')
2555    _, _, value = line.partition(':')
2556    return safe_version(value.strip()) or None
2557
2558
2559class Distribution:
2560    """Wrap an actual or potential sys.path entry w/metadata"""
2561    PKG_INFO = 'PKG-INFO'
2562
2563    def __init__(
2564            self, location=None, metadata=None, project_name=None,
2565            version=None, py_version=PY_MAJOR, platform=None,
2566            precedence=EGG_DIST):
2567        self.project_name = safe_name(project_name or 'Unknown')
2568        if version is not None:
2569            self._version = safe_version(version)
2570        self.py_version = py_version
2571        self.platform = platform
2572        self.location = location
2573        self.precedence = precedence
2574        self._provider = metadata or empty_provider
2575
2576    @classmethod
2577    def from_location(cls, location, basename, metadata=None, **kw):
2578        project_name, version, py_version, platform = [None] * 4
2579        basename, ext = os.path.splitext(basename)
2580        if ext.lower() in _distributionImpl:
2581            cls = _distributionImpl[ext.lower()]
2582
2583            match = EGG_NAME(basename)
2584            if match:
2585                project_name, version, py_version, platform = match.group(
2586                    'name', 'ver', 'pyver', 'plat'
2587                )
2588        return cls(
2589            location, metadata, project_name=project_name, version=version,
2590            py_version=py_version, platform=platform, **kw
2591        )._reload_version()
2592
2593    def _reload_version(self):
2594        return self
2595
2596    @property
2597    def hashcmp(self):
2598        return (
2599            self.parsed_version,
2600            self.precedence,
2601            self.key,
2602            self.location,
2603            self.py_version or '',
2604            self.platform or '',
2605        )
2606
2607    def __hash__(self):
2608        return hash(self.hashcmp)
2609
2610    def __lt__(self, other):
2611        return self.hashcmp < other.hashcmp
2612
2613    def __le__(self, other):
2614        return self.hashcmp <= other.hashcmp
2615
2616    def __gt__(self, other):
2617        return self.hashcmp > other.hashcmp
2618
2619    def __ge__(self, other):
2620        return self.hashcmp >= other.hashcmp
2621
2622    def __eq__(self, other):
2623        if not isinstance(other, self.__class__):
2624            # It's not a Distribution, so they are not equal
2625            return False
2626        return self.hashcmp == other.hashcmp
2627
2628    def __ne__(self, other):
2629        return not self == other
2630
2631    # These properties have to be lazy so that we don't have to load any
2632    # metadata until/unless it's actually needed.  (i.e., some distributions
2633    # may not know their name or version without loading PKG-INFO)
2634
2635    @property
2636    def key(self):
2637        try:
2638            return self._key
2639        except AttributeError:
2640            self._key = key = self.project_name.lower()
2641            return key
2642
2643    @property
2644    def parsed_version(self):
2645        if not hasattr(self, "_parsed_version"):
2646            self._parsed_version = parse_version(self.version)
2647
2648        return self._parsed_version
2649
2650    def _warn_legacy_version(self):
2651        LV = packaging.version.LegacyVersion
2652        is_legacy = isinstance(self._parsed_version, LV)
2653        if not is_legacy:
2654            return
2655
2656        # While an empty version is technically a legacy version and
2657        # is not a valid PEP 440 version, it's also unlikely to
2658        # actually come from someone and instead it is more likely that
2659        # it comes from setuptools attempting to parse a filename and
2660        # including it in the list. So for that we'll gate this warning
2661        # on if the version is anything at all or not.
2662        if not self.version:
2663            return
2664
2665        tmpl = textwrap.dedent("""
2666            '{project_name} ({version})' is being parsed as a legacy,
2667            non PEP 440,
2668            version. You may find odd behavior and sort order.
2669            In particular it will be sorted as less than 0.0. It
2670            is recommended to migrate to PEP 440 compatible
2671            versions.
2672            """).strip().replace('\n', ' ')
2673
2674        warnings.warn(tmpl.format(**vars(self)), PEP440Warning)
2675
2676    @property
2677    def version(self):
2678        try:
2679            return self._version
2680        except AttributeError as e:
2681            version = self._get_version()
2682            if version is None:
2683                path = self._get_metadata_path_for_display(self.PKG_INFO)
2684                msg = (
2685                    "Missing 'Version:' header and/or {} file at path: {}"
2686                ).format(self.PKG_INFO, path)
2687                raise ValueError(msg, self) from e
2688
2689            return version
2690
2691    @property
2692    def _dep_map(self):
2693        """
2694        A map of extra to its list of (direct) requirements
2695        for this distribution, including the null extra.
2696        """
2697        try:
2698            return self.__dep_map
2699        except AttributeError:
2700            self.__dep_map = self._filter_extras(self._build_dep_map())
2701        return self.__dep_map
2702
2703    @staticmethod
2704    def _filter_extras(dm):
2705        """
2706        Given a mapping of extras to dependencies, strip off
2707        environment markers and filter out any dependencies
2708        not matching the markers.
2709        """
2710        for extra in list(filter(None, dm)):
2711            new_extra = extra
2712            reqs = dm.pop(extra)
2713            new_extra, _, marker = extra.partition(':')
2714            fails_marker = marker and (
2715                invalid_marker(marker)
2716                or not evaluate_marker(marker)
2717            )
2718            if fails_marker:
2719                reqs = []
2720            new_extra = safe_extra(new_extra) or None
2721
2722            dm.setdefault(new_extra, []).extend(reqs)
2723        return dm
2724
2725    def _build_dep_map(self):
2726        dm = {}
2727        for name in 'requires.txt', 'depends.txt':
2728            for extra, reqs in split_sections(self._get_metadata(name)):
2729                dm.setdefault(extra, []).extend(parse_requirements(reqs))
2730        return dm
2731
2732    def requires(self, extras=()):
2733        """List of Requirements needed for this distro if `extras` are used"""
2734        dm = self._dep_map
2735        deps = []
2736        deps.extend(dm.get(None, ()))
2737        for ext in extras:
2738            try:
2739                deps.extend(dm[safe_extra(ext)])
2740            except KeyError as e:
2741                raise UnknownExtra(
2742                    "%s has no such extra feature %r" % (self, ext)
2743                ) from e
2744        return deps
2745
2746    def _get_metadata_path_for_display(self, name):
2747        """
2748        Return the path to the given metadata file, if available.
2749        """
2750        try:
2751            # We need to access _get_metadata_path() on the provider object
2752            # directly rather than through this class's __getattr__()
2753            # since _get_metadata_path() is marked private.
2754            path = self._provider._get_metadata_path(name)
2755
2756        # Handle exceptions e.g. in case the distribution's metadata
2757        # provider doesn't support _get_metadata_path().
2758        except Exception:
2759            return '[could not detect]'
2760
2761        return path
2762
2763    def _get_metadata(self, name):
2764        if self.has_metadata(name):
2765            for line in self.get_metadata_lines(name):
2766                yield line
2767
2768    def _get_version(self):
2769        lines = self._get_metadata(self.PKG_INFO)
2770        version = _version_from_file(lines)
2771
2772        return version
2773
2774    def activate(self, path=None, replace=False):
2775        """Ensure distribution is importable on `path` (default=sys.path)"""
2776        if path is None:
2777            path = sys.path
2778        self.insert_on(path, replace=replace)
2779        if path is sys.path:
2780            fixup_namespace_packages(self.location)
2781            for pkg in self._get_metadata('namespace_packages.txt'):
2782                if pkg in sys.modules:
2783                    declare_namespace(pkg)
2784
2785    def egg_name(self):
2786        """Return what this distribution's standard .egg filename should be"""
2787        filename = "%s-%s-py%s" % (
2788            to_filename(self.project_name), to_filename(self.version),
2789            self.py_version or PY_MAJOR
2790        )
2791
2792        if self.platform:
2793            filename += '-' + self.platform
2794        return filename
2795
2796    def __repr__(self):
2797        if self.location:
2798            return "%s (%s)" % (self, self.location)
2799        else:
2800            return str(self)
2801
2802    def __str__(self):
2803        try:
2804            version = getattr(self, 'version', None)
2805        except ValueError:
2806            version = None
2807        version = version or "[unknown version]"
2808        return "%s %s" % (self.project_name, version)
2809
2810    def __getattr__(self, attr):
2811        """Delegate all unrecognized public attributes to .metadata provider"""
2812        if attr.startswith('_'):
2813            raise AttributeError(attr)
2814        return getattr(self._provider, attr)
2815
2816    def __dir__(self):
2817        return list(
2818            set(super(Distribution, self).__dir__())
2819            | set(
2820                attr for attr in self._provider.__dir__()
2821                if not attr.startswith('_')
2822            )
2823        )
2824
2825    @classmethod
2826    def from_filename(cls, filename, metadata=None, **kw):
2827        return cls.from_location(
2828            _normalize_cached(filename), os.path.basename(filename), metadata,
2829            **kw
2830        )
2831
2832    def as_requirement(self):
2833        """Return a ``Requirement`` that matches this distribution exactly"""
2834        if isinstance(self.parsed_version, packaging.version.Version):
2835            spec = "%s==%s" % (self.project_name, self.parsed_version)
2836        else:
2837            spec = "%s===%s" % (self.project_name, self.parsed_version)
2838
2839        return Requirement.parse(spec)
2840
2841    def load_entry_point(self, group, name):
2842        """Return the `name` entry point of `group` or raise ImportError"""
2843        ep = self.get_entry_info(group, name)
2844        if ep is None:
2845            raise ImportError("Entry point %r not found" % ((group, name),))
2846        return ep.load()
2847
2848    def get_entry_map(self, group=None):
2849        """Return the entry point map for `group`, or the full entry map"""
2850        try:
2851            ep_map = self._ep_map
2852        except AttributeError:
2853            ep_map = self._ep_map = EntryPoint.parse_map(
2854                self._get_metadata('entry_points.txt'), self
2855            )
2856        if group is not None:
2857            return ep_map.get(group, {})
2858        return ep_map
2859
2860    def get_entry_info(self, group, name):
2861        """Return the EntryPoint object for `group`+`name`, or ``None``"""
2862        return self.get_entry_map(group).get(name)
2863
2864    # FIXME: 'Distribution.insert_on' is too complex (13)
2865    def insert_on(self, path, loc=None, replace=False):  # noqa: C901
2866        """Ensure self.location is on path
2867
2868        If replace=False (default):
2869            - If location is already in path anywhere, do nothing.
2870            - Else:
2871              - If it's an egg and its parent directory is on path,
2872                insert just ahead of the parent.
2873              - Else: add to the end of path.
2874        If replace=True:
2875            - If location is already on path anywhere (not eggs)
2876              or higher priority than its parent (eggs)
2877              do nothing.
2878            - Else:
2879              - If it's an egg and its parent directory is on path,
2880                insert just ahead of the parent,
2881                removing any lower-priority entries.
2882              - Else: add it to the front of path.
2883        """
2884
2885        loc = loc or self.location
2886        if not loc:
2887            return
2888
2889        nloc = _normalize_cached(loc)
2890        bdir = os.path.dirname(nloc)
2891        npath = [(p and _normalize_cached(p) or p) for p in path]
2892
2893        for p, item in enumerate(npath):
2894            if item == nloc:
2895                if replace:
2896                    break
2897                else:
2898                    # don't modify path (even removing duplicates) if
2899                    # found and not replace
2900                    return
2901            elif item == bdir and self.precedence == EGG_DIST:
2902                # if it's an .egg, give it precedence over its directory
2903                # UNLESS it's already been added to sys.path and replace=False
2904                if (not replace) and nloc in npath[p:]:
2905                    return
2906                if path is sys.path:
2907                    self.check_version_conflict()
2908                path.insert(p, loc)
2909                npath.insert(p, nloc)
2910                break
2911        else:
2912            if path is sys.path:
2913                self.check_version_conflict()
2914            if replace:
2915                path.insert(0, loc)
2916            else:
2917                path.append(loc)
2918            return
2919
2920        # p is the spot where we found or inserted loc; now remove duplicates
2921        while True:
2922            try:
2923                np = npath.index(nloc, p + 1)
2924            except ValueError:
2925                break
2926            else:
2927                del npath[np], path[np]
2928                # ha!
2929                p = np
2930
2931        return
2932
2933    def check_version_conflict(self):
2934        if self.key == 'setuptools':
2935            # ignore the inevitable setuptools self-conflicts  :(
2936            return
2937
2938        nsp = dict.fromkeys(self._get_metadata('namespace_packages.txt'))
2939        loc = normalize_path(self.location)
2940        for modname in self._get_metadata('top_level.txt'):
2941            if (modname not in sys.modules or modname in nsp
2942                    or modname in _namespace_packages):
2943                continue
2944            if modname in ('pkg_resources', 'setuptools', 'site'):
2945                continue
2946            fn = getattr(sys.modules[modname], '__file__', None)
2947            if fn and (normalize_path(fn).startswith(loc) or
2948                       fn.startswith(self.location)):
2949                continue
2950            issue_warning(
2951                "Module %s was already imported from %s, but %s is being added"
2952                " to sys.path" % (modname, fn, self.location),
2953            )
2954
2955    def has_version(self):
2956        try:
2957            self.version
2958        except ValueError:
2959            issue_warning("Unbuilt egg for " + repr(self))
2960            return False
2961        return True
2962
2963    def clone(self, **kw):
2964        """Copy this distribution, substituting in any changed keyword args"""
2965        names = 'project_name version py_version platform location precedence'
2966        for attr in names.split():
2967            kw.setdefault(attr, getattr(self, attr, None))
2968        kw.setdefault('metadata', self._provider)
2969        return self.__class__(**kw)
2970
2971    @property
2972    def extras(self):
2973        return [dep for dep in self._dep_map if dep]
2974
2975
2976class EggInfoDistribution(Distribution):
2977    def _reload_version(self):
2978        """
2979        Packages installed by distutils (e.g. numpy or scipy),
2980        which uses an old safe_version, and so
2981        their version numbers can get mangled when
2982        converted to filenames (e.g., 1.11.0.dev0+2329eae to
2983        1.11.0.dev0_2329eae). These distributions will not be
2984        parsed properly
2985        downstream by Distribution and safe_version, so
2986        take an extra step and try to get the version number from
2987        the metadata file itself instead of the filename.
2988        """
2989        md_version = self._get_version()
2990        if md_version:
2991            self._version = md_version
2992        return self
2993
2994
2995class DistInfoDistribution(Distribution):
2996    """
2997    Wrap an actual or potential sys.path entry
2998    w/metadata, .dist-info style.
2999    """
3000    PKG_INFO = 'METADATA'
3001    EQEQ = re.compile(r"([\(,])\s*(\d.*?)\s*([,\)])")
3002
3003    @property
3004    def _parsed_pkg_info(self):
3005        """Parse and cache metadata"""
3006        try:
3007            return self._pkg_info
3008        except AttributeError:
3009            metadata = self.get_metadata(self.PKG_INFO)
3010            self._pkg_info = email.parser.Parser().parsestr(metadata)
3011            return self._pkg_info
3012
3013    @property
3014    def _dep_map(self):
3015        try:
3016            return self.__dep_map
3017        except AttributeError:
3018            self.__dep_map = self._compute_dependencies()
3019            return self.__dep_map
3020
3021    def _compute_dependencies(self):
3022        """Recompute this distribution's dependencies."""
3023        dm = self.__dep_map = {None: []}
3024
3025        reqs = []
3026        # Including any condition expressions
3027        for req in self._parsed_pkg_info.get_all('Requires-Dist') or []:
3028            reqs.extend(parse_requirements(req))
3029
3030        def reqs_for_extra(extra):
3031            for req in reqs:
3032                if not req.marker or req.marker.evaluate({'extra': extra}):
3033                    yield req
3034
3035        common = frozenset(reqs_for_extra(None))
3036        dm[None].extend(common)
3037
3038        for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []:
3039            s_extra = safe_extra(extra.strip())
3040            dm[s_extra] = list(frozenset(reqs_for_extra(extra)) - common)
3041
3042        return dm
3043
3044
3045_distributionImpl = {
3046    '.egg': Distribution,
3047    '.egg-info': EggInfoDistribution,
3048    '.dist-info': DistInfoDistribution,
3049}
3050
3051
3052def issue_warning(*args, **kw):
3053    level = 1
3054    g = globals()
3055    try:
3056        # find the first stack frame that is *not* code in
3057        # the pkg_resources module, to use for the warning
3058        while sys._getframe(level).f_globals is g:
3059            level += 1
3060    except ValueError:
3061        pass
3062    warnings.warn(stacklevel=level + 1, *args, **kw)
3063
3064
3065def parse_requirements(strs):
3066    """Yield ``Requirement`` objects for each specification in `strs`
3067
3068    `strs` must be a string, or a (possibly-nested) iterable thereof.
3069    """
3070    # create a steppable iterator, so we can handle \-continuations
3071    lines = iter(yield_lines(strs))
3072
3073    for line in lines:
3074        # Drop comments -- a hash without a space may be in a URL.
3075        if ' #' in line:
3076            line = line[:line.find(' #')]
3077        # If there is a line continuation, drop it, and append the next line.
3078        if line.endswith('\\'):
3079            line = line[:-2].strip()
3080            try:
3081                line += next(lines)
3082            except StopIteration:
3083                return
3084        yield Requirement(line)
3085
3086
3087class RequirementParseError(packaging.requirements.InvalidRequirement):
3088    "Compatibility wrapper for InvalidRequirement"
3089
3090
3091class Requirement(packaging.requirements.Requirement):
3092    def __init__(self, requirement_string):
3093        """DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!"""
3094        super(Requirement, self).__init__(requirement_string)
3095        self.unsafe_name = self.name
3096        project_name = safe_name(self.name)
3097        self.project_name, self.key = project_name, project_name.lower()
3098        self.specs = [
3099            (spec.operator, spec.version) for spec in self.specifier]
3100        self.extras = tuple(map(safe_extra, self.extras))
3101        self.hashCmp = (
3102            self.key,
3103            self.url,
3104            self.specifier,
3105            frozenset(self.extras),
3106            str(self.marker) if self.marker else None,
3107        )
3108        self.__hash = hash(self.hashCmp)
3109
3110    def __eq__(self, other):
3111        return (
3112            isinstance(other, Requirement) and
3113            self.hashCmp == other.hashCmp
3114        )
3115
3116    def __ne__(self, other):
3117        return not self == other
3118
3119    def __contains__(self, item):
3120        if isinstance(item, Distribution):
3121            if item.key != self.key:
3122                return False
3123
3124            item = item.version
3125
3126        # Allow prereleases always in order to match the previous behavior of
3127        # this method. In the future this should be smarter and follow PEP 440
3128        # more accurately.
3129        return self.specifier.contains(item, prereleases=True)
3130
3131    def __hash__(self):
3132        return self.__hash
3133
3134    def __repr__(self):
3135        return "Requirement.parse(%r)" % str(self)
3136
3137    @staticmethod
3138    def parse(s):
3139        req, = parse_requirements(s)
3140        return req
3141
3142
3143def _always_object(classes):
3144    """
3145    Ensure object appears in the mro even
3146    for old-style classes.
3147    """
3148    if object not in classes:
3149        return classes + (object,)
3150    return classes
3151
3152
3153def _find_adapter(registry, ob):
3154    """Return an adapter factory for `ob` from `registry`"""
3155    types = _always_object(inspect.getmro(getattr(ob, '__class__', type(ob))))
3156    for t in types:
3157        if t in registry:
3158            return registry[t]
3159
3160
3161def ensure_directory(path):
3162    """Ensure that the parent directory of `path` exists"""
3163    dirname = os.path.dirname(path)
3164    os.makedirs(dirname, exist_ok=True)
3165
3166
3167def _bypass_ensure_directory(path):
3168    """Sandbox-bypassing version of ensure_directory()"""
3169    if not WRITE_SUPPORT:
3170        raise IOError('"os.mkdir" not supported on this platform.')
3171    dirname, filename = split(path)
3172    if dirname and filename and not isdir(dirname):
3173        _bypass_ensure_directory(dirname)
3174        try:
3175            mkdir(dirname, 0o755)
3176        except FileExistsError:
3177            pass
3178
3179
3180def split_sections(s):
3181    """Split a string or iterable thereof into (section, content) pairs
3182
3183    Each ``section`` is a stripped version of the section header ("[section]")
3184    and each ``content`` is a list of stripped lines excluding blank lines and
3185    comment-only lines.  If there are any such lines before the first section
3186    header, they're returned in a first ``section`` of ``None``.
3187    """
3188    section = None
3189    content = []
3190    for line in yield_lines(s):
3191        if line.startswith("["):
3192            if line.endswith("]"):
3193                if section or content:
3194                    yield section, content
3195                section = line[1:-1].strip()
3196                content = []
3197            else:
3198                raise ValueError("Invalid section heading", line)
3199        else:
3200            content.append(line)
3201
3202    # wrap up last segment
3203    yield section, content
3204
3205
3206def _mkstemp(*args, **kw):
3207    old_open = os.open
3208    try:
3209        # temporarily bypass sandboxing
3210        os.open = os_open
3211        return tempfile.mkstemp(*args, **kw)
3212    finally:
3213        # and then put it back
3214        os.open = old_open
3215
3216
3217# Silence the PEP440Warning by default, so that end users don't get hit by it
3218# randomly just because they use pkg_resources. We want to append the rule
3219# because we want earlier uses of filterwarnings to take precedence over this
3220# one.
3221warnings.filterwarnings("ignore", category=PEP440Warning, append=True)
3222
3223
3224# from jaraco.functools 1.3
3225def _call_aside(f, *args, **kwargs):
3226    f(*args, **kwargs)
3227    return f
3228
3229
3230@_call_aside
3231def _initialize(g=globals()):
3232    "Set up global resource manager (deliberately not state-saved)"
3233    manager = ResourceManager()
3234    g['_manager'] = manager
3235    g.update(
3236        (name, getattr(manager, name))
3237        for name in dir(manager)
3238        if not name.startswith('_')
3239    )
3240
3241
3242@_call_aside
3243def _initialize_master_working_set():
3244    """
3245    Prepare the master working set and make the ``require()``
3246    API available.
3247
3248    This function has explicit effects on the global state
3249    of pkg_resources. It is intended to be invoked once at
3250    the initialization of this module.
3251
3252    Invocation by other packages is unsupported and done
3253    at their own risk.
3254    """
3255    working_set = WorkingSet._build_master()
3256    _declare_state('object', working_set=working_set)
3257
3258    require = working_set.require
3259    iter_entry_points = working_set.iter_entry_points
3260    add_activation_listener = working_set.subscribe
3261    run_script = working_set.run_script
3262    # backward compatibility
3263    run_main = run_script
3264    # Activate all distributions already on sys.path with replace=False and
3265    # ensure that all distributions added to the working set in the future
3266    # (e.g. by calling ``require()``) will get activated as well,
3267    # with higher priority (replace=True).
3268    tuple(
3269        dist.activate(replace=False)
3270        for dist in working_set
3271    )
3272    add_activation_listener(
3273        lambda dist: dist.activate(replace=True),
3274        existing=False,
3275    )
3276    working_set.entries = []
3277    # match order
3278    list(map(working_set.add_entry, sys.path))
3279    globals().update(locals())
3280
3281
3282class PkgResourcesDeprecationWarning(Warning):
3283    """
3284    Base class for warning about deprecations in ``pkg_resources``
3285
3286    This class is not derived from ``DeprecationWarning``, and as such is
3287    visible by default.
3288    """
3289