Let us walk on the 3-isogeny graph
Loading...
Searching...
No Matches
freeze.py
Go to the documentation of this file.
1import collections
2import logging
3import os
4from typing import Container, Dict, Generator, Iterable, List, NamedTuple, Optional, Set
5
6from pip._vendor.packaging.utils import canonicalize_name
7from pip._vendor.packaging.version import Version
8
9from pip._internal.exceptions import BadCommand, InstallationError
10from pip._internal.metadata import BaseDistribution, get_environment
12 install_req_from_editable,
13 install_req_from_line,
14)
15from pip._internal.req.req_file import COMMENT_RE
16from pip._internal.utils.direct_url_helpers import direct_url_as_pep440_direct_reference
17
18logger = logging.getLogger(__name__)
19
20
21class _EditableInfo(NamedTuple):
22 requirement: str
23 comments: List[str]
24
25
26def freeze(
27 requirement: Optional[List[str]] = None,
28 local_only: bool = False,
29 user_only: bool = False,
30 paths: Optional[List[str]] = None,
31 isolated: bool = False,
32 exclude_editable: bool = False,
33 skip: Container[str] = (),
34) -> Generator[str, None, None]:
35 installations: Dict[str, FrozenRequirement] = {}
36
37 dists = get_environment(paths).iter_installed_distributions(
38 local_only=local_only,
39 skip=(),
40 user_only=user_only,
41 )
42 for dist in dists:
44 if exclude_editable and req.editable:
45 continue
46 installations[req.canonical_name] = req
47
48 if requirement:
49 # the options that don't get turned into an InstallRequirement
50 # should only be emitted once, even if the same option is in multiple
51 # requirements files, so we need to keep track of what has been emitted
52 # so that we don't emit it again if it's seen again
53 emitted_options: Set[str] = set()
54 # keep track of which files a requirement is in so that we can
55 # give an accurate warning if a requirement appears multiple times.
56 req_files: Dict[str, List[str]] = collections.defaultdict(list)
57 for req_file_path in requirement:
58 with open(req_file_path) as req_file:
59 for line in req_file:
60 if (
61 not line.strip()
62 or line.strip().startswith("#")
64 (
65 "-r",
66 "--requirement",
67 "-f",
68 "--find-links",
69 "-i",
70 "--index-url",
71 "--pre",
72 "--trusted-host",
73 "--process-dependency-links",
74 "--extra-index-url",
75 "--use-feature",
76 )
77 )
78 ):
79 line = line.rstrip()
80 if line not in emitted_options:
82 yield line
83 continue
84
85 if line.startswith("-e") or line.startswith("--editable"):
86 if line.startswith("-e"):
87 line = line[2:].strip()
88 else:
89 line = line[len("--editable") :].strip().lstrip("=")
90 line_req = install_req_from_editable(
91 line,
92 isolated=isolated,
93 )
94 else:
95 line_req = install_req_from_line(
96 COMMENT_RE.sub("", line).strip(),
97 isolated=isolated,
98 )
99
100 if not line_req.name:
102 "Skipping line in requirement file [%s] because "
103 "it's not clear what it would install: %s",
104 req_file_path,
105 line.strip(),
106 )
108 " (add #egg=PackageName to the URL to avoid"
109 " this warning)"
110 )
111 else:
112 line_req_canonical_name = canonicalize_name(line_req.name)
113 if line_req_canonical_name not in installations:
114 # either it's not installed, or it is installed
115 # but has been processed already
116 if not req_files[line_req.name]:
118 "Requirement file [%s] contains %s, but "
119 "package %r is not installed",
120 req_file_path,
121 COMMENT_RE.sub("", line).strip(),
123 )
124 else:
125 req_files[line_req.name].append(req_file_path)
126 else:
127 yield str(installations[line_req_canonical_name]).rstrip()
128 del installations[line_req_canonical_name]
129 req_files[line_req.name].append(req_file_path)
130
131 # Warn about requirements that were included multiple times (in a
132 # single requirements file or in different requirements files).
133 for name, files in req_files.items():
134 if len(files) > 1:
136 "Requirement %s included multiple times [%s]",
137 name,
138 ", ".join(sorted(set(files))),
139 )
140
141 yield ("## The following requirements were added by pip freeze:")
142 for installation in sorted(installations.values(), key=lambda x: x.name.lower()):
143 if installation.canonical_name not in skip:
144 yield str(installation).rstrip()
145
146
147def _format_as_name_version(dist: BaseDistribution) -> str:
148 dist_version = dist.version
149 if isinstance(dist_version, Version):
150 return f"{dist.raw_name}=={dist_version}"
151 return f"{dist.raw_name}==={dist_version}"
152
153
154def _get_editable_info(dist: BaseDistribution) -> _EditableInfo:
155 """
156 Compute and return values (req, comments) for use in
157 FrozenRequirement.from_dist().
158 """
159 editable_project_location = dist.editable_project_location
160 assert editable_project_location
161 location = os.path.normcase(os.path.abspath(editable_project_location))
162
163 from pip._internal.vcs import RemoteNotFoundError, RemoteNotValidError, vcs
164
165 vcs_backend = vcs.get_backend_for_dir(location)
166
167 if vcs_backend is None:
168 display = _format_as_name_version(dist)
170 'No VCS found for editable requirement "%s" in: %r',
171 display,
172 location,
173 )
174 return _EditableInfo(
175 requirement=location,
176 comments=[f"# Editable install with no version control ({display})"],
177 )
178
179 vcs_name = type(vcs_backend).__name__
180
181 try:
183 except RemoteNotFoundError:
184 display = _format_as_name_version(dist)
185 return _EditableInfo(
186 requirement=location,
187 comments=[f"# Editable {vcs_name} install with no remote ({display})"],
188 )
189 except RemoteNotValidError as ex:
190 display = _format_as_name_version(dist)
191 return _EditableInfo(
192 requirement=location,
193 comments=[
194 f"# Editable {vcs_name} install ({display}) with either a deleted "
195 f"local remote or invalid URI:",
196 f"# '{ex.url}'",
197 ],
198 )
199 except BadCommand:
201 "cannot determine version of editable source in %s "
202 "(%s command not found in path)",
203 location,
205 )
206 return _EditableInfo(requirement=location, comments=[])
207 except InstallationError as exc:
208 logger.warning("Error when trying to get requirement for VCS system %s", exc)
209 else:
210 return _EditableInfo(requirement=req, comments=[])
211
212 logger.warning("Could not determine repository location of %s", location)
213
214 return _EditableInfo(
215 requirement=location,
216 comments=["## !! Could not determine repository location"],
217 )
218
219
222 self,
223 name: str,
224 req: str,
225 editable: bool,
226 comments: Iterable[str] = (),
227 ) -> None:
228 self.name = name
229 self.canonical_name = canonicalize_name(name)
230 self.req = req
231 self.editable = editable
232 self.comments = comments
233
234 @classmethod
235 def from_dist(cls, dist: BaseDistribution) -> "FrozenRequirement":
236 editable = dist.editable
237 if editable:
238 req, comments = _get_editable_info(dist)
239 else:
240 comments = []
241 direct_url = dist.direct_url
242 if direct_url:
243 # if PEP 610 metadata is present, use it
244 req = direct_url_as_pep440_direct_reference(direct_url, dist.raw_name)
245 else:
246 # name==version requirement
247 req = _format_as_name_version(dist)
248
249 return cls(dist.raw_name, req, editable, comments=comments)
250
251 def __str__(self) -> str:
252 req = self.req
253 if self.editable:
254 req = f"-e {req}"
255 return "\n".join(list(self.comments) + [str(req)]) + "\n"
"FrozenRequirement" from_dist(cls, BaseDistribution dist)
Definition freeze.py:235
None __init__(self, str name, str req, bool editable, Iterable[str] comments=())
Definition freeze.py:227
_EditableInfo _get_editable_info(BaseDistribution dist)
Definition freeze.py:154
str _format_as_name_version(BaseDistribution dist)
Definition freeze.py:147
for i