]> jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/pip/_internal/commands/list.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / pip / _internal / commands / list.py
1 import json
2 import logging
3 from optparse import Values
4 from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast
5
6 from pip._vendor.packaging.utils import canonicalize_name
7
8 from pip._internal.cli import cmdoptions
9 from pip._internal.cli.req_command import IndexGroupCommand
10 from pip._internal.cli.status_codes import SUCCESS
11 from pip._internal.exceptions import CommandError
12 from pip._internal.index.collector import LinkCollector
13 from pip._internal.index.package_finder import PackageFinder
14 from pip._internal.metadata import BaseDistribution, get_environment
15 from pip._internal.models.selection_prefs import SelectionPreferences
16 from pip._internal.network.session import PipSession
17 from pip._internal.utils.compat import stdlib_pkgs
18 from pip._internal.utils.misc import tabulate, write_output
19
20 if TYPE_CHECKING:
21 from pip._internal.metadata.base import DistributionVersion
22
23 class _DistWithLatestInfo(BaseDistribution):
24 """Give the distribution object a couple of extra fields.
25
26 These will be populated during ``get_outdated()``. This is dirty but
27 makes the rest of the code much cleaner.
28 """
29
30 latest_version: DistributionVersion
31 latest_filetype: str
32
33 _ProcessedDists = Sequence[_DistWithLatestInfo]
34
35
36 logger = logging.getLogger(__name__)
37
38
39 class ListCommand(IndexGroupCommand):
40 """
41 List installed packages, including editables.
42
43 Packages are listed in a case-insensitive sorted order.
44 """
45
46 ignore_require_venv = True
47 usage = """
48 %prog [options]"""
49
50 def add_options(self) -> None:
51 self.cmd_opts.add_option(
52 "-o",
53 "--outdated",
54 action="store_true",
55 default=False,
56 help="List outdated packages",
57 )
58 self.cmd_opts.add_option(
59 "-u",
60 "--uptodate",
61 action="store_true",
62 default=False,
63 help="List uptodate packages",
64 )
65 self.cmd_opts.add_option(
66 "-e",
67 "--editable",
68 action="store_true",
69 default=False,
70 help="List editable projects.",
71 )
72 self.cmd_opts.add_option(
73 "-l",
74 "--local",
75 action="store_true",
76 default=False,
77 help=(
78 "If in a virtualenv that has global access, do not list "
79 "globally-installed packages."
80 ),
81 )
82 self.cmd_opts.add_option(
83 "--user",
84 dest="user",
85 action="store_true",
86 default=False,
87 help="Only output packages installed in user-site.",
88 )
89 self.cmd_opts.add_option(cmdoptions.list_path())
90 self.cmd_opts.add_option(
91 "--pre",
92 action="store_true",
93 default=False,
94 help=(
95 "Include pre-release and development versions. By default, "
96 "pip only finds stable versions."
97 ),
98 )
99
100 self.cmd_opts.add_option(
101 "--format",
102 action="store",
103 dest="list_format",
104 default="columns",
105 choices=("columns", "freeze", "json"),
106 help=(
107 "Select the output format among: columns (default), freeze, or json. "
108 "The 'freeze' format cannot be used with the --outdated option."
109 ),
110 )
111
112 self.cmd_opts.add_option(
113 "--not-required",
114 action="store_true",
115 dest="not_required",
116 help="List packages that are not dependencies of installed packages.",
117 )
118
119 self.cmd_opts.add_option(
120 "--exclude-editable",
121 action="store_false",
122 dest="include_editable",
123 help="Exclude editable package from output.",
124 )
125 self.cmd_opts.add_option(
126 "--include-editable",
127 action="store_true",
128 dest="include_editable",
129 help="Include editable package from output.",
130 default=True,
131 )
132 self.cmd_opts.add_option(cmdoptions.list_exclude())
133 index_opts = cmdoptions.make_option_group(cmdoptions.index_group, self.parser)
134
135 self.parser.insert_option_group(0, index_opts)
136 self.parser.insert_option_group(0, self.cmd_opts)
137
138 def _build_package_finder(
139 self, options: Values, session: PipSession
140 ) -> PackageFinder:
141 """
142 Create a package finder appropriate to this list command.
143 """
144 link_collector = LinkCollector.create(session, options=options)
145
146 # Pass allow_yanked=False to ignore yanked versions.
147 selection_prefs = SelectionPreferences(
148 allow_yanked=False,
149 allow_all_prereleases=options.pre,
150 )
151
152 return PackageFinder.create(
153 link_collector=link_collector,
154 selection_prefs=selection_prefs,
155 )
156
157 def run(self, options: Values, args: List[str]) -> int:
158 if options.outdated and options.uptodate:
159 raise CommandError("Options --outdated and --uptodate cannot be combined.")
160
161 if options.outdated and options.list_format == "freeze":
162 raise CommandError(
163 "List format 'freeze' cannot be used with the --outdated option."
164 )
165
166 cmdoptions.check_list_path_option(options)
167
168 skip = set(stdlib_pkgs)
169 if options.excludes:
170 skip.update(canonicalize_name(n) for n in options.excludes)
171
172 packages: "_ProcessedDists" = [
173 cast("_DistWithLatestInfo", d)
174 for d in get_environment(options.path).iter_installed_distributions(
175 local_only=options.local,
176 user_only=options.user,
177 editables_only=options.editable,
178 include_editables=options.include_editable,
179 skip=skip,
180 )
181 ]
182
183 # get_not_required must be called firstly in order to find and
184 # filter out all dependencies correctly. Otherwise a package
185 # can't be identified as requirement because some parent packages
186 # could be filtered out before.
187 if options.not_required:
188 packages = self.get_not_required(packages, options)
189
190 if options.outdated:
191 packages = self.get_outdated(packages, options)
192 elif options.uptodate:
193 packages = self.get_uptodate(packages, options)
194
195 self.output_package_listing(packages, options)
196 return SUCCESS
197
198 def get_outdated(
199 self, packages: "_ProcessedDists", options: Values
200 ) -> "_ProcessedDists":
201 return [
202 dist
203 for dist in self.iter_packages_latest_infos(packages, options)
204 if dist.latest_version > dist.version
205 ]
206
207 def get_uptodate(
208 self, packages: "_ProcessedDists", options: Values
209 ) -> "_ProcessedDists":
210 return [
211 dist
212 for dist in self.iter_packages_latest_infos(packages, options)
213 if dist.latest_version == dist.version
214 ]
215
216 def get_not_required(
217 self, packages: "_ProcessedDists", options: Values
218 ) -> "_ProcessedDists":
219 dep_keys = {
220 canonicalize_name(dep.name)
221 for dist in packages
222 for dep in (dist.iter_dependencies() or ())
223 }
224
225 # Create a set to remove duplicate packages, and cast it to a list
226 # to keep the return type consistent with get_outdated and
227 # get_uptodate
228 return list({pkg for pkg in packages if pkg.canonical_name not in dep_keys})
229
230 def iter_packages_latest_infos(
231 self, packages: "_ProcessedDists", options: Values
232 ) -> Generator["_DistWithLatestInfo", None, None]:
233 with self._build_session(options) as session:
234 finder = self._build_package_finder(options, session)
235
236 def latest_info(
237 dist: "_DistWithLatestInfo",
238 ) -> Optional["_DistWithLatestInfo"]:
239 all_candidates = finder.find_all_candidates(dist.canonical_name)
240 if not options.pre:
241 # Remove prereleases
242 all_candidates = [
243 candidate
244 for candidate in all_candidates
245 if not candidate.version.is_prerelease
246 ]
247
248 evaluator = finder.make_candidate_evaluator(
249 project_name=dist.canonical_name,
250 )
251 best_candidate = evaluator.sort_best_candidate(all_candidates)
252 if best_candidate is None:
253 return None
254
255 remote_version = best_candidate.version
256 if best_candidate.link.is_wheel:
257 typ = "wheel"
258 else:
259 typ = "sdist"
260 dist.latest_version = remote_version
261 dist.latest_filetype = typ
262 return dist
263
264 for dist in map(latest_info, packages):
265 if dist is not None:
266 yield dist
267
268 def output_package_listing(
269 self, packages: "_ProcessedDists", options: Values
270 ) -> None:
271 packages = sorted(
272 packages,
273 key=lambda dist: dist.canonical_name,
274 )
275 if options.list_format == "columns" and packages:
276 data, header = format_for_columns(packages, options)
277 self.output_package_listing_columns(data, header)
278 elif options.list_format == "freeze":
279 for dist in packages:
280 if options.verbose >= 1:
281 write_output(
282 "%s==%s (%s)", dist.raw_name, dist.version, dist.location
283 )
284 else:
285 write_output("%s==%s", dist.raw_name, dist.version)
286 elif options.list_format == "json":
287 write_output(format_for_json(packages, options))
288
289 def output_package_listing_columns(
290 self, data: List[List[str]], header: List[str]
291 ) -> None:
292 # insert the header first: we need to know the size of column names
293 if len(data) > 0:
294 data.insert(0, header)
295
296 pkg_strings, sizes = tabulate(data)
297
298 # Create and add a separator.
299 if len(data) > 0:
300 pkg_strings.insert(1, " ".join(map(lambda x: "-" * x, sizes)))
301
302 for val in pkg_strings:
303 write_output(val)
304
305
306 def format_for_columns(
307 pkgs: "_ProcessedDists", options: Values
308 ) -> Tuple[List[List[str]], List[str]]:
309 """
310 Convert the package data into something usable
311 by output_package_listing_columns.
312 """
313 header = ["Package", "Version"]
314
315 running_outdated = options.outdated
316 if running_outdated:
317 header.extend(["Latest", "Type"])
318
319 has_editables = any(x.editable for x in pkgs)
320 if has_editables:
321 header.append("Editable project location")
322
323 if options.verbose >= 1:
324 header.append("Location")
325 if options.verbose >= 1:
326 header.append("Installer")
327
328 data = []
329 for proj in pkgs:
330 # if we're working on the 'outdated' list, separate out the
331 # latest_version and type
332 row = [proj.raw_name, str(proj.version)]
333
334 if running_outdated:
335 row.append(str(proj.latest_version))
336 row.append(proj.latest_filetype)
337
338 if has_editables:
339 row.append(proj.editable_project_location or "")
340
341 if options.verbose >= 1:
342 row.append(proj.location or "")
343 if options.verbose >= 1:
344 row.append(proj.installer)
345
346 data.append(row)
347
348 return data, header
349
350
351 def format_for_json(packages: "_ProcessedDists", options: Values) -> str:
352 data = []
353 for dist in packages:
354 info = {
355 "name": dist.raw_name,
356 "version": str(dist.version),
357 }
358 if options.verbose >= 1:
359 info["location"] = dist.location or ""
360 info["installer"] = dist.installer
361 if options.outdated:
362 info["latest_version"] = str(dist.latest_version)
363 info["latest_filetype"] = dist.latest_filetype
364 editable_project_location = dist.editable_project_location
365 if editable_project_location:
366 info["editable_project_location"] = editable_project_location
367 data.append(info)
368 return json.dumps(data)