| Viewing file:  freeze.py (9.54 KB)      -rw-r--r-- Select action/file-type:
 
  (+) |  (+) |  (+) | Code (+) | Session (+) |  (+) | SDB (+) |  (+) |  (+) |  (+) |  (+) |  (+) | 
 
import collectionsimport logging
 import os
 from typing import Container, Dict, Iterable, Iterator, List, NamedTuple, Optional, Set
 
 from pip._vendor.packaging.utils import canonicalize_name
 from pip._vendor.packaging.version import Version
 
 from pip._internal.exceptions import BadCommand, InstallationError
 from pip._internal.metadata import BaseDistribution, get_environment
 from pip._internal.req.constructors import (
 install_req_from_editable,
 install_req_from_line,
 )
 from pip._internal.req.req_file import COMMENT_RE
 from pip._internal.utils.direct_url_helpers import direct_url_as_pep440_direct_reference
 
 logger = logging.getLogger(__name__)
 
 
 class _EditableInfo(NamedTuple):
 requirement: str
 comments: List[str]
 
 
 def freeze(
 requirement: Optional[List[str]] = None,
 local_only: bool = False,
 user_only: bool = False,
 paths: Optional[List[str]] = None,
 isolated: bool = False,
 exclude_editable: bool = False,
 skip: Container[str] = (),
 ) -> Iterator[str]:
 installations: Dict[str, FrozenRequirement] = {}
 
 dists = get_environment(paths).iter_installed_distributions(
 local_only=local_only,
 skip=(),
 user_only=user_only,
 )
 for dist in dists:
 req = FrozenRequirement.from_dist(dist)
 if exclude_editable and req.editable:
 continue
 installations[req.canonical_name] = req
 
 if requirement:
 # the options that don't get turned into an InstallRequirement
 # should only be emitted once, even if the same option is in multiple
 # requirements files, so we need to keep track of what has been emitted
 # so that we don't emit it again if it's seen again
 emitted_options: Set[str] = set()
 # keep track of which files a requirement is in so that we can
 # give an accurate warning if a requirement appears multiple times.
 req_files: Dict[str, List[str]] = collections.defaultdict(list)
 for req_file_path in requirement:
 with open(req_file_path) as req_file:
 for line in req_file:
 if (
 not line.strip()
 or line.strip().startswith("#")
 or line.startswith(
 (
 "-r",
 "--requirement",
 "-f",
 "--find-links",
 "-i",
 "--index-url",
 "--pre",
 "--trusted-host",
 "--process-dependency-links",
 "--extra-index-url",
 "--use-feature",
 )
 )
 ):
 line = line.rstrip()
 if line not in emitted_options:
 emitted_options.add(line)
 yield line
 continue
 
 if line.startswith("-e") or line.startswith("--editable"):
 if line.startswith("-e"):
 line = line[2:].strip()
 else:
 line = line[len("--editable") :].strip().lstrip("=")
 line_req = install_req_from_editable(
 line,
 isolated=isolated,
 )
 else:
 line_req = install_req_from_line(
 COMMENT_RE.sub("", line).strip(),
 isolated=isolated,
 )
 
 if not line_req.name:
 logger.info(
 "Skipping line in requirement file [%s] because "
 "it's not clear what it would install: %s",
 req_file_path,
 line.strip(),
 )
 logger.info(
 "  (add #egg=PackageName to the URL to avoid"
 " this warning)"
 )
 else:
 line_req_canonical_name = canonicalize_name(line_req.name)
 if line_req_canonical_name not in installations:
 # either it's not installed, or it is installed
 # but has been processed already
 if not req_files[line_req.name]:
 logger.warning(
 "Requirement file [%s] contains %s, but "
 "package %r is not installed",
 req_file_path,
 COMMENT_RE.sub("", line).strip(),
 line_req.name,
 )
 else:
 req_files[line_req.name].append(req_file_path)
 else:
 yield str(installations[line_req_canonical_name]).rstrip()
 del installations[line_req_canonical_name]
 req_files[line_req.name].append(req_file_path)
 
 # Warn about requirements that were included multiple times (in a
 # single requirements file or in different requirements files).
 for name, files in req_files.items():
 if len(files) > 1:
 logger.warning(
 "Requirement %s included multiple times [%s]",
 name,
 ", ".join(sorted(set(files))),
 )
 
 yield ("## The following requirements were added by pip freeze:")
 for installation in sorted(installations.values(), key=lambda x: x.name.lower()):
 if installation.canonical_name not in skip:
 yield str(installation).rstrip()
 
 
 def _format_as_name_version(dist: BaseDistribution) -> str:
 if isinstance(dist.version, Version):
 return f"{dist.raw_name}=={dist.version}"
 return f"{dist.raw_name}==={dist.version}"
 
 
 def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
 """
 Compute and return values (req, comments) for use in
 FrozenRequirement.from_dist().
 """
 editable_project_location = dist.editable_project_location
 assert editable_project_location
 location = os.path.normcase(os.path.abspath(editable_project_location))
 
 from pip._internal.vcs import RemoteNotFoundError, RemoteNotValidError, vcs
 
 vcs_backend = vcs.get_backend_for_dir(location)
 
 if vcs_backend is None:
 display = _format_as_name_version(dist)
 logger.debug(
 'No VCS found for editable requirement "%s" in: %r',
 display,
 location,
 )
 return _EditableInfo(
 requirement=location,
 comments=[f"# Editable install with no version control ({display})"],
 )
 
 vcs_name = type(vcs_backend).__name__
 
 try:
 req = vcs_backend.get_src_requirement(location, dist.raw_name)
 except RemoteNotFoundError:
 display = _format_as_name_version(dist)
 return _EditableInfo(
 requirement=location,
 comments=[f"# Editable {vcs_name} install with no remote ({display})"],
 )
 except RemoteNotValidError as ex:
 display = _format_as_name_version(dist)
 return _EditableInfo(
 requirement=location,
 comments=[
 f"# Editable {vcs_name} install ({display}) with either a deleted "
 f"local remote or invalid URI:",
 f"# '{ex.url}'",
 ],
 )
 except BadCommand:
 logger.warning(
 "cannot determine version of editable source in %s "
 "(%s command not found in path)",
 location,
 vcs_backend.name,
 )
 return _EditableInfo(requirement=location, comments=[])
 except InstallationError as exc:
 logger.warning("Error when trying to get requirement for VCS system %s", exc)
 else:
 return _EditableInfo(requirement=req, comments=[])
 
 logger.warning("Could not determine repository location of %s", location)
 
 return _EditableInfo(
 requirement=location,
 comments=["## !! Could not determine repository location"],
 )
 
 
 class FrozenRequirement:
 def __init__(
 self,
 name: str,
 req: str,
 editable: bool,
 comments: Iterable[str] = (),
 ) -> None:
 self.name = name
 self.canonical_name = canonicalize_name(name)
 self.req = req
 self.editable = editable
 self.comments = comments
 
 @classmethod
 def from_dist(cls, dist: BaseDistribution) -> "FrozenRequirement":
 editable = dist.editable
 if editable:
 req, comments = _get_editable_info(dist)
 else:
 comments = []
 direct_url = dist.direct_url
 if direct_url:
 # if PEP 610 metadata is present, use it
 req = direct_url_as_pep440_direct_reference(direct_url, dist.raw_name)
 else:
 # name==version requirement
 req = _format_as_name_version(dist)
 
 return cls(dist.raw_name, req, editable, comments=comments)
 
 def __str__(self) -> str:
 req = self.req
 if self.editable:
 req = f"-e {req}"
 return "\n".join(list(self.comments) + [str(req)]) + "\n"
 
 |