Path: blob/main/test/lib/python3.9/site-packages/pip/_internal/operations/freeze.py
4804 views
import collections1import logging2import os3from typing import Container, Dict, Generator, Iterable, List, NamedTuple, Optional, Set45from pip._vendor.packaging.utils import canonicalize_name6from pip._vendor.packaging.version import Version78from pip._internal.exceptions import BadCommand, InstallationError9from pip._internal.metadata import BaseDistribution, get_environment10from pip._internal.req.constructors import (11install_req_from_editable,12install_req_from_line,13)14from pip._internal.req.req_file import COMMENT_RE15from pip._internal.utils.direct_url_helpers import direct_url_as_pep440_direct_reference1617logger = logging.getLogger(__name__)181920class _EditableInfo(NamedTuple):21requirement: str22comments: List[str]232425def freeze(26requirement: Optional[List[str]] = None,27local_only: bool = False,28user_only: bool = False,29paths: Optional[List[str]] = None,30isolated: bool = False,31exclude_editable: bool = False,32skip: Container[str] = (),33) -> Generator[str, None, None]:34installations: Dict[str, FrozenRequirement] = {}3536dists = get_environment(paths).iter_installed_distributions(37local_only=local_only,38skip=(),39user_only=user_only,40)41for dist in dists:42req = FrozenRequirement.from_dist(dist)43if exclude_editable and req.editable:44continue45installations[req.canonical_name] = req4647if requirement:48# the options that don't get turned into an InstallRequirement49# should only be emitted once, even if the same option is in multiple50# requirements files, so we need to keep track of what has been emitted51# so that we don't emit it again if it's seen again52emitted_options: Set[str] = set()53# keep track of which files a requirement is in so that we can54# give an accurate warning if a requirement appears multiple times.55req_files: Dict[str, List[str]] = collections.defaultdict(list)56for req_file_path in requirement:57with open(req_file_path) as req_file:58for line in req_file:59if (60not line.strip()61or line.strip().startswith("#")62or line.startswith(63(64"-r",65"--requirement",66"-f",67"--find-links",68"-i",69"--index-url",70"--pre",71"--trusted-host",72"--process-dependency-links",73"--extra-index-url",74"--use-feature",75)76)77):78line = line.rstrip()79if line not in emitted_options:80emitted_options.add(line)81yield line82continue8384if line.startswith("-e") or line.startswith("--editable"):85if line.startswith("-e"):86line = line[2:].strip()87else:88line = line[len("--editable") :].strip().lstrip("=")89line_req = install_req_from_editable(90line,91isolated=isolated,92)93else:94line_req = install_req_from_line(95COMMENT_RE.sub("", line).strip(),96isolated=isolated,97)9899if not line_req.name:100logger.info(101"Skipping line in requirement file [%s] because "102"it's not clear what it would install: %s",103req_file_path,104line.strip(),105)106logger.info(107" (add #egg=PackageName to the URL to avoid"108" this warning)"109)110else:111line_req_canonical_name = canonicalize_name(line_req.name)112if line_req_canonical_name not in installations:113# either it's not installed, or it is installed114# but has been processed already115if not req_files[line_req.name]:116logger.warning(117"Requirement file [%s] contains %s, but "118"package %r is not installed",119req_file_path,120COMMENT_RE.sub("", line).strip(),121line_req.name,122)123else:124req_files[line_req.name].append(req_file_path)125else:126yield str(installations[line_req_canonical_name]).rstrip()127del installations[line_req_canonical_name]128req_files[line_req.name].append(req_file_path)129130# Warn about requirements that were included multiple times (in a131# single requirements file or in different requirements files).132for name, files in req_files.items():133if len(files) > 1:134logger.warning(135"Requirement %s included multiple times [%s]",136name,137", ".join(sorted(set(files))),138)139140yield ("## The following requirements were added by pip freeze:")141for installation in sorted(installations.values(), key=lambda x: x.name.lower()):142if installation.canonical_name not in skip:143yield str(installation).rstrip()144145146def _format_as_name_version(dist: BaseDistribution) -> str:147if isinstance(dist.version, Version):148return f"{dist.raw_name}=={dist.version}"149return f"{dist.raw_name}==={dist.version}"150151152def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:153"""154Compute and return values (req, comments) for use in155FrozenRequirement.from_dist().156"""157editable_project_location = dist.editable_project_location158assert editable_project_location159location = os.path.normcase(os.path.abspath(editable_project_location))160161from pip._internal.vcs import RemoteNotFoundError, RemoteNotValidError, vcs162163vcs_backend = vcs.get_backend_for_dir(location)164165if vcs_backend is None:166display = _format_as_name_version(dist)167logger.debug(168'No VCS found for editable requirement "%s" in: %r',169display,170location,171)172return _EditableInfo(173requirement=location,174comments=[f"# Editable install with no version control ({display})"],175)176177vcs_name = type(vcs_backend).__name__178179try:180req = vcs_backend.get_src_requirement(location, dist.raw_name)181except RemoteNotFoundError:182display = _format_as_name_version(dist)183return _EditableInfo(184requirement=location,185comments=[f"# Editable {vcs_name} install with no remote ({display})"],186)187except RemoteNotValidError as ex:188display = _format_as_name_version(dist)189return _EditableInfo(190requirement=location,191comments=[192f"# Editable {vcs_name} install ({display}) with either a deleted "193f"local remote or invalid URI:",194f"# '{ex.url}'",195],196)197except BadCommand:198logger.warning(199"cannot determine version of editable source in %s "200"(%s command not found in path)",201location,202vcs_backend.name,203)204return _EditableInfo(requirement=location, comments=[])205except InstallationError as exc:206logger.warning("Error when trying to get requirement for VCS system %s", exc)207else:208return _EditableInfo(requirement=req, comments=[])209210logger.warning("Could not determine repository location of %s", location)211212return _EditableInfo(213requirement=location,214comments=["## !! Could not determine repository location"],215)216217218class FrozenRequirement:219def __init__(220self,221name: str,222req: str,223editable: bool,224comments: Iterable[str] = (),225) -> None:226self.name = name227self.canonical_name = canonicalize_name(name)228self.req = req229self.editable = editable230self.comments = comments231232@classmethod233def from_dist(cls, dist: BaseDistribution) -> "FrozenRequirement":234editable = dist.editable235if editable:236req, comments = _get_editable_info(dist)237else:238comments = []239direct_url = dist.direct_url240if direct_url:241# if PEP 610 metadata is present, use it242req = direct_url_as_pep440_direct_reference(direct_url, dist.raw_name)243else:244# name==version requirement245req = _format_as_name_version(dist)246247return cls(dist.raw_name, req, editable, comments=comments)248249def __str__(self) -> str:250req = self.req251if self.editable:252req = f"-e {req}"253return "\n".join(list(self.comments) + [str(req)]) + "\n"254255256