Path: blob/master/venv/Lib/site-packages/pip/_internal/operations/freeze.py
811 views
# The following comment should be removed at some point in the future.1# mypy: strict-optional=False2# mypy: disallow-untyped-defs=False34from __future__ import absolute_import56import collections7import logging8import os910from pip._vendor import six11from pip._vendor.packaging.utils import canonicalize_name12from pip._vendor.pkg_resources import RequirementParseError1314from pip._internal.exceptions import BadCommand, InstallationError15from pip._internal.req.constructors import (16install_req_from_editable,17install_req_from_line,18)19from pip._internal.req.req_file import COMMENT_RE20from pip._internal.utils.direct_url_helpers import (21direct_url_as_pep440_direct_reference,22dist_get_direct_url,23)24from pip._internal.utils.misc import (25dist_is_editable,26get_installed_distributions,27)28from pip._internal.utils.typing import MYPY_CHECK_RUNNING2930if MYPY_CHECK_RUNNING:31from typing import (32Iterator, Optional, List, Container, Set, Dict, Tuple, Iterable, Union33)34from pip._internal.cache import WheelCache35from pip._vendor.pkg_resources import (36Distribution, Requirement37)3839RequirementInfo = Tuple[Optional[Union[str, Requirement]], bool, List[str]]404142logger = logging.getLogger(__name__)434445def freeze(46requirement=None, # type: Optional[List[str]]47find_links=None, # type: Optional[List[str]]48local_only=None, # type: Optional[bool]49user_only=None, # type: Optional[bool]50paths=None, # type: Optional[List[str]]51isolated=False, # type: bool52wheel_cache=None, # type: Optional[WheelCache]53exclude_editable=False, # type: bool54skip=() # type: Container[str]55):56# type: (...) -> Iterator[str]57find_links = find_links or []5859for link in find_links:60yield '-f {}'.format(link)61installations = {} # type: Dict[str, FrozenRequirement]62for dist in get_installed_distributions(local_only=local_only,63skip=(),64user_only=user_only,65paths=paths):66try:67req = FrozenRequirement.from_dist(dist)68except RequirementParseError as exc:69# We include dist rather than dist.project_name because the70# dist string includes more information, like the version and71# location. We also include the exception message to aid72# troubleshooting.73logger.warning(74'Could not generate requirement for distribution %r: %s',75dist, exc76)77continue78if exclude_editable and req.editable:79continue80installations[req.canonical_name] = req8182if requirement:83# the options that don't get turned into an InstallRequirement84# should only be emitted once, even if the same option is in multiple85# requirements files, so we need to keep track of what has been emitted86# so that we don't emit it again if it's seen again87emitted_options = set() # type: Set[str]88# keep track of which files a requirement is in so that we can89# give an accurate warning if a requirement appears multiple times.90req_files = collections.defaultdict(list) # type: Dict[str, List[str]]91for req_file_path in requirement:92with open(req_file_path) as req_file:93for line in req_file:94if (not line.strip() or95line.strip().startswith('#') or96line.startswith((97'-r', '--requirement',98'-Z', '--always-unzip',99'-f', '--find-links',100'-i', '--index-url',101'--pre',102'--trusted-host',103'--process-dependency-links',104'--extra-index-url'))):105line = line.rstrip()106if line not in emitted_options:107emitted_options.add(line)108yield line109continue110111if line.startswith('-e') or line.startswith('--editable'):112if line.startswith('-e'):113line = line[2:].strip()114else:115line = line[len('--editable'):].strip().lstrip('=')116line_req = install_req_from_editable(117line,118isolated=isolated,119)120else:121line_req = install_req_from_line(122COMMENT_RE.sub('', line).strip(),123isolated=isolated,124)125126if not line_req.name:127logger.info(128"Skipping line in requirement file [%s] because "129"it's not clear what it would install: %s",130req_file_path, line.strip(),131)132logger.info(133" (add #egg=PackageName to the URL to avoid"134" this warning)"135)136else:137line_req_canonical_name = canonicalize_name(138line_req.name)139if line_req_canonical_name not in installations:140# either it's not installed, or it is installed141# but has been processed already142if not req_files[line_req.name]:143logger.warning(144"Requirement file [%s] contains %s, but "145"package %r is not installed",146req_file_path,147COMMENT_RE.sub('', line).strip(),148line_req.name149)150else:151req_files[line_req.name].append(req_file_path)152else:153yield str(installations[154line_req_canonical_name]).rstrip()155del installations[line_req_canonical_name]156req_files[line_req.name].append(req_file_path)157158# Warn about requirements that were included multiple times (in a159# single requirements file or in different requirements files).160for name, files in six.iteritems(req_files):161if len(files) > 1:162logger.warning("Requirement %s included multiple times [%s]",163name, ', '.join(sorted(set(files))))164165yield(166'## The following requirements were added by '167'pip freeze:'168)169for installation in sorted(170installations.values(), key=lambda x: x.name.lower()):171if installation.canonical_name not in skip:172yield str(installation).rstrip()173174175def get_requirement_info(dist):176# type: (Distribution) -> RequirementInfo177"""178Compute and return values (req, editable, comments) for use in179FrozenRequirement.from_dist().180"""181if not dist_is_editable(dist):182return (None, False, [])183184location = os.path.normcase(os.path.abspath(dist.location))185186from pip._internal.vcs import vcs, RemoteNotFoundError187vcs_backend = vcs.get_backend_for_dir(location)188189if vcs_backend is None:190req = dist.as_requirement()191logger.debug(192'No VCS found for editable requirement "%s" in: %r', req,193location,194)195comments = [196'# Editable install with no version control ({})'.format(req)197]198return (location, True, comments)199200try:201req = vcs_backend.get_src_requirement(location, dist.project_name)202except RemoteNotFoundError:203req = dist.as_requirement()204comments = [205'# Editable {} install with no remote ({})'.format(206type(vcs_backend).__name__, req,207)208]209return (location, True, comments)210211except BadCommand:212logger.warning(213'cannot determine version of editable source in %s '214'(%s command not found in path)',215location,216vcs_backend.name,217)218return (None, True, [])219220except InstallationError as exc:221logger.warning(222"Error when trying to get requirement for VCS system %s, "223"falling back to uneditable format", exc224)225else:226if req is not None:227return (req, True, [])228229logger.warning(230'Could not determine repository location of %s', location231)232comments = ['## !! Could not determine repository location']233234return (None, False, comments)235236237class FrozenRequirement(object):238def __init__(self, name, req, editable, comments=()):239# type: (str, Union[str, Requirement], bool, Iterable[str]) -> None240self.name = name241self.canonical_name = canonicalize_name(name)242self.req = req243self.editable = editable244self.comments = comments245246@classmethod247def from_dist(cls, dist):248# type: (Distribution) -> FrozenRequirement249# TODO `get_requirement_info` is taking care of editable requirements.250# TODO This should be refactored when we will add detection of251# editable that provide .dist-info metadata.252req, editable, comments = get_requirement_info(dist)253if req is None and not editable:254# if PEP 610 metadata is present, attempt to use it255direct_url = dist_get_direct_url(dist)256if direct_url:257req = direct_url_as_pep440_direct_reference(258direct_url, dist.project_name259)260comments = []261if req is None:262# name==version requirement263req = dist.as_requirement()264265return cls(dist.project_name, req, editable, comments=comments)266267def __str__(self):268req = self.req269if self.editable:270req = '-e {}'.format(req)271return '\n'.join(list(self.comments) + [str(req)]) + '\n'272273274