2019-09-28 20:12:49 +02:00
|
|
|
# The following comment should be removed at some point in the future.
|
|
|
|
# mypy: disallow-untyped-defs=False
|
|
|
|
|
2014-08-31 01:52:28 +02:00
|
|
|
from __future__ import absolute_import
|
|
|
|
|
2016-05-13 23:50:44 +02:00
|
|
|
import json
|
2014-08-31 01:52:28 +02:00
|
|
|
import logging
|
|
|
|
|
2016-05-13 23:50:44 +02:00
|
|
|
from pip._vendor import six
|
2017-07-17 19:55:58 +02:00
|
|
|
from pip._vendor.six.moves import zip_longest
|
2016-05-13 23:50:44 +02:00
|
|
|
|
2018-07-23 09:31:05 +02:00
|
|
|
from pip._internal.cli import cmdoptions
|
2019-08-07 22:04:02 +02:00
|
|
|
from pip._internal.cli.req_command import IndexGroupCommand
|
2017-08-31 17:48:18 +02:00
|
|
|
from pip._internal.exceptions import CommandError
|
|
|
|
from pip._internal.index import PackageFinder
|
2019-06-28 20:14:55 +02:00
|
|
|
from pip._internal.models.selection_prefs import SelectionPreferences
|
2017-08-31 17:48:18 +02:00
|
|
|
from pip._internal.utils.misc import (
|
2019-07-22 06:45:27 +02:00
|
|
|
dist_is_editable,
|
|
|
|
get_installed_distributions,
|
2019-08-23 02:29:22 +02:00
|
|
|
write_output,
|
2017-08-31 17:48:18 +02:00
|
|
|
)
|
2019-09-14 20:08:32 +02:00
|
|
|
from pip._internal.utils.outdated import make_link_collector
|
2017-08-31 17:48:18 +02:00
|
|
|
from pip._internal.utils.packaging import get_installer
|
2017-05-16 12:16:30 +02:00
|
|
|
|
2014-08-31 01:52:28 +02:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2019-08-07 22:04:02 +02:00
|
|
|
class ListCommand(IndexGroupCommand):
|
2014-03-08 19:03:28 +01:00
|
|
|
"""
|
|
|
|
List installed packages, including editables.
|
|
|
|
|
|
|
|
Packages are listed in a case-insensitive sorted order.
|
|
|
|
"""
|
2019-07-10 09:36:33 +02:00
|
|
|
|
2013-01-18 22:25:15 +01:00
|
|
|
usage = """
|
|
|
|
%prog [options]"""
|
2011-03-19 19:30:56 +01:00
|
|
|
|
2012-12-16 05:26:21 +01:00
|
|
|
def __init__(self, *args, **kw):
|
|
|
|
super(ListCommand, self).__init__(*args, **kw)
|
|
|
|
|
|
|
|
cmd_opts = self.cmd_opts
|
|
|
|
|
|
|
|
cmd_opts.add_option(
|
2012-09-12 19:57:59 +02:00
|
|
|
'-o', '--outdated',
|
|
|
|
action='store_true',
|
|
|
|
default=False,
|
2015-10-09 21:02:20 +02:00
|
|
|
help='List outdated packages')
|
2012-12-16 05:26:21 +01:00
|
|
|
cmd_opts.add_option(
|
2012-09-13 02:09:07 +02:00
|
|
|
'-u', '--uptodate',
|
|
|
|
action='store_true',
|
|
|
|
default=False,
|
2015-10-09 21:02:20 +02:00
|
|
|
help='List uptodate packages')
|
2012-12-16 08:23:20 +01:00
|
|
|
cmd_opts.add_option(
|
2012-12-20 07:08:56 +01:00
|
|
|
'-e', '--editable',
|
2012-12-16 08:23:20 +01:00
|
|
|
action='store_true',
|
|
|
|
default=False,
|
2013-01-18 22:25:15 +01:00
|
|
|
help='List editable projects.')
|
2012-12-16 08:31:39 +01:00
|
|
|
cmd_opts.add_option(
|
|
|
|
'-l', '--local',
|
|
|
|
action='store_true',
|
|
|
|
default=False,
|
2014-01-27 15:07:10 +01:00
|
|
|
help=('If in a virtualenv that has global access, do not list '
|
|
|
|
'globally-installed packages.'),
|
|
|
|
)
|
2014-10-14 16:46:57 +02:00
|
|
|
self.cmd_opts.add_option(
|
|
|
|
'--user',
|
|
|
|
dest='user',
|
|
|
|
action='store_true',
|
|
|
|
default=False,
|
|
|
|
help='Only output packages installed in user-site.')
|
2019-06-12 23:09:00 +02:00
|
|
|
cmd_opts.add_option(cmdoptions.list_path())
|
2013-06-24 19:40:04 +02:00
|
|
|
cmd_opts.add_option(
|
|
|
|
'--pre',
|
|
|
|
action='store_true',
|
|
|
|
default=False,
|
2014-01-27 15:07:10 +01:00
|
|
|
help=("Include pre-release and development versions. By default, "
|
|
|
|
"pip only finds stable versions."),
|
|
|
|
)
|
2013-06-24 19:40:04 +02:00
|
|
|
|
2016-05-13 23:50:44 +02:00
|
|
|
cmd_opts.add_option(
|
|
|
|
'--format',
|
|
|
|
action='store',
|
|
|
|
dest='list_format',
|
2017-03-20 03:08:31 +01:00
|
|
|
default="columns",
|
2018-06-21 15:42:15 +02:00
|
|
|
choices=('columns', 'freeze', 'json'),
|
2017-03-20 03:08:31 +01:00
|
|
|
help="Select the output format among: columns (default), freeze, "
|
2018-06-21 15:42:15 +02:00
|
|
|
"or json",
|
2016-05-13 23:50:44 +02:00
|
|
|
)
|
|
|
|
|
2016-09-22 11:02:05 +02:00
|
|
|
cmd_opts.add_option(
|
2016-10-05 14:03:03 +02:00
|
|
|
'--not-required',
|
2016-09-22 11:02:05 +02:00
|
|
|
action='store_true',
|
2016-10-05 14:03:03 +02:00
|
|
|
dest='not_required',
|
2016-09-28 10:18:37 +02:00
|
|
|
help="List packages that are not dependencies of "
|
|
|
|
"installed packages.",
|
2016-09-22 11:02:05 +02:00
|
|
|
)
|
|
|
|
|
2017-01-31 13:06:47 +01:00
|
|
|
cmd_opts.add_option(
|
|
|
|
'--exclude-editable',
|
|
|
|
action='store_false',
|
|
|
|
dest='include_editable',
|
|
|
|
help='Exclude editable package from output.',
|
|
|
|
)
|
|
|
|
cmd_opts.add_option(
|
|
|
|
'--include-editable',
|
|
|
|
action='store_true',
|
|
|
|
dest='include_editable',
|
|
|
|
help='Include editable package from output.',
|
|
|
|
default=True,
|
|
|
|
)
|
2018-07-23 09:30:10 +02:00
|
|
|
index_opts = cmdoptions.make_option_group(
|
|
|
|
cmdoptions.index_group, self.parser
|
|
|
|
)
|
2012-12-16 05:26:21 +01:00
|
|
|
|
|
|
|
self.parser.insert_option_group(0, index_opts)
|
|
|
|
self.parser.insert_option_group(0, cmd_opts)
|
|
|
|
|
2019-06-21 18:10:03 +02:00
|
|
|
def _build_package_finder(self, options, session):
|
2011-07-09 12:34:15 +02:00
|
|
|
"""
|
2012-10-01 22:23:49 +02:00
|
|
|
Create a package finder appropriate to this list command.
|
2011-07-09 12:34:15 +02:00
|
|
|
"""
|
2019-09-14 20:08:32 +02:00
|
|
|
link_collector = make_link_collector(session, options=options)
|
2019-06-21 18:10:03 +02:00
|
|
|
|
2019-06-27 20:50:27 +02:00
|
|
|
# Pass allow_yanked=False to ignore yanked versions.
|
2019-06-28 20:14:55 +02:00
|
|
|
selection_prefs = SelectionPreferences(
|
2019-06-27 20:50:27 +02:00
|
|
|
allow_yanked=False,
|
2014-01-27 15:07:10 +01:00
|
|
|
allow_all_prereleases=options.pre,
|
2019-06-28 20:14:55 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
return PackageFinder.create(
|
2019-09-14 20:08:32 +02:00
|
|
|
link_collector=link_collector,
|
2019-06-28 20:14:55 +02:00
|
|
|
selection_prefs=selection_prefs,
|
2014-01-27 15:07:10 +01:00
|
|
|
)
|
2011-03-19 19:30:56 +01:00
|
|
|
|
|
|
|
def run(self, options, args):
|
2015-10-09 21:02:20 +02:00
|
|
|
if options.outdated and options.uptodate:
|
|
|
|
raise CommandError(
|
|
|
|
"Options --outdated and --uptodate cannot be combined.")
|
2015-09-03 05:08:11 +02:00
|
|
|
|
2019-06-12 23:09:00 +02:00
|
|
|
cmdoptions.check_list_path_option(options)
|
2019-06-08 00:05:34 +02:00
|
|
|
|
2016-05-14 11:30:10 +02:00
|
|
|
packages = get_installed_distributions(
|
|
|
|
local_only=options.local,
|
|
|
|
user_only=options.user,
|
|
|
|
editables_only=options.editable,
|
2017-01-31 13:06:47 +01:00
|
|
|
include_editables=options.include_editable,
|
2019-06-08 00:05:34 +02:00
|
|
|
paths=options.path,
|
2016-05-14 11:30:10 +02:00
|
|
|
)
|
2016-09-22 11:02:05 +02:00
|
|
|
|
2019-01-07 17:10:08 +01:00
|
|
|
# get_not_required must be called firstly in order to find and
|
|
|
|
# filter out all dependencies correctly. Otherwise a package
|
|
|
|
# can't be identified as requirement because some parent packages
|
|
|
|
# could be filtered out before.
|
2019-01-05 21:58:16 +01:00
|
|
|
if options.not_required:
|
|
|
|
packages = self.get_not_required(packages, options)
|
|
|
|
|
2012-09-12 19:57:59 +02:00
|
|
|
if options.outdated:
|
2016-05-14 11:30:10 +02:00
|
|
|
packages = self.get_outdated(packages, options)
|
2012-09-13 02:09:07 +02:00
|
|
|
elif options.uptodate:
|
2016-05-14 11:30:10 +02:00
|
|
|
packages = self.get_uptodate(packages, options)
|
2016-09-23 11:09:56 +02:00
|
|
|
|
2016-05-13 23:50:44 +02:00
|
|
|
self.output_package_listing(packages, options)
|
2012-09-12 19:57:59 +02:00
|
|
|
|
2016-05-14 11:30:10 +02:00
|
|
|
def get_outdated(self, packages, options):
|
|
|
|
return [
|
|
|
|
dist for dist in self.iter_packages_latest_infos(packages, options)
|
|
|
|
if dist.latest_version > dist.parsed_version
|
|
|
|
]
|
|
|
|
|
|
|
|
def get_uptodate(self, packages, options):
|
|
|
|
return [
|
|
|
|
dist for dist in self.iter_packages_latest_infos(packages, options)
|
|
|
|
if dist.latest_version == dist.parsed_version
|
|
|
|
]
|
|
|
|
|
2016-10-05 14:03:03 +02:00
|
|
|
def get_not_required(self, packages, options):
|
2016-09-24 09:45:13 +02:00
|
|
|
dep_keys = set()
|
2016-09-30 14:07:10 +02:00
|
|
|
for dist in packages:
|
2016-09-24 09:45:13 +02:00
|
|
|
dep_keys.update(requirement.key for requirement in dist.requires())
|
2017-12-15 06:58:30 +01:00
|
|
|
return {pkg for pkg in packages if pkg.key not in dep_keys}
|
2016-09-22 11:02:05 +02:00
|
|
|
|
2016-05-14 11:30:10 +02:00
|
|
|
def iter_packages_latest_infos(self, packages, options):
|
2014-06-17 22:24:42 +02:00
|
|
|
with self._build_session(options) as session:
|
2019-06-21 18:10:03 +02:00
|
|
|
finder = self._build_package_finder(options, session)
|
2013-08-16 14:04:27 +02:00
|
|
|
|
2016-05-14 11:30:10 +02:00
|
|
|
for dist in packages:
|
2015-03-04 15:02:06 +01:00
|
|
|
typ = 'unknown'
|
2015-11-05 23:20:21 +01:00
|
|
|
all_candidates = finder.find_all_candidates(dist.key)
|
2016-01-20 15:12:00 +01:00
|
|
|
if not options.pre:
|
|
|
|
# Remove prereleases
|
|
|
|
all_candidates = [candidate for candidate in all_candidates
|
|
|
|
if not candidate.version.is_prerelease]
|
|
|
|
|
2019-07-14 18:24:59 +02:00
|
|
|
evaluator = finder.make_candidate_evaluator(
|
|
|
|
project_name=dist.project_name,
|
|
|
|
)
|
2019-07-20 04:07:49 +02:00
|
|
|
best_candidate = evaluator.sort_best_candidate(all_candidates)
|
2019-04-22 12:40:35 +02:00
|
|
|
if best_candidate is None:
|
2011-07-13 15:36:49 +02:00
|
|
|
continue
|
2019-04-22 12:40:35 +02:00
|
|
|
|
2015-11-05 23:20:21 +01:00
|
|
|
remote_version = best_candidate.version
|
2019-07-20 09:11:22 +02:00
|
|
|
if best_candidate.link.is_wheel:
|
2015-11-05 23:20:21 +01:00
|
|
|
typ = 'wheel'
|
2014-06-17 22:24:42 +02:00
|
|
|
else:
|
2015-11-05 23:20:21 +01:00
|
|
|
typ = 'sdist'
|
2016-05-13 23:50:44 +02:00
|
|
|
# This is dirty but makes the rest of the code much cleaner
|
|
|
|
dist.latest_version = remote_version
|
|
|
|
dist.latest_filetype = typ
|
|
|
|
yield dist
|
2012-09-13 00:50:25 +02:00
|
|
|
|
2016-05-13 23:50:44 +02:00
|
|
|
def output_package_listing(self, packages, options):
|
|
|
|
packages = sorted(
|
|
|
|
packages,
|
2014-01-27 15:07:10 +01:00
|
|
|
key=lambda dist: dist.project_name.lower(),
|
|
|
|
)
|
2016-05-13 23:50:44 +02:00
|
|
|
if options.list_format == 'columns' and packages:
|
|
|
|
data, header = format_for_columns(packages, options)
|
2016-05-12 19:13:48 +02:00
|
|
|
self.output_package_listing_columns(data, header)
|
2016-05-13 23:50:44 +02:00
|
|
|
elif options.list_format == 'freeze':
|
|
|
|
for dist in packages:
|
2017-04-03 12:34:15 +02:00
|
|
|
if options.verbose >= 1:
|
2019-08-23 02:29:22 +02:00
|
|
|
write_output("%s==%s (%s)", dist.project_name,
|
|
|
|
dist.version, dist.location)
|
2017-04-02 12:45:04 +02:00
|
|
|
else:
|
2019-08-23 02:29:22 +02:00
|
|
|
write_output("%s==%s", dist.project_name, dist.version)
|
2016-05-13 23:50:44 +02:00
|
|
|
elif options.list_format == 'json':
|
2019-08-23 02:29:22 +02:00
|
|
|
write_output(format_for_json(packages, options))
|
2016-05-12 19:13:48 +02:00
|
|
|
|
|
|
|
def output_package_listing_columns(self, data, header):
|
|
|
|
# insert the header first: we need to know the size of column names
|
|
|
|
if len(data) > 0:
|
|
|
|
data.insert(0, header)
|
|
|
|
|
|
|
|
pkg_strings, sizes = tabulate(data)
|
|
|
|
|
|
|
|
# Create and add a separator.
|
|
|
|
if len(data) > 0:
|
|
|
|
pkg_strings.insert(1, " ".join(map(lambda x: '-' * x, sizes)))
|
|
|
|
|
|
|
|
for val in pkg_strings:
|
2019-08-23 02:29:22 +02:00
|
|
|
write_output(val)
|
2012-09-13 00:50:25 +02:00
|
|
|
|
2016-05-12 19:13:48 +02:00
|
|
|
|
|
|
|
def tabulate(vals):
|
|
|
|
# From pfmoore on GitHub:
|
|
|
|
# https://github.com/pypa/pip/issues/3651#issuecomment-216932564
|
|
|
|
assert len(vals) > 0
|
|
|
|
|
|
|
|
sizes = [0] * max(len(x) for x in vals)
|
|
|
|
for row in vals:
|
|
|
|
sizes = [max(s, len(str(c))) for s, c in zip_longest(sizes, row)]
|
|
|
|
|
|
|
|
result = []
|
|
|
|
for row in vals:
|
|
|
|
display = " ".join([str(c).ljust(s) if c is not None else ''
|
|
|
|
for s, c in zip_longest(sizes, row)])
|
|
|
|
result.append(display)
|
|
|
|
|
|
|
|
return result, sizes
|
|
|
|
|
|
|
|
|
|
|
|
def format_for_columns(pkgs, options):
|
|
|
|
"""
|
|
|
|
Convert the package data into something usable
|
|
|
|
by output_package_listing_columns.
|
|
|
|
"""
|
2016-05-13 23:50:44 +02:00
|
|
|
running_outdated = options.outdated
|
2016-05-12 19:13:48 +02:00
|
|
|
# Adjust the header for the `pip list --outdated` case.
|
2016-05-13 23:50:44 +02:00
|
|
|
if running_outdated:
|
2016-05-12 19:13:48 +02:00
|
|
|
header = ["Package", "Version", "Latest", "Type"]
|
2016-05-13 23:50:44 +02:00
|
|
|
else:
|
|
|
|
header = ["Package", "Version"]
|
2016-05-12 19:13:48 +02:00
|
|
|
|
|
|
|
data = []
|
2017-04-03 12:34:15 +02:00
|
|
|
if options.verbose >= 1 or any(dist_is_editable(x) for x in pkgs):
|
2016-05-12 19:13:48 +02:00
|
|
|
header.append("Location")
|
2017-04-06 15:16:18 +02:00
|
|
|
if options.verbose >= 1:
|
|
|
|
header.append("Installer")
|
2016-05-12 19:13:48 +02:00
|
|
|
|
|
|
|
for proj in pkgs:
|
|
|
|
# if we're working on the 'outdated' list, separate out the
|
|
|
|
# latest_version and type
|
|
|
|
row = [proj.project_name, proj.version]
|
|
|
|
|
|
|
|
if running_outdated:
|
2016-05-13 23:50:44 +02:00
|
|
|
row.append(proj.latest_version)
|
|
|
|
row.append(proj.latest_filetype)
|
2016-05-12 19:13:48 +02:00
|
|
|
|
2017-04-03 12:34:15 +02:00
|
|
|
if options.verbose >= 1 or dist_is_editable(proj):
|
2016-05-12 19:13:48 +02:00
|
|
|
row.append(proj.location)
|
2017-04-06 15:16:18 +02:00
|
|
|
if options.verbose >= 1:
|
|
|
|
row.append(get_installer(proj))
|
2016-05-12 19:13:48 +02:00
|
|
|
|
|
|
|
data.append(row)
|
|
|
|
|
|
|
|
return data, header
|
2016-05-14 11:30:10 +02:00
|
|
|
|
|
|
|
|
|
|
|
def format_for_json(packages, options):
|
|
|
|
data = []
|
|
|
|
for dist in packages:
|
|
|
|
info = {
|
|
|
|
'name': dist.project_name,
|
|
|
|
'version': six.text_type(dist.version),
|
|
|
|
}
|
2017-04-03 12:34:15 +02:00
|
|
|
if options.verbose >= 1:
|
2017-04-02 12:45:04 +02:00
|
|
|
info['location'] = dist.location
|
2017-04-06 15:16:18 +02:00
|
|
|
info['installer'] = get_installer(dist)
|
2016-05-14 11:30:10 +02:00
|
|
|
if options.outdated:
|
|
|
|
info['latest_version'] = six.text_type(dist.latest_version)
|
|
|
|
info['latest_filetype'] = dist.latest_filetype
|
|
|
|
data.append(info)
|
|
|
|
return json.dumps(data)
|