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