|
| 1 | +import json |
| 2 | +import sys |
| 3 | +from optparse import Values |
| 4 | +from typing import Any, Dict, List |
| 5 | + |
| 6 | +from pip._vendor.packaging.markers import default_environment |
| 7 | + |
| 8 | +from pip import __version__ |
| 9 | +from pip._internal.cli import cmdoptions |
| 10 | +from pip._internal.cli.req_command import Command |
| 11 | +from pip._internal.cli.status_codes import SUCCESS |
| 12 | +from pip._internal.metadata import BaseDistribution, get_environment |
| 13 | +from pip._internal.utils.compat import stdlib_pkgs |
| 14 | +from pip._internal.utils.urls import path_to_url |
| 15 | + |
| 16 | + |
| 17 | +class InspectCommand(Command): |
| 18 | + """ |
| 19 | + Inspect the content of a Python environment. |
| 20 | + """ |
| 21 | + |
| 22 | + ignore_require_venv = True |
| 23 | + usage = """ |
| 24 | + %prog [options]""" |
| 25 | + |
| 26 | + def add_options(self) -> None: |
| 27 | + self.cmd_opts.add_option( |
| 28 | + "-l", |
| 29 | + "--local", |
| 30 | + action="store_true", |
| 31 | + default=False, |
| 32 | + help=( |
| 33 | + "If in a virtualenv that has global access, do not list " |
| 34 | + "globally-installed packages." |
| 35 | + ), |
| 36 | + ) |
| 37 | + self.cmd_opts.add_option( |
| 38 | + "--user", |
| 39 | + dest="user", |
| 40 | + action="store_true", |
| 41 | + default=False, |
| 42 | + help="Only output packages installed in user-site.", |
| 43 | + ) |
| 44 | + self.cmd_opts.add_option(cmdoptions.list_path()) |
| 45 | + self.parser.insert_option_group(0, self.cmd_opts) |
| 46 | + |
| 47 | + def run(self, options: Values, args: List[str]) -> int: |
| 48 | + cmdoptions.check_list_path_option(options) |
| 49 | + dists = get_environment(options.path).iter_installed_distributions( |
| 50 | + local_only=options.local, |
| 51 | + user_only=options.user, |
| 52 | + skip=set(stdlib_pkgs), |
| 53 | + ) |
| 54 | + output = { |
| 55 | + "version": "0", |
| 56 | + "pip_version": __version__, |
| 57 | + "installed": [self._dist_to_dict(dist) for dist in dists], |
| 58 | + "environment": default_environment(), |
| 59 | + # TODO tags? scheme? |
| 60 | + } |
| 61 | + json.dump(output, sys.stdout, indent=2, ensure_ascii=True) |
| 62 | + return SUCCESS |
| 63 | + |
| 64 | + def _dist_to_dict(self, dist: BaseDistribution) -> Dict[str, Any]: |
| 65 | + res: Dict[str, Any] = { |
| 66 | + "metadata": dist.metadata_dict, |
| 67 | + "metadata_location": dist.info_location, |
| 68 | + } |
| 69 | + # direct_url. Note that we don't have download_info (as in the installation |
| 70 | + # report) since it is not recorded in installed metadata. |
| 71 | + direct_url = dist.direct_url |
| 72 | + if direct_url is not None: |
| 73 | + res["direct_url"] = direct_url.to_dict() |
| 74 | + else: |
| 75 | + # Emulate direct_url for legacy editable installs. |
| 76 | + editable_project_location = dist.editable_project_location |
| 77 | + if editable_project_location is not None: |
| 78 | + res["direct_url"] = { |
| 79 | + "url": path_to_url(editable_project_location), |
| 80 | + "dir_info": { |
| 81 | + "editable": True, |
| 82 | + }, |
| 83 | + } |
| 84 | + # installer |
| 85 | + installer = dist.installer |
| 86 | + if dist.installer: |
| 87 | + res["installer"] = installer |
| 88 | + # requested |
| 89 | + if dist.installed_with_dist_info: |
| 90 | + res["requested"] = dist.requested |
| 91 | + return res |
0 commit comments