3 from optparse
import Values
4 from typing
import TYPE_CHECKING
, Generator
, List
, Optional
, Sequence
, Tuple
, cast
6 from pip
._vendor
.packaging
.utils
import canonicalize_name
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
21 from pip
._internal
.metadata
.base
import DistributionVersion
23 class _DistWithLatestInfo(BaseDistribution
):
24 """Give the distribution object a couple of extra fields.
26 These will be populated during ``get_outdated()``. This is dirty but
27 makes the rest of the code much cleaner.
30 latest_version
: DistributionVersion
33 _ProcessedDists
= Sequence
[_DistWithLatestInfo
]
36 logger
= logging
.getLogger(__name__
)
39 class ListCommand(IndexGroupCommand
):
41 List installed packages, including editables.
43 Packages are listed in a case-insensitive sorted order.
46 ignore_require_venv
= True
50 def add_options(self
) -> None:
51 self
.cmd_opts
.add_option(
56 help="List outdated packages",
58 self
.cmd_opts
.add_option(
63 help="List uptodate packages",
65 self
.cmd_opts
.add_option(
70 help="List editable projects.",
72 self
.cmd_opts
.add_option(
78 "If in a virtualenv that has global access, do not list "
79 "globally-installed packages."
82 self
.cmd_opts
.add_option(
87 help="Only output packages installed in user-site.",
89 self
.cmd_opts
.add_option(cmdoptions
.list_path())
90 self
.cmd_opts
.add_option(
95 "Include pre-release and development versions. By default, "
96 "pip only finds stable versions."
100 self
.cmd_opts
.add_option(
105 choices
=("columns", "freeze", "json"),
107 "Select the output format among: columns (default), freeze, or json. "
108 "The 'freeze' format cannot be used with the --outdated option."
112 self
.cmd_opts
.add_option(
116 help="List packages that are not dependencies of installed packages.",
119 self
.cmd_opts
.add_option(
120 "--exclude-editable",
121 action
="store_false",
122 dest
="include_editable",
123 help="Exclude editable package from output.",
125 self
.cmd_opts
.add_option(
126 "--include-editable",
128 dest
="include_editable",
129 help="Include editable package from output.",
132 self
.cmd_opts
.add_option(cmdoptions
.list_exclude())
133 index_opts
= cmdoptions
.make_option_group(cmdoptions
.index_group
, self
.parser
)
135 self
.parser
.insert_option_group(0, index_opts
)
136 self
.parser
.insert_option_group(0, self
.cmd_opts
)
138 def _build_package_finder(
139 self
, options
: Values
, session
: PipSession
142 Create a package finder appropriate to this list command.
144 link_collector
= LinkCollector
.create(session
, options
=options
)
146 # Pass allow_yanked=False to ignore yanked versions.
147 selection_prefs
= SelectionPreferences(
149 allow_all_prereleases
=options
.pre
,
152 return PackageFinder
.create(
153 link_collector
=link_collector
,
154 selection_prefs
=selection_prefs
,
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.")
161 if options
.outdated
and options
.list_format
== "freeze":
163 "List format 'freeze' cannot be used with the --outdated option."
166 cmdoptions
.check_list_path_option(options
)
168 skip
= set(stdlib_pkgs
)
170 skip
.update(canonicalize_name(n
) for n
in options
.excludes
)
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
,
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
)
191 packages
= self
.get_outdated(packages
, options
)
192 elif options
.uptodate
:
193 packages
= self
.get_uptodate(packages
, options
)
195 self
.output_package_listing(packages
, options
)
199 self
, packages
: "_ProcessedDists", options
: Values
200 ) -> "_ProcessedDists":
203 for dist
in self
.iter_packages_latest_infos(packages
, options
)
204 if dist
.latest_version
> dist
.version
208 self
, packages
: "_ProcessedDists", options
: Values
209 ) -> "_ProcessedDists":
212 for dist
in self
.iter_packages_latest_infos(packages
, options
)
213 if dist
.latest_version
== dist
.version
216 def get_not_required(
217 self
, packages
: "_ProcessedDists", options
: Values
218 ) -> "_ProcessedDists":
220 canonicalize_name(dep
.name
)
222 for dep
in (dist
.iter_dependencies() or ())
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
228 return list({pkg for pkg in packages if pkg.canonical_name not in dep_keys}
)
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
)
237 dist
: "_DistWithLatestInfo",
238 ) -> Optional
["_DistWithLatestInfo"]:
239 all_candidates
= finder
.find_all_candidates(dist
.canonical_name
)
244 for candidate
in all_candidates
245 if not candidate
.version
.is_prerelease
248 evaluator
= finder
.make_candidate_evaluator(
249 project_name
=dist
.canonical_name
,
251 best_candidate
= evaluator
.sort_best_candidate(all_candidates
)
252 if best_candidate
is None:
255 remote_version
= best_candidate
.version
256 if best_candidate
.link
.is_wheel
:
260 dist
.latest_version
= remote_version
261 dist
.latest_filetype
= typ
264 for dist
in map(latest_info
, packages
):
268 def output_package_listing(
269 self
, packages
: "_ProcessedDists", options
: Values
273 key
=lambda dist
: dist
.canonical_name
,
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:
282 "%s==%s (%s)", dist
.raw_name
, dist
.version
, dist
.location
285 write_output("%s==%s", dist
.raw_name
, dist
.version
)
286 elif options
.list_format
== "json":
287 write_output(format_for_json(packages
, options
))
289 def output_package_listing_columns(
290 self
, data
: List
[List
[str]], header
: List
[str]
292 # insert the header first: we need to know the size of column names
294 data
.insert(0, header
)
296 pkg_strings
, sizes
= tabulate(data
)
298 # Create and add a separator.
300 pkg_strings
.insert(1, " ".join(map(lambda x
: "-" * x
, sizes
)))
302 for val
in pkg_strings
:
306 def format_for_columns(
307 pkgs
: "_ProcessedDists", options
: Values
308 ) -> Tuple
[List
[List
[str]], List
[str]]:
310 Convert the package data into something usable
311 by output_package_listing_columns.
313 header
= ["Package", "Version"]
315 running_outdated
= options
.outdated
317 header
.extend(["Latest", "Type"])
319 has_editables
= any(x
.editable
for x
in pkgs
)
321 header
.append("Editable project location")
323 if options
.verbose
>= 1:
324 header
.append("Location")
325 if options
.verbose
>= 1:
326 header
.append("Installer")
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
)]
335 row
.append(str(proj
.latest_version
))
336 row
.append(proj
.latest_filetype
)
339 row
.append(proj
.editable_project_location
or "")
341 if options
.verbose
>= 1:
342 row
.append(proj
.location
or "")
343 if options
.verbose
>= 1:
344 row
.append(proj
.installer
)
351 def format_for_json(packages
: "_ProcessedDists", options
: Values
) -> str:
353 for dist
in packages
:
355 "name": dist
.raw_name
,
356 "version": str(dist
.version
),
358 if options
.verbose
>= 1:
359 info
["location"] = dist
.location
or ""
360 info
["installer"] = dist
.installer
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
368 return json
.dumps(data
)