33 DIRECT_URL_METADATA_NAME,
35 DirectUrlValidationError,
43from ._json
import msg_to_json
46 from typing
import Protocol
50DistributionVersion = Union[LegacyVersion, Version]
59 def name(self) -> str:
67 def group(self) -> str:
72 entry: Tuple[str, ...],
73 info: Tuple[str, ...],
75 """Convert a legacy installed-files.txt path into modern RECORD path.
77 The legacy format stores paths relative to the info directory, while the
78 modern format stores paths relative to the package root, e.g. the
79 site-packages directory.
81 :param entry: Path parts of the installed-files.txt entry.
82 :param info: Path parts of the egg-info directory relative to package root.
83 :returns: The converted entry.
85 For best compatibility with symlinks, this does not use ``abspath()`` or
86 ``Path.resolve()``, but tries to work with path parts:
88 1. While ``entry`` starts with ``..``, remove the equal amounts of parts
89 from ``info``; if ``info`` is empty, start appending ``..`` instead.
90 2. Join the two directly.
92 while entry
and entry[0] ==
"..":
93 if not info
or info[-1] ==
"..":
110 """Load the distribution from a metadata directory.
112 :param directory: Path to a metadata directory, e.g. ``.dist-info``.
119 metadata_contents: bytes,
122 ) ->
"BaseDistribution":
123 """Load the distribution from the contents of a METADATA file.
125 This is used to implement PEP 658 by generating a "shallow" dist object that can
126 be used for resolution without downloading or building the actual dist yet.
128 :param metadata_contents: The contents of a METADATA file.
129 :param filename: File name for the dist with this metadata.
130 :param project_name: Name of the project this dist represents.
135 def from_wheel(cls, wheel:
"Wheel", name: str) ->
"BaseDistribution":
136 """Load the distribution from a given wheel.
138 :param wheel: A concrete wheel definition.
139 :param name: File name of the wheel.
141 :raises InvalidWheel: Whenever loading of the wheel causes a
142 :py:exc:`zipfile.BadZipFile` exception to be thrown.
143 :raises UnsupportedWheel: If the wheel is a valid zip, but malformed
149 return f
"{self.raw_name} {self.version} ({self.location})"
152 return f
"{self.raw_name} {self.version}"
156 """Where the distribution is loaded from.
158 A string value is not necessarily a filesystem path, since distributions
159 can be loaded from other sources, e.g. arbitrary zip archives. ``None``
160 means the distribution is created in-memory.
162 Do not canonicalize this value with e.g. ``pathlib.Path.resolve()``. If
163 this is a symbolic link, we want to preserve the relative path between
164 it and files in the distribution.
170 """The project location for editable distributions.
172 This is the directory where pyproject.toml or setup.py is located.
173 None if the distribution is not installed in editable mode.
192 """The distribution's "installed" location.
194 This should generally be a ``site-packages`` directory. This is
195 usually ``dist.location``, except for legacy develop-installed packages,
196 where ``dist.location`` is the source code location, and this is where
197 the ``.egg-link`` file is.
199 The returned location is normalized (in particular, with symlinks removed).
205 """Location of the .[egg|dist]-info directory or file.
207 Similarly to ``location``, a string value is not necessarily a
208 filesystem path. ``None`` means the distribution is created in-memory.
210 For a modern .dist-info installation on disk, this should be something
211 like ``{location}/{raw_name}-{version}.dist-info``.
213 Do not canonicalize this value with e.g. ``pathlib.Path.resolve()``. If
214 this is a symbolic link, we want to preserve the relative path between
215 it and other files in the distribution.
221 """Whether this distribution is installed with legacy distutils format.
223 A distribution installed with "raw" distutils not patched by setuptools
224 uses one single file at ``info_location`` to store metadata. We need to
225 treat this specially on uninstallation.
228 if not info_location:
234 """Whether this distribution is installed as an egg.
236 This usually indicates the distribution was installed by (older versions
246 """Whether this distribution is installed with the ``.egg-info`` format.
248 This usually indicates the distribution was installed with setuptools
249 with an old pip version or with ``single-version-externally-managed``.
251 Note that this ensure the metadata store is a directory. distutils can
252 also installs an ``.egg-info``, but as a file, not a directory. This
253 property is *False* for that case. Also see ``installed_by_distutils``.
256 if not info_location:
264 """Whether this distribution is installed with the "modern format".
266 This indicates a "modern" installation, e.g. storing metadata in the
267 ``.dist-info`` directory. This applies to installations made by
268 setuptools (but through pip, not directly), or anything using the
269 standardized build backend interface (PEP 517).
272 if not info_location:
283 def version(self) -> DistributionVersion:
288 """Convert a project name to its setuptools-compatible filename.
290 This is a copy of ``pkg_resources.to_filename()`` for compatibility.
296 """Obtain a DirectUrl from this distribution.
298 Returns None if the distribution has no `direct_url.json` metadata,
299 or if `direct_url.json` is invalid.
302 content = self.
read_text(DIRECT_URL_METADATA_NAME)
303 except FileNotFoundError:
310 DirectUrlValidationError,
313 "Error parsing %s for %s: %s",
314 DIRECT_URL_METADATA_NAME,
323 installer_text = self.
read_text(
"INSTALLER")
324 except (OSError, ValueError, NoneMetadataError):
334 return self.
is_file(
"REQUESTED")
342 """If distribution is installed in the current virtual environment.
344 Always True if we're not in a virtualenv.
363 """Check whether an entry in the info directory is a file."""
367 """Find distutils 'scripts' entries metadata.
369 If 'scripts' is supplied in ``setup.py``, distutils records those in the
370 installed distribution's ``scripts`` directory, a file for each script.
375 """Read a file in the info directory.
377 :raise FileNotFoundError: If ``path`` does not exist in the directory.
378 :raise NoneMetadataError: If ``path`` exists in the info directory, but
389 @functools.lru_cache(maxsize=1)
399 """Metadata of distribution parsed from e.g. METADATA or PKG-INFO.
401 This should return an empty message if the metadata file is unavailable.
403 :raises NoneMetadataError: If the metadata file is available, but does
404 not contain valid metadata.
410 """PEP 566 compliant JSON-serializable representation of METADATA or PKG-INFO.
412 This should return an empty dict if the metadata file is unavailable.
414 :raises NoneMetadataError: If the metadata file is available, but does
415 not contain valid metadata.
421 """Value of "Metadata-Version:" in distribution metadata, if available."""
426 """Value of "Name:" in distribution metadata."""
433 """Value of "Requires-Python:" in distribution metadata.
435 If the key does not exist or contains an invalid value, an empty
436 SpecifierSet should be returned.
444 except InvalidSpecifier
as e:
445 message =
"Package %r has an invalid Requires-Python: %s"
451 """Dependencies of this distribution.
453 For modern .dist-info distributions, this is the collection of
454 "Requires-Dist:" entries in distribution metadata.
459 """Extras provided by this distribution.
461 For modern .dist-info distributions, this is the collection of
462 "Provides-Extra:" entries in distribution metadata.
469 except FileNotFoundError:
476 text = self.
read_text(
"installed-files.txt")
477 except FileNotFoundError:
482 if root
is None or info
is None:
496 """Iterate through file entries declared in this distribution.
498 For modern .dist-info distributions, this is the files listed in the
499 ``RECORD`` metadata file. For legacy setuptools distributions, this
500 comes from ``installed-files.txt``, with entries normalized to be
501 compatible with the format used by ``RECORD``.
503 :return: An iterator for listed entries, or None if the distribution
504 contains neither ``RECORD`` nor ``installed-files.txt``.
512 """Parse a ``requires.txt`` in an egg-info directory.
514 This is an INI-ish format where an egg-info stores dependencies. A
515 section name describes extra other environment markers, while each entry
516 is an arbitrary string (not a key-value pair) representing a dependency
517 as a requirement string (no markers).
519 There is a construct in ``importlib.metadata`` called ``Sectioned`` that
520 does mostly the same, but the format is currently considered private.
524 except FileNotFoundError:
532 extra, _, marker =
line.strip(
"[]").partition(
":")
534 yield RequiresEntry(requirement=line, extra=extra, marker=marker)
537 """Get extras from the egg-info directory."""
546 """Get distribution dependencies from the egg-info directory.
548 To ease parsing, this converts a legacy dependency entry into a PEP 508
549 requirement string. Like ``_iter_requires_txt_entries()``, there is code
550 in ``importlib.metadata`` that does mostly the same, but not do exactly
553 Namely, ``importlib.metadata`` does not normalize the extra name before
554 putting it into the requirement string, which causes marker comparison
555 to fail because the dist-info format do normalize. This is consistent in
556 all currently available PEP 517 backends, although not standardized.
560 marker = f
'({entry.marker}) and extra == "{safe_extra(entry.extra)}"'
562 marker = f
'extra == "{safe_extra(entry.extra)}"'
568 yield f
"{entry.requirement} ; {marker}"
573 """Add egg-info requires.txt information to the metadata."""
576 metadata[
"Requires-Dist"] = dep
579 metadata[
"Provides-Extra"] = extra
583 """An environment containing distributions to introspect."""
590 def from_paths(cls, paths: Optional[List[str]]) ->
"BaseEnvironment":
594 """Given a requirement name, return the installed distributions.
596 The name may not be normalized. The implementation must canonicalize
602 """Iterate through installed distributions.
604 This function should be implemented by subclass, but never called
605 directly. Use the public ``iter_distribution()`` instead, which
606 implements additional logic to make sure the distributions are valid.
611 """Iterate through all installed distributions without any filtering."""
618 r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$",
622 if not project_name_valid:
624 "Ignoring invalid distribution %s (%s)",
633 local_only: bool =
True,
634 skip: Container[str] = stdlib_pkgs,
635 include_editables: bool =
True,
636 editables_only: bool =
False,
637 user_only: bool =
False,
638 ) -> Iterator[BaseDistribution]:
639 """Return a list of installed distributions.
641 This is based on ``iter_all_distributions()`` with additional filtering
642 options. Note that ``iter_installed_distributions()`` without arguments
643 is *not* equal to ``iter_all_distributions()``, since some of the
644 configurations exclude packages by default.
646 :param local_only: If True (default), only return installations
647 local to the current virtualenv, if in a virtualenv.
648 :param skip: An iterable of canonicalized project names to ignore;
649 defaults to ``stdlib_pkgs``.
650 :param include_editables: If False, don't report editables.
651 :param editables_only: If True, only report editables.
652 :param user_only: If True, only report installations in the user
657 it = (d
for d
in it
if d.local)
658 if not include_editables:
683 def __init__(self, location: str, stream: IO[bytes]) ->
None: