123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- from __future__ import absolute_import
- import json
- import logging
- import warnings
- try:
- from itertools import zip_longest
- except ImportError:
- from itertools import izip_longest as zip_longest
- from pip._vendor import six
- from pip.basecommand import Command
- from pip.exceptions import CommandError
- from pip.index import PackageFinder
- from pip.utils import (
- get_installed_distributions, dist_is_editable)
- from pip.utils.deprecation import RemovedInPip10Warning
- from pip.cmdoptions import make_option_group, index_group
- logger = logging.getLogger(__name__)
- class ListCommand(Command):
- """
- List installed packages, including editables.
- Packages are listed in a case-insensitive sorted order.
- """
- name = 'list'
- usage = """
- %prog [options]"""
- summary = 'List installed packages.'
- def __init__(self, *args, **kw):
- super(ListCommand, self).__init__(*args, **kw)
- cmd_opts = self.cmd_opts
- cmd_opts.add_option(
- '-o', '--outdated',
- action='store_true',
- default=False,
- help='List outdated packages')
- cmd_opts.add_option(
- '-u', '--uptodate',
- action='store_true',
- default=False,
- help='List uptodate packages')
- cmd_opts.add_option(
- '-e', '--editable',
- action='store_true',
- default=False,
- help='List editable projects.')
- cmd_opts.add_option(
- '-l', '--local',
- action='store_true',
- default=False,
- help=('If in a virtualenv that has global access, do not list '
- 'globally-installed packages.'),
- )
- self.cmd_opts.add_option(
- '--user',
- dest='user',
- action='store_true',
- default=False,
- help='Only output packages installed in user-site.')
- cmd_opts.add_option(
- '--pre',
- action='store_true',
- default=False,
- help=("Include pre-release and development versions. By default, "
- "pip only finds stable versions."),
- )
- cmd_opts.add_option(
- '--format',
- action='store',
- dest='list_format',
- choices=('legacy', 'columns', 'freeze', 'json'),
- help="Select the output format among: legacy (default), columns, "
- "freeze or json.",
- )
- cmd_opts.add_option(
- '--not-required',
- action='store_true',
- dest='not_required',
- help="List packages that are not dependencies of "
- "installed packages.",
- )
- index_opts = make_option_group(index_group, self.parser)
- self.parser.insert_option_group(0, index_opts)
- self.parser.insert_option_group(0, cmd_opts)
- def _build_package_finder(self, options, index_urls, session):
- """
- Create a package finder appropriate to this list command.
- """
- return PackageFinder(
- find_links=options.find_links,
- index_urls=index_urls,
- allow_all_prereleases=options.pre,
- trusted_hosts=options.trusted_hosts,
- process_dependency_links=options.process_dependency_links,
- session=session,
- )
- def run(self, options, args):
- if options.allow_external:
- warnings.warn(
- "--allow-external has been deprecated and will be removed in "
- "the future. Due to changes in the repository protocol, it no "
- "longer has any effect.",
- RemovedInPip10Warning,
- )
- if options.allow_all_external:
- warnings.warn(
- "--allow-all-external has been deprecated and will be removed "
- "in the future. Due to changes in the repository protocol, it "
- "no longer has any effect.",
- RemovedInPip10Warning,
- )
- if options.allow_unverified:
- warnings.warn(
- "--allow-unverified has been deprecated and will be removed "
- "in the future. Due to changes in the repository protocol, it "
- "no longer has any effect.",
- RemovedInPip10Warning,
- )
- if options.list_format is None:
- warnings.warn(
- "The default format will switch to columns in the future. "
- "You can use --format=(legacy|columns) (or define a "
- "format=(legacy|columns) in your pip.conf under the [list] "
- "section) to disable this warning.",
- RemovedInPip10Warning,
- )
- if options.outdated and options.uptodate:
- raise CommandError(
- "Options --outdated and --uptodate cannot be combined.")
- packages = get_installed_distributions(
- local_only=options.local,
- user_only=options.user,
- editables_only=options.editable,
- )
- if options.outdated:
- packages = self.get_outdated(packages, options)
- elif options.uptodate:
- packages = self.get_uptodate(packages, options)
- if options.not_required:
- packages = self.get_not_required(packages, options)
- self.output_package_listing(packages, options)
- 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
- ]
- def get_not_required(self, packages, options):
- dep_keys = set()
- for dist in packages:
- dep_keys.update(requirement.key for requirement in dist.requires())
- return set(pkg for pkg in packages if pkg.key not in dep_keys)
- def iter_packages_latest_infos(self, packages, options):
- index_urls = [options.index_url] + options.extra_index_urls
- if options.no_index:
- logger.debug('Ignoring indexes: %s', ','.join(index_urls))
- index_urls = []
- dependency_links = []
- for dist in packages:
- if dist.has_metadata('dependency_links.txt'):
- dependency_links.extend(
- dist.get_metadata_lines('dependency_links.txt'),
- )
- with self._build_session(options) as session:
- finder = self._build_package_finder(options, index_urls, session)
- finder.add_dependency_links(dependency_links)
- for dist in packages:
- typ = 'unknown'
- all_candidates = finder.find_all_candidates(dist.key)
- if not options.pre:
- # Remove prereleases
- all_candidates = [candidate for candidate in all_candidates
- if not candidate.version.is_prerelease]
- if not all_candidates:
- continue
- best_candidate = max(all_candidates,
- key=finder._candidate_sort_key)
- remote_version = best_candidate.version
- if best_candidate.location.is_wheel:
- typ = 'wheel'
- else:
- typ = 'sdist'
- # This is dirty but makes the rest of the code much cleaner
- dist.latest_version = remote_version
- dist.latest_filetype = typ
- yield dist
- def output_legacy(self, dist):
- if dist_is_editable(dist):
- return '%s (%s, %s)' % (
- dist.project_name,
- dist.version,
- dist.location,
- )
- else:
- return '%s (%s)' % (dist.project_name, dist.version)
- def output_legacy_latest(self, dist):
- return '%s - Latest: %s [%s]' % (
- self.output_legacy(dist),
- dist.latest_version,
- dist.latest_filetype,
- )
- def output_package_listing(self, packages, options):
- packages = sorted(
- packages,
- key=lambda dist: dist.project_name.lower(),
- )
- if options.list_format == 'columns' and packages:
- data, header = format_for_columns(packages, options)
- self.output_package_listing_columns(data, header)
- elif options.list_format == 'freeze':
- for dist in packages:
- logger.info("%s==%s", dist.project_name, dist.version)
- elif options.list_format == 'json':
- logger.info(format_for_json(packages, options))
- else: # legacy
- for dist in packages:
- if options.outdated:
- logger.info(self.output_legacy_latest(dist))
- else:
- logger.info(self.output_legacy(dist))
- 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:
- logger.info(val)
- 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.
- """
- running_outdated = options.outdated
- # Adjust the header for the `pip list --outdated` case.
- if running_outdated:
- header = ["Package", "Version", "Latest", "Type"]
- else:
- header = ["Package", "Version"]
- data = []
- if any(dist_is_editable(x) for x in pkgs):
- header.append("Location")
- 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:
- row.append(proj.latest_version)
- row.append(proj.latest_filetype)
- if dist_is_editable(proj):
- row.append(proj.location)
- data.append(row)
- return data, header
- def format_for_json(packages, options):
- data = []
- for dist in packages:
- info = {
- 'name': dist.project_name,
- 'version': six.text_type(dist.version),
- }
- 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)
|