(root)/
Python-3.11.7/
Lib/
importlib/
metadata/
__init__.py
       1  import os
       2  import re
       3  import abc
       4  import csv
       5  import sys
       6  import email
       7  import pathlib
       8  import zipfile
       9  import operator
      10  import textwrap
      11  import warnings
      12  import functools
      13  import itertools
      14  import posixpath
      15  import collections
      16  
      17  from . import _adapters, _meta
      18  from ._collections import FreezableDefaultDict, Pair
      19  from ._functools import method_cache, pass_none
      20  from ._itertools import always_iterable, unique_everseen
      21  from ._meta import PackageMetadata, SimplePath
      22  
      23  from contextlib import suppress
      24  from importlib import import_module
      25  from importlib.abc import MetaPathFinder
      26  from itertools import starmap
      27  from typing import List, Mapping, Optional, Union
      28  
      29  
      30  __all__ = [
      31      'Distribution',
      32      'DistributionFinder',
      33      'PackageMetadata',
      34      'PackageNotFoundError',
      35      'distribution',
      36      'distributions',
      37      'entry_points',
      38      'files',
      39      'metadata',
      40      'packages_distributions',
      41      'requires',
      42      'version',
      43  ]
      44  
      45  
      46  class ESC[4;38;5;81mPackageNotFoundError(ESC[4;38;5;149mModuleNotFoundError):
      47      """The package was not found."""
      48  
      49      def __str__(self):
      50          return f"No package metadata was found for {self.name}"
      51  
      52      @property
      53      def name(self):
      54          (name,) = self.args
      55          return name
      56  
      57  
      58  class ESC[4;38;5;81mSectioned:
      59      """
      60      A simple entry point config parser for performance
      61  
      62      >>> for item in Sectioned.read(Sectioned._sample):
      63      ...     print(item)
      64      Pair(name='sec1', value='# comments ignored')
      65      Pair(name='sec1', value='a = 1')
      66      Pair(name='sec1', value='b = 2')
      67      Pair(name='sec2', value='a = 2')
      68  
      69      >>> res = Sectioned.section_pairs(Sectioned._sample)
      70      >>> item = next(res)
      71      >>> item.name
      72      'sec1'
      73      >>> item.value
      74      Pair(name='a', value='1')
      75      >>> item = next(res)
      76      >>> item.value
      77      Pair(name='b', value='2')
      78      >>> item = next(res)
      79      >>> item.name
      80      'sec2'
      81      >>> item.value
      82      Pair(name='a', value='2')
      83      >>> list(res)
      84      []
      85      """
      86  
      87      _sample = textwrap.dedent(
      88          """
      89          [sec1]
      90          # comments ignored
      91          a = 1
      92          b = 2
      93  
      94          [sec2]
      95          a = 2
      96          """
      97      ).lstrip()
      98  
      99      @classmethod
     100      def section_pairs(cls, text):
     101          return (
     102              section._replace(value=Pair.parse(section.value))
     103              for section in cls.read(text, filter_=cls.valid)
     104              if section.name is not None
     105          )
     106  
     107      @staticmethod
     108      def read(text, filter_=None):
     109          lines = filter(filter_, map(str.strip, text.splitlines()))
     110          name = None
     111          for value in lines:
     112              section_match = value.startswith('[') and value.endswith(']')
     113              if section_match:
     114                  name = value.strip('[]')
     115                  continue
     116              yield Pair(name, value)
     117  
     118      @staticmethod
     119      def valid(line):
     120          return line and not line.startswith('#')
     121  
     122  
     123  class ESC[4;38;5;81mDeprecatedTuple:
     124      """
     125      Provide subscript item access for backward compatibility.
     126  
     127      >>> recwarn = getfixture('recwarn')
     128      >>> ep = EntryPoint(name='name', value='value', group='group')
     129      >>> ep[:]
     130      ('name', 'value', 'group')
     131      >>> ep[0]
     132      'name'
     133      >>> len(recwarn)
     134      1
     135      """
     136  
     137      _warn = functools.partial(
     138          warnings.warn,
     139          "EntryPoint tuple interface is deprecated. Access members by name.",
     140          DeprecationWarning,
     141          stacklevel=2,
     142      )
     143  
     144      def __getitem__(self, item):
     145          self._warn()
     146          return self._key()[item]
     147  
     148  
     149  class ESC[4;38;5;81mEntryPoint(ESC[4;38;5;149mDeprecatedTuple):
     150      """An entry point as defined by Python packaging conventions.
     151  
     152      See `the packaging docs on entry points
     153      <https://packaging.python.org/specifications/entry-points/>`_
     154      for more information.
     155  
     156      >>> ep = EntryPoint(
     157      ...     name=None, group=None, value='package.module:attr [extra1, extra2]')
     158      >>> ep.module
     159      'package.module'
     160      >>> ep.attr
     161      'attr'
     162      >>> ep.extras
     163      ['extra1', 'extra2']
     164      """
     165  
     166      pattern = re.compile(
     167          r'(?P<module>[\w.]+)\s*'
     168          r'(:\s*(?P<attr>[\w.]+)\s*)?'
     169          r'((?P<extras>\[.*\])\s*)?$'
     170      )
     171      """
     172      A regular expression describing the syntax for an entry point,
     173      which might look like:
     174  
     175          - module
     176          - package.module
     177          - package.module:attribute
     178          - package.module:object.attribute
     179          - package.module:attr [extra1, extra2]
     180  
     181      Other combinations are possible as well.
     182  
     183      The expression is lenient about whitespace around the ':',
     184      following the attr, and following any extras.
     185      """
     186  
     187      name: str
     188      value: str
     189      group: str
     190  
     191      dist: Optional['Distribution'] = None
     192  
     193      def __init__(self, name, value, group):
     194          vars(self).update(name=name, value=value, group=group)
     195  
     196      def load(self):
     197          """Load the entry point from its definition. If only a module
     198          is indicated by the value, return that module. Otherwise,
     199          return the named object.
     200          """
     201          match = self.pattern.match(self.value)
     202          module = import_module(match.group('module'))
     203          attrs = filter(None, (match.group('attr') or '').split('.'))
     204          return functools.reduce(getattr, attrs, module)
     205  
     206      @property
     207      def module(self):
     208          match = self.pattern.match(self.value)
     209          return match.group('module')
     210  
     211      @property
     212      def attr(self):
     213          match = self.pattern.match(self.value)
     214          return match.group('attr')
     215  
     216      @property
     217      def extras(self):
     218          match = self.pattern.match(self.value)
     219          return re.findall(r'\w+', match.group('extras') or '')
     220  
     221      def _for(self, dist):
     222          vars(self).update(dist=dist)
     223          return self
     224  
     225      def __iter__(self):
     226          """
     227          Supply iter so one may construct dicts of EntryPoints by name.
     228          """
     229          msg = (
     230              "Construction of dict of EntryPoints is deprecated in "
     231              "favor of EntryPoints."
     232          )
     233          warnings.warn(msg, DeprecationWarning)
     234          return iter((self.name, self))
     235  
     236      def matches(self, **params):
     237          """
     238          EntryPoint matches the given parameters.
     239  
     240          >>> ep = EntryPoint(group='foo', name='bar', value='bing:bong [extra1, extra2]')
     241          >>> ep.matches(group='foo')
     242          True
     243          >>> ep.matches(name='bar', value='bing:bong [extra1, extra2]')
     244          True
     245          >>> ep.matches(group='foo', name='other')
     246          False
     247          >>> ep.matches()
     248          True
     249          >>> ep.matches(extras=['extra1', 'extra2'])
     250          True
     251          >>> ep.matches(module='bing')
     252          True
     253          >>> ep.matches(attr='bong')
     254          True
     255          """
     256          attrs = (getattr(self, param) for param in params)
     257          return all(map(operator.eq, params.values(), attrs))
     258  
     259      def _key(self):
     260          return self.name, self.value, self.group
     261  
     262      def __lt__(self, other):
     263          return self._key() < other._key()
     264  
     265      def __eq__(self, other):
     266          return self._key() == other._key()
     267  
     268      def __setattr__(self, name, value):
     269          raise AttributeError("EntryPoint objects are immutable.")
     270  
     271      def __repr__(self):
     272          return (
     273              f'EntryPoint(name={self.name!r}, value={self.value!r}, '
     274              f'group={self.group!r})'
     275          )
     276  
     277      def __hash__(self):
     278          return hash(self._key())
     279  
     280  
     281  class ESC[4;38;5;81mDeprecatedList(ESC[4;38;5;149mlist):
     282      """
     283      Allow an otherwise immutable object to implement mutability
     284      for compatibility.
     285  
     286      >>> recwarn = getfixture('recwarn')
     287      >>> dl = DeprecatedList(range(3))
     288      >>> dl[0] = 1
     289      >>> dl.append(3)
     290      >>> del dl[3]
     291      >>> dl.reverse()
     292      >>> dl.sort()
     293      >>> dl.extend([4])
     294      >>> dl.pop(-1)
     295      4
     296      >>> dl.remove(1)
     297      >>> dl += [5]
     298      >>> dl + [6]
     299      [1, 2, 5, 6]
     300      >>> dl + (6,)
     301      [1, 2, 5, 6]
     302      >>> dl.insert(0, 0)
     303      >>> dl
     304      [0, 1, 2, 5]
     305      >>> dl == [0, 1, 2, 5]
     306      True
     307      >>> dl == (0, 1, 2, 5)
     308      True
     309      >>> len(recwarn)
     310      1
     311      """
     312  
     313      __slots__ = ()
     314  
     315      _warn = functools.partial(
     316          warnings.warn,
     317          "EntryPoints list interface is deprecated. Cast to list if needed.",
     318          DeprecationWarning,
     319          stacklevel=2,
     320      )
     321  
     322      def _wrap_deprecated_method(method_name: str):  # type: ignore
     323          def wrapped(self, *args, **kwargs):
     324              self._warn()
     325              return getattr(super(), method_name)(*args, **kwargs)
     326  
     327          return method_name, wrapped
     328  
     329      locals().update(
     330          map(
     331              _wrap_deprecated_method,
     332              '__setitem__ __delitem__ append reverse extend pop remove '
     333              '__iadd__ insert sort'.split(),
     334          )
     335      )
     336  
     337      def __add__(self, other):
     338          if not isinstance(other, tuple):
     339              self._warn()
     340              other = tuple(other)
     341          return self.__class__(tuple(self) + other)
     342  
     343      def __eq__(self, other):
     344          if not isinstance(other, tuple):
     345              self._warn()
     346              other = tuple(other)
     347  
     348          return tuple(self).__eq__(other)
     349  
     350  
     351  class ESC[4;38;5;81mEntryPoints(ESC[4;38;5;149mDeprecatedList):
     352      """
     353      An immutable collection of selectable EntryPoint objects.
     354      """
     355  
     356      __slots__ = ()
     357  
     358      def __getitem__(self, name):  # -> EntryPoint:
     359          """
     360          Get the EntryPoint in self matching name.
     361          """
     362          if isinstance(name, int):
     363              warnings.warn(
     364                  "Accessing entry points by index is deprecated. "
     365                  "Cast to tuple if needed.",
     366                  DeprecationWarning,
     367                  stacklevel=2,
     368              )
     369              return super().__getitem__(name)
     370          try:
     371              return next(iter(self.select(name=name)))
     372          except StopIteration:
     373              raise KeyError(name)
     374  
     375      def select(self, **params):
     376          """
     377          Select entry points from self that match the
     378          given parameters (typically group and/or name).
     379          """
     380          return EntryPoints(ep for ep in self if ep.matches(**params))
     381  
     382      @property
     383      def names(self):
     384          """
     385          Return the set of all names of all entry points.
     386          """
     387          return {ep.name for ep in self}
     388  
     389      @property
     390      def groups(self):
     391          """
     392          Return the set of all groups of all entry points.
     393  
     394          For coverage while SelectableGroups is present.
     395          >>> EntryPoints().groups
     396          set()
     397          """
     398          return {ep.group for ep in self}
     399  
     400      @classmethod
     401      def _from_text_for(cls, text, dist):
     402          return cls(ep._for(dist) for ep in cls._from_text(text))
     403  
     404      @staticmethod
     405      def _from_text(text):
     406          return (
     407              EntryPoint(name=item.value.name, value=item.value.value, group=item.name)
     408              for item in Sectioned.section_pairs(text or '')
     409          )
     410  
     411  
     412  class ESC[4;38;5;81mDeprecated:
     413      """
     414      Compatibility add-in for mapping to indicate that
     415      mapping behavior is deprecated.
     416  
     417      >>> recwarn = getfixture('recwarn')
     418      >>> class DeprecatedDict(Deprecated, dict): pass
     419      >>> dd = DeprecatedDict(foo='bar')
     420      >>> dd.get('baz', None)
     421      >>> dd['foo']
     422      'bar'
     423      >>> list(dd)
     424      ['foo']
     425      >>> list(dd.keys())
     426      ['foo']
     427      >>> 'foo' in dd
     428      True
     429      >>> list(dd.values())
     430      ['bar']
     431      >>> len(recwarn)
     432      1
     433      """
     434  
     435      _warn = functools.partial(
     436          warnings.warn,
     437          "SelectableGroups dict interface is deprecated. Use select.",
     438          DeprecationWarning,
     439          stacklevel=2,
     440      )
     441  
     442      def __getitem__(self, name):
     443          self._warn()
     444          return super().__getitem__(name)
     445  
     446      def get(self, name, default=None):
     447          self._warn()
     448          return super().get(name, default)
     449  
     450      def __iter__(self):
     451          self._warn()
     452          return super().__iter__()
     453  
     454      def __contains__(self, *args):
     455          self._warn()
     456          return super().__contains__(*args)
     457  
     458      def keys(self):
     459          self._warn()
     460          return super().keys()
     461  
     462      def values(self):
     463          self._warn()
     464          return super().values()
     465  
     466  
     467  class ESC[4;38;5;81mSelectableGroups(ESC[4;38;5;149mDeprecated, ESC[4;38;5;149mdict):
     468      """
     469      A backward- and forward-compatible result from
     470      entry_points that fully implements the dict interface.
     471      """
     472  
     473      @classmethod
     474      def load(cls, eps):
     475          by_group = operator.attrgetter('group')
     476          ordered = sorted(eps, key=by_group)
     477          grouped = itertools.groupby(ordered, by_group)
     478          return cls((group, EntryPoints(eps)) for group, eps in grouped)
     479  
     480      @property
     481      def _all(self):
     482          """
     483          Reconstruct a list of all entrypoints from the groups.
     484          """
     485          groups = super(Deprecated, self).values()
     486          return EntryPoints(itertools.chain.from_iterable(groups))
     487  
     488      @property
     489      def groups(self):
     490          return self._all.groups
     491  
     492      @property
     493      def names(self):
     494          """
     495          for coverage:
     496          >>> SelectableGroups().names
     497          set()
     498          """
     499          return self._all.names
     500  
     501      def select(self, **params):
     502          if not params:
     503              return self
     504          return self._all.select(**params)
     505  
     506  
     507  class ESC[4;38;5;81mPackagePath(ESC[4;38;5;149mpathlibESC[4;38;5;149m.ESC[4;38;5;149mPurePosixPath):
     508      """A reference to a path in a package"""
     509  
     510      def read_text(self, encoding='utf-8'):
     511          with self.locate().open(encoding=encoding) as stream:
     512              return stream.read()
     513  
     514      def read_binary(self):
     515          with self.locate().open('rb') as stream:
     516              return stream.read()
     517  
     518      def locate(self):
     519          """Return a path-like object for this path"""
     520          return self.dist.locate_file(self)
     521  
     522  
     523  class ESC[4;38;5;81mFileHash:
     524      def __init__(self, spec):
     525          self.mode, _, self.value = spec.partition('=')
     526  
     527      def __repr__(self):
     528          return f'<FileHash mode: {self.mode} value: {self.value}>'
     529  
     530  
     531  class ESC[4;38;5;81mDistribution:
     532      """A Python distribution package."""
     533  
     534      @abc.abstractmethod
     535      def read_text(self, filename):
     536          """Attempt to load metadata file given by the name.
     537  
     538          :param filename: The name of the file in the distribution info.
     539          :return: The text if found, otherwise None.
     540          """
     541  
     542      @abc.abstractmethod
     543      def locate_file(self, path):
     544          """
     545          Given a path to a file in this distribution, return a path
     546          to it.
     547          """
     548  
     549      @classmethod
     550      def from_name(cls, name: str):
     551          """Return the Distribution for the given package name.
     552  
     553          :param name: The name of the distribution package to search for.
     554          :return: The Distribution instance (or subclass thereof) for the named
     555              package, if found.
     556          :raises PackageNotFoundError: When the named package's distribution
     557              metadata cannot be found.
     558          :raises ValueError: When an invalid value is supplied for name.
     559          """
     560          if not name:
     561              raise ValueError("A distribution name is required.")
     562          try:
     563              return next(cls.discover(name=name))
     564          except StopIteration:
     565              raise PackageNotFoundError(name)
     566  
     567      @classmethod
     568      def discover(cls, **kwargs):
     569          """Return an iterable of Distribution objects for all packages.
     570  
     571          Pass a ``context`` or pass keyword arguments for constructing
     572          a context.
     573  
     574          :context: A ``DistributionFinder.Context`` object.
     575          :return: Iterable of Distribution objects for all packages.
     576          """
     577          context = kwargs.pop('context', None)
     578          if context and kwargs:
     579              raise ValueError("cannot accept context and kwargs")
     580          context = context or DistributionFinder.Context(**kwargs)
     581          return itertools.chain.from_iterable(
     582              resolver(context) for resolver in cls._discover_resolvers()
     583          )
     584  
     585      @staticmethod
     586      def at(path):
     587          """Return a Distribution for the indicated metadata path
     588  
     589          :param path: a string or path-like object
     590          :return: a concrete Distribution instance for the path
     591          """
     592          return PathDistribution(pathlib.Path(path))
     593  
     594      @staticmethod
     595      def _discover_resolvers():
     596          """Search the meta_path for resolvers."""
     597          declared = (
     598              getattr(finder, 'find_distributions', None) for finder in sys.meta_path
     599          )
     600          return filter(None, declared)
     601  
     602      @property
     603      def metadata(self) -> _meta.PackageMetadata:
     604          """Return the parsed metadata for this Distribution.
     605  
     606          The returned object will have keys that name the various bits of
     607          metadata.  See PEP 566 for details.
     608          """
     609          text = (
     610              self.read_text('METADATA')
     611              or self.read_text('PKG-INFO')
     612              # This last clause is here to support old egg-info files.  Its
     613              # effect is to just end up using the PathDistribution's self._path
     614              # (which points to the egg-info file) attribute unchanged.
     615              or self.read_text('')
     616          )
     617          return _adapters.Message(email.message_from_string(text))
     618  
     619      @property
     620      def name(self):
     621          """Return the 'Name' metadata for the distribution package."""
     622          return self.metadata['Name']
     623  
     624      @property
     625      def _normalized_name(self):
     626          """Return a normalized version of the name."""
     627          return Prepared.normalize(self.name)
     628  
     629      @property
     630      def version(self):
     631          """Return the 'Version' metadata for the distribution package."""
     632          return self.metadata['Version']
     633  
     634      @property
     635      def entry_points(self):
     636          return EntryPoints._from_text_for(self.read_text('entry_points.txt'), self)
     637  
     638      @property
     639      def files(self):
     640          """Files in this distribution.
     641  
     642          :return: List of PackagePath for this distribution or None
     643  
     644          Result is `None` if the metadata file that enumerates files
     645          (i.e. RECORD for dist-info or SOURCES.txt for egg-info) is
     646          missing.
     647          Result may be empty if the metadata exists but is empty.
     648          """
     649  
     650          def make_file(name, hash=None, size_str=None):
     651              result = PackagePath(name)
     652              result.hash = FileHash(hash) if hash else None
     653              result.size = int(size_str) if size_str else None
     654              result.dist = self
     655              return result
     656  
     657          @pass_none
     658          def make_files(lines):
     659              return list(starmap(make_file, csv.reader(lines)))
     660  
     661          return make_files(self._read_files_distinfo() or self._read_files_egginfo())
     662  
     663      def _read_files_distinfo(self):
     664          """
     665          Read the lines of RECORD
     666          """
     667          text = self.read_text('RECORD')
     668          return text and text.splitlines()
     669  
     670      def _read_files_egginfo(self):
     671          """
     672          SOURCES.txt might contain literal commas, so wrap each line
     673          in quotes.
     674          """
     675          text = self.read_text('SOURCES.txt')
     676          return text and map('"{}"'.format, text.splitlines())
     677  
     678      @property
     679      def requires(self):
     680          """Generated requirements specified for this Distribution"""
     681          reqs = self._read_dist_info_reqs() or self._read_egg_info_reqs()
     682          return reqs and list(reqs)
     683  
     684      def _read_dist_info_reqs(self):
     685          return self.metadata.get_all('Requires-Dist')
     686  
     687      def _read_egg_info_reqs(self):
     688          source = self.read_text('requires.txt')
     689          return pass_none(self._deps_from_requires_text)(source)
     690  
     691      @classmethod
     692      def _deps_from_requires_text(cls, source):
     693          return cls._convert_egg_info_reqs_to_simple_reqs(Sectioned.read(source))
     694  
     695      @staticmethod
     696      def _convert_egg_info_reqs_to_simple_reqs(sections):
     697          """
     698          Historically, setuptools would solicit and store 'extra'
     699          requirements, including those with environment markers,
     700          in separate sections. More modern tools expect each
     701          dependency to be defined separately, with any relevant
     702          extras and environment markers attached directly to that
     703          requirement. This method converts the former to the
     704          latter. See _test_deps_from_requires_text for an example.
     705          """
     706  
     707          def make_condition(name):
     708              return name and f'extra == "{name}"'
     709  
     710          def quoted_marker(section):
     711              section = section or ''
     712              extra, sep, markers = section.partition(':')
     713              if extra and markers:
     714                  markers = f'({markers})'
     715              conditions = list(filter(None, [markers, make_condition(extra)]))
     716              return '; ' + ' and '.join(conditions) if conditions else ''
     717  
     718          def url_req_space(req):
     719              """
     720              PEP 508 requires a space between the url_spec and the quoted_marker.
     721              Ref python/importlib_metadata#357.
     722              """
     723              # '@' is uniquely indicative of a url_req.
     724              return ' ' * ('@' in req)
     725  
     726          for section in sections:
     727              space = url_req_space(section.value)
     728              yield section.value + space + quoted_marker(section.name)
     729  
     730  
     731  class ESC[4;38;5;81mDistributionFinder(ESC[4;38;5;149mMetaPathFinder):
     732      """
     733      A MetaPathFinder capable of discovering installed distributions.
     734      """
     735  
     736      class ESC[4;38;5;81mContext:
     737          """
     738          Keyword arguments presented by the caller to
     739          ``distributions()`` or ``Distribution.discover()``
     740          to narrow the scope of a search for distributions
     741          in all DistributionFinders.
     742  
     743          Each DistributionFinder may expect any parameters
     744          and should attempt to honor the canonical
     745          parameters defined below when appropriate.
     746          """
     747  
     748          name = None
     749          """
     750          Specific name for which a distribution finder should match.
     751          A name of ``None`` matches all distributions.
     752          """
     753  
     754          def __init__(self, **kwargs):
     755              vars(self).update(kwargs)
     756  
     757          @property
     758          def path(self):
     759              """
     760              The sequence of directory path that a distribution finder
     761              should search.
     762  
     763              Typically refers to Python installed package paths such as
     764              "site-packages" directories and defaults to ``sys.path``.
     765              """
     766              return vars(self).get('path', sys.path)
     767  
     768      @abc.abstractmethod
     769      def find_distributions(self, context=Context()):
     770          """
     771          Find distributions.
     772  
     773          Return an iterable of all Distribution instances capable of
     774          loading the metadata for packages matching the ``context``,
     775          a DistributionFinder.Context instance.
     776          """
     777  
     778  
     779  class ESC[4;38;5;81mFastPath:
     780      """
     781      Micro-optimized class for searching a path for
     782      children.
     783  
     784      >>> FastPath('').children()
     785      ['...']
     786      """
     787  
     788      @functools.lru_cache()  # type: ignore
     789      def __new__(cls, root):
     790          return super().__new__(cls)
     791  
     792      def __init__(self, root):
     793          self.root = root
     794  
     795      def joinpath(self, child):
     796          return pathlib.Path(self.root, child)
     797  
     798      def children(self):
     799          with suppress(Exception):
     800              return os.listdir(self.root or '.')
     801          with suppress(Exception):
     802              return self.zip_children()
     803          return []
     804  
     805      def zip_children(self):
     806          zip_path = zipfile.Path(self.root)
     807          names = zip_path.root.namelist()
     808          self.joinpath = zip_path.joinpath
     809  
     810          return dict.fromkeys(child.split(posixpath.sep, 1)[0] for child in names)
     811  
     812      def search(self, name):
     813          return self.lookup(self.mtime).search(name)
     814  
     815      @property
     816      def mtime(self):
     817          with suppress(OSError):
     818              return os.stat(self.root).st_mtime
     819          self.lookup.cache_clear()
     820  
     821      @method_cache
     822      def lookup(self, mtime):
     823          return Lookup(self)
     824  
     825  
     826  class ESC[4;38;5;81mLookup:
     827      def __init__(self, path: FastPath):
     828          base = os.path.basename(path.root).lower()
     829          base_is_egg = base.endswith(".egg")
     830          self.infos = FreezableDefaultDict(list)
     831          self.eggs = FreezableDefaultDict(list)
     832  
     833          for child in path.children():
     834              low = child.lower()
     835              if low.endswith((".dist-info", ".egg-info")):
     836                  # rpartition is faster than splitext and suitable for this purpose.
     837                  name = low.rpartition(".")[0].partition("-")[0]
     838                  normalized = Prepared.normalize(name)
     839                  self.infos[normalized].append(path.joinpath(child))
     840              elif base_is_egg and low == "egg-info":
     841                  name = base.rpartition(".")[0].partition("-")[0]
     842                  legacy_normalized = Prepared.legacy_normalize(name)
     843                  self.eggs[legacy_normalized].append(path.joinpath(child))
     844  
     845          self.infos.freeze()
     846          self.eggs.freeze()
     847  
     848      def search(self, prepared):
     849          infos = (
     850              self.infos[prepared.normalized]
     851              if prepared
     852              else itertools.chain.from_iterable(self.infos.values())
     853          )
     854          eggs = (
     855              self.eggs[prepared.legacy_normalized]
     856              if prepared
     857              else itertools.chain.from_iterable(self.eggs.values())
     858          )
     859          return itertools.chain(infos, eggs)
     860  
     861  
     862  class ESC[4;38;5;81mPrepared:
     863      """
     864      A prepared search for metadata on a possibly-named package.
     865      """
     866  
     867      normalized = None
     868      legacy_normalized = None
     869  
     870      def __init__(self, name):
     871          self.name = name
     872          if name is None:
     873              return
     874          self.normalized = self.normalize(name)
     875          self.legacy_normalized = self.legacy_normalize(name)
     876  
     877      @staticmethod
     878      def normalize(name):
     879          """
     880          PEP 503 normalization plus dashes as underscores.
     881          """
     882          return re.sub(r"[-_.]+", "-", name).lower().replace('-', '_')
     883  
     884      @staticmethod
     885      def legacy_normalize(name):
     886          """
     887          Normalize the package name as found in the convention in
     888          older packaging tools versions and specs.
     889          """
     890          return name.lower().replace('-', '_')
     891  
     892      def __bool__(self):
     893          return bool(self.name)
     894  
     895  
     896  class ESC[4;38;5;81mMetadataPathFinder(ESC[4;38;5;149mDistributionFinder):
     897      @classmethod
     898      def find_distributions(cls, context=DistributionFinder.Context()):
     899          """
     900          Find distributions.
     901  
     902          Return an iterable of all Distribution instances capable of
     903          loading the metadata for packages matching ``context.name``
     904          (or all names if ``None`` indicated) along the paths in the list
     905          of directories ``context.path``.
     906          """
     907          found = cls._search_paths(context.name, context.path)
     908          return map(PathDistribution, found)
     909  
     910      @classmethod
     911      def _search_paths(cls, name, paths):
     912          """Find metadata directories in paths heuristically."""
     913          prepared = Prepared(name)
     914          return itertools.chain.from_iterable(
     915              path.search(prepared) for path in map(FastPath, paths)
     916          )
     917  
     918      def invalidate_caches(cls):
     919          FastPath.__new__.cache_clear()
     920  
     921  
     922  class ESC[4;38;5;81mPathDistribution(ESC[4;38;5;149mDistribution):
     923      def __init__(self, path: SimplePath):
     924          """Construct a distribution.
     925  
     926          :param path: SimplePath indicating the metadata directory.
     927          """
     928          self._path = path
     929  
     930      def read_text(self, filename):
     931          with suppress(
     932              FileNotFoundError,
     933              IsADirectoryError,
     934              KeyError,
     935              NotADirectoryError,
     936              PermissionError,
     937          ):
     938              return self._path.joinpath(filename).read_text(encoding='utf-8')
     939  
     940      read_text.__doc__ = Distribution.read_text.__doc__
     941  
     942      def locate_file(self, path):
     943          return self._path.parent / path
     944  
     945      @property
     946      def _normalized_name(self):
     947          """
     948          Performance optimization: where possible, resolve the
     949          normalized name from the file system path.
     950          """
     951          stem = os.path.basename(str(self._path))
     952          return (
     953              pass_none(Prepared.normalize)(self._name_from_stem(stem))
     954              or super()._normalized_name
     955          )
     956  
     957      @staticmethod
     958      def _name_from_stem(stem):
     959          """
     960          >>> PathDistribution._name_from_stem('foo-3.0.egg-info')
     961          'foo'
     962          >>> PathDistribution._name_from_stem('CherryPy-3.0.dist-info')
     963          'CherryPy'
     964          >>> PathDistribution._name_from_stem('face.egg-info')
     965          'face'
     966          >>> PathDistribution._name_from_stem('foo.bar')
     967          """
     968          filename, ext = os.path.splitext(stem)
     969          if ext not in ('.dist-info', '.egg-info'):
     970              return
     971          name, sep, rest = filename.partition('-')
     972          return name
     973  
     974  
     975  def distribution(distribution_name):
     976      """Get the ``Distribution`` instance for the named package.
     977  
     978      :param distribution_name: The name of the distribution package as a string.
     979      :return: A ``Distribution`` instance (or subclass thereof).
     980      """
     981      return Distribution.from_name(distribution_name)
     982  
     983  
     984  def distributions(**kwargs):
     985      """Get all ``Distribution`` instances in the current environment.
     986  
     987      :return: An iterable of ``Distribution`` instances.
     988      """
     989      return Distribution.discover(**kwargs)
     990  
     991  
     992  def metadata(distribution_name) -> _meta.PackageMetadata:
     993      """Get the metadata for the named package.
     994  
     995      :param distribution_name: The name of the distribution package to query.
     996      :return: A PackageMetadata containing the parsed metadata.
     997      """
     998      return Distribution.from_name(distribution_name).metadata
     999  
    1000  
    1001  def version(distribution_name):
    1002      """Get the version string for the named package.
    1003  
    1004      :param distribution_name: The name of the distribution package to query.
    1005      :return: The version string for the package as defined in the package's
    1006          "Version" metadata key.
    1007      """
    1008      return distribution(distribution_name).version
    1009  
    1010  
    1011  _unique = functools.partial(
    1012      unique_everseen,
    1013      key=operator.attrgetter('_normalized_name'),
    1014  )
    1015  """
    1016  Wrapper for ``distributions`` to return unique distributions by name.
    1017  """
    1018  
    1019  
    1020  def entry_points(**params) -> Union[EntryPoints, SelectableGroups]:
    1021      """Return EntryPoint objects for all installed packages.
    1022  
    1023      Pass selection parameters (group or name) to filter the
    1024      result to entry points matching those properties (see
    1025      EntryPoints.select()).
    1026  
    1027      For compatibility, returns ``SelectableGroups`` object unless
    1028      selection parameters are supplied. In the future, this function
    1029      will return ``EntryPoints`` instead of ``SelectableGroups``
    1030      even when no selection parameters are supplied.
    1031  
    1032      For maximum future compatibility, pass selection parameters
    1033      or invoke ``.select`` with parameters on the result.
    1034  
    1035      :return: EntryPoints or SelectableGroups for all installed packages.
    1036      """
    1037      eps = itertools.chain.from_iterable(
    1038          dist.entry_points for dist in _unique(distributions())
    1039      )
    1040      return SelectableGroups.load(eps).select(**params)
    1041  
    1042  
    1043  def files(distribution_name):
    1044      """Return a list of files for the named package.
    1045  
    1046      :param distribution_name: The name of the distribution package to query.
    1047      :return: List of files composing the distribution.
    1048      """
    1049      return distribution(distribution_name).files
    1050  
    1051  
    1052  def requires(distribution_name):
    1053      """
    1054      Return a list of requirements for the named package.
    1055  
    1056      :return: An iterator of requirements, suitable for
    1057          packaging.requirement.Requirement.
    1058      """
    1059      return distribution(distribution_name).requires
    1060  
    1061  
    1062  def packages_distributions() -> Mapping[str, List[str]]:
    1063      """
    1064      Return a mapping of top-level packages to their
    1065      distributions.
    1066  
    1067      >>> import collections.abc
    1068      >>> pkgs = packages_distributions()
    1069      >>> all(isinstance(dist, collections.abc.Sequence) for dist in pkgs.values())
    1070      True
    1071      """
    1072      pkg_to_dist = collections.defaultdict(list)
    1073      for dist in distributions():
    1074          for pkg in _top_level_declared(dist) or _top_level_inferred(dist):
    1075              pkg_to_dist[pkg].append(dist.metadata['Name'])
    1076      return dict(pkg_to_dist)
    1077  
    1078  
    1079  def _top_level_declared(dist):
    1080      return (dist.read_text('top_level.txt') or '').split()
    1081  
    1082  
    1083  def _top_level_inferred(dist):
    1084      return {
    1085          f.parts[0] if len(f.parts) > 1 else f.with_suffix('').name
    1086          for f in always_iterable(dist.files)
    1087          if f.suffix == ".py"
    1088      }