You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

list.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. from __future__ import absolute_import
  2. import json
  3. import logging
  4. import warnings
  5. from pip._vendor import six
  6. from pip._vendor.six.moves import zip_longest
  7. from pip._internal.basecommand import Command
  8. from pip._internal.cmdoptions import index_group, make_option_group
  9. from pip._internal.exceptions import CommandError
  10. from pip._internal.index import PackageFinder
  11. from pip._internal.utils.deprecation import RemovedInPip11Warning
  12. from pip._internal.utils.misc import (
  13. dist_is_editable, get_installed_distributions,
  14. )
  15. from pip._internal.utils.packaging import get_installer
  16. logger = logging.getLogger(__name__)
  17. class ListCommand(Command):
  18. """
  19. List installed packages, including editables.
  20. Packages are listed in a case-insensitive sorted order.
  21. """
  22. name = 'list'
  23. usage = """
  24. %prog [options]"""
  25. summary = 'List installed packages.'
  26. def __init__(self, *args, **kw):
  27. super(ListCommand, self).__init__(*args, **kw)
  28. cmd_opts = self.cmd_opts
  29. cmd_opts.add_option(
  30. '-o', '--outdated',
  31. action='store_true',
  32. default=False,
  33. help='List outdated packages')
  34. cmd_opts.add_option(
  35. '-u', '--uptodate',
  36. action='store_true',
  37. default=False,
  38. help='List uptodate packages')
  39. cmd_opts.add_option(
  40. '-e', '--editable',
  41. action='store_true',
  42. default=False,
  43. help='List editable projects.')
  44. cmd_opts.add_option(
  45. '-l', '--local',
  46. action='store_true',
  47. default=False,
  48. help=('If in a virtualenv that has global access, do not list '
  49. 'globally-installed packages.'),
  50. )
  51. self.cmd_opts.add_option(
  52. '--user',
  53. dest='user',
  54. action='store_true',
  55. default=False,
  56. help='Only output packages installed in user-site.')
  57. cmd_opts.add_option(
  58. '--pre',
  59. action='store_true',
  60. default=False,
  61. help=("Include pre-release and development versions. By default, "
  62. "pip only finds stable versions."),
  63. )
  64. cmd_opts.add_option(
  65. '--format',
  66. action='store',
  67. dest='list_format',
  68. default="columns",
  69. choices=('legacy', 'columns', 'freeze', 'json'),
  70. help="Select the output format among: columns (default), freeze, "
  71. "json, or legacy.",
  72. )
  73. cmd_opts.add_option(
  74. '--not-required',
  75. action='store_true',
  76. dest='not_required',
  77. help="List packages that are not dependencies of "
  78. "installed packages.",
  79. )
  80. cmd_opts.add_option(
  81. '--exclude-editable',
  82. action='store_false',
  83. dest='include_editable',
  84. help='Exclude editable package from output.',
  85. )
  86. cmd_opts.add_option(
  87. '--include-editable',
  88. action='store_true',
  89. dest='include_editable',
  90. help='Include editable package from output.',
  91. default=True,
  92. )
  93. index_opts = make_option_group(index_group, self.parser)
  94. self.parser.insert_option_group(0, index_opts)
  95. self.parser.insert_option_group(0, cmd_opts)
  96. def _build_package_finder(self, options, index_urls, session):
  97. """
  98. Create a package finder appropriate to this list command.
  99. """
  100. return PackageFinder(
  101. find_links=options.find_links,
  102. index_urls=index_urls,
  103. allow_all_prereleases=options.pre,
  104. trusted_hosts=options.trusted_hosts,
  105. process_dependency_links=options.process_dependency_links,
  106. session=session,
  107. )
  108. def run(self, options, args):
  109. if options.list_format == "legacy":
  110. warnings.warn(
  111. "The legacy format has been deprecated and will be removed "
  112. "in the future.",
  113. RemovedInPip11Warning,
  114. )
  115. if options.outdated and options.uptodate:
  116. raise CommandError(
  117. "Options --outdated and --uptodate cannot be combined.")
  118. packages = get_installed_distributions(
  119. local_only=options.local,
  120. user_only=options.user,
  121. editables_only=options.editable,
  122. include_editables=options.include_editable,
  123. )
  124. if options.outdated:
  125. packages = self.get_outdated(packages, options)
  126. elif options.uptodate:
  127. packages = self.get_uptodate(packages, options)
  128. if options.not_required:
  129. packages = self.get_not_required(packages, options)
  130. self.output_package_listing(packages, options)
  131. def get_outdated(self, packages, options):
  132. return [
  133. dist for dist in self.iter_packages_latest_infos(packages, options)
  134. if dist.latest_version > dist.parsed_version
  135. ]
  136. def get_uptodate(self, packages, options):
  137. return [
  138. dist for dist in self.iter_packages_latest_infos(packages, options)
  139. if dist.latest_version == dist.parsed_version
  140. ]
  141. def get_not_required(self, packages, options):
  142. dep_keys = set()
  143. for dist in packages:
  144. dep_keys.update(requirement.key for requirement in dist.requires())
  145. return {pkg for pkg in packages if pkg.key not in dep_keys}
  146. def iter_packages_latest_infos(self, packages, options):
  147. index_urls = [options.index_url] + options.extra_index_urls
  148. if options.no_index:
  149. logger.debug('Ignoring indexes: %s', ','.join(index_urls))
  150. index_urls = []
  151. dependency_links = []
  152. for dist in packages:
  153. if dist.has_metadata('dependency_links.txt'):
  154. dependency_links.extend(
  155. dist.get_metadata_lines('dependency_links.txt'),
  156. )
  157. with self._build_session(options) as session:
  158. finder = self._build_package_finder(options, index_urls, session)
  159. finder.add_dependency_links(dependency_links)
  160. for dist in packages:
  161. typ = 'unknown'
  162. all_candidates = finder.find_all_candidates(dist.key)
  163. if not options.pre:
  164. # Remove prereleases
  165. all_candidates = [candidate for candidate in all_candidates
  166. if not candidate.version.is_prerelease]
  167. if not all_candidates:
  168. continue
  169. best_candidate = max(all_candidates,
  170. key=finder._candidate_sort_key)
  171. remote_version = best_candidate.version
  172. if best_candidate.location.is_wheel:
  173. typ = 'wheel'
  174. else:
  175. typ = 'sdist'
  176. # This is dirty but makes the rest of the code much cleaner
  177. dist.latest_version = remote_version
  178. dist.latest_filetype = typ
  179. yield dist
  180. def output_legacy(self, dist, options):
  181. if options.verbose >= 1:
  182. return '%s (%s, %s, %s)' % (
  183. dist.project_name,
  184. dist.version,
  185. dist.location,
  186. get_installer(dist),
  187. )
  188. elif dist_is_editable(dist):
  189. return '%s (%s, %s)' % (
  190. dist.project_name,
  191. dist.version,
  192. dist.location,
  193. )
  194. else:
  195. return '%s (%s)' % (dist.project_name, dist.version)
  196. def output_legacy_latest(self, dist, options):
  197. return '%s - Latest: %s [%s]' % (
  198. self.output_legacy(dist, options),
  199. dist.latest_version,
  200. dist.latest_filetype,
  201. )
  202. def output_package_listing(self, packages, options):
  203. packages = sorted(
  204. packages,
  205. key=lambda dist: dist.project_name.lower(),
  206. )
  207. if options.list_format == 'columns' and packages:
  208. data, header = format_for_columns(packages, options)
  209. self.output_package_listing_columns(data, header)
  210. elif options.list_format == 'freeze':
  211. for dist in packages:
  212. if options.verbose >= 1:
  213. logger.info("%s==%s (%s)", dist.project_name,
  214. dist.version, dist.location)
  215. else:
  216. logger.info("%s==%s", dist.project_name, dist.version)
  217. elif options.list_format == 'json':
  218. logger.info(format_for_json(packages, options))
  219. elif options.list_format == "legacy":
  220. for dist in packages:
  221. if options.outdated:
  222. logger.info(self.output_legacy_latest(dist, options))
  223. else:
  224. logger.info(self.output_legacy(dist, options))
  225. def output_package_listing_columns(self, data, header):
  226. # insert the header first: we need to know the size of column names
  227. if len(data) > 0:
  228. data.insert(0, header)
  229. pkg_strings, sizes = tabulate(data)
  230. # Create and add a separator.
  231. if len(data) > 0:
  232. pkg_strings.insert(1, " ".join(map(lambda x: '-' * x, sizes)))
  233. for val in pkg_strings:
  234. logger.info(val)
  235. def tabulate(vals):
  236. # From pfmoore on GitHub:
  237. # https://github.com/pypa/pip/issues/3651#issuecomment-216932564
  238. assert len(vals) > 0
  239. sizes = [0] * max(len(x) for x in vals)
  240. for row in vals:
  241. sizes = [max(s, len(str(c))) for s, c in zip_longest(sizes, row)]
  242. result = []
  243. for row in vals:
  244. display = " ".join([str(c).ljust(s) if c is not None else ''
  245. for s, c in zip_longest(sizes, row)])
  246. result.append(display)
  247. return result, sizes
  248. def format_for_columns(pkgs, options):
  249. """
  250. Convert the package data into something usable
  251. by output_package_listing_columns.
  252. """
  253. running_outdated = options.outdated
  254. # Adjust the header for the `pip list --outdated` case.
  255. if running_outdated:
  256. header = ["Package", "Version", "Latest", "Type"]
  257. else:
  258. header = ["Package", "Version"]
  259. data = []
  260. if options.verbose >= 1 or any(dist_is_editable(x) for x in pkgs):
  261. header.append("Location")
  262. if options.verbose >= 1:
  263. header.append("Installer")
  264. for proj in pkgs:
  265. # if we're working on the 'outdated' list, separate out the
  266. # latest_version and type
  267. row = [proj.project_name, proj.version]
  268. if running_outdated:
  269. row.append(proj.latest_version)
  270. row.append(proj.latest_filetype)
  271. if options.verbose >= 1 or dist_is_editable(proj):
  272. row.append(proj.location)
  273. if options.verbose >= 1:
  274. row.append(get_installer(proj))
  275. data.append(row)
  276. return data, header
  277. def format_for_json(packages, options):
  278. data = []
  279. for dist in packages:
  280. info = {
  281. 'name': dist.project_name,
  282. 'version': six.text_type(dist.version),
  283. }
  284. if options.verbose >= 1:
  285. info['location'] = dist.location
  286. info['installer'] = get_installer(dist)
  287. if options.outdated:
  288. info['latest_version'] = six.text_type(dist.latest_version)
  289. info['latest_filetype'] = dist.latest_filetype
  290. data.append(info)
  291. return json.dumps(data)