Skip to content

Commit 29e7a09

Browse files
committed
Add pip inspect command
1 parent 20ed685 commit 29e7a09

File tree

7 files changed

+211
-0
lines changed

7 files changed

+211
-0
lines changed

docs/html/cli/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pip
1616
1717
pip_install
1818
pip_uninstall
19+
pip_inspect
1920
pip_list
2021
pip_show
2122
pip_freeze

docs/html/cli/pip_inspect.rst

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
.. _`pip inspect`:
2+
3+
===========
4+
pip inspect
5+
===========
6+
7+
8+
9+
Usage
10+
=====
11+
12+
.. tab:: Unix/macOS
13+
14+
.. pip-command-usage:: inspect "python -m pip"
15+
16+
.. tab:: Windows
17+
18+
.. pip-command-usage:: inspect "py -m pip"
19+
20+
21+
Description
22+
===========
23+
24+
.. pip-command-description:: inspect
25+
26+
The format of the JSON output is described in :doc:`../reference/inspect-report`.
27+
28+
29+
Options
30+
=======
31+
32+
.. pip-command-options:: inspect

docs/html/reference/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ interoperability standards that pip utilises/implements.
99
build-system/index
1010
requirement-specifiers
1111
requirements-file-format
12+
inspect-report
1213
```

docs/html/reference/inspect-report.md

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# `pip inspect` JSON output specification
2+
3+
The `pip inspect` command produces a detailed JSON report of the Python
4+
environment, including installed distributions.
5+
6+
## Specification
7+
8+
The report is a JSON object with the following properties:
9+
10+
- `version`: the string `0`, denoting that the inspect command is an experimental
11+
feature. This value will change to `1`, when the feature is deemed stable after
12+
gathering user feedback (likely in pip 22.3 or 23.0). Backward incompatible changes
13+
may be introduced in version `1` without notice. After that, it will change only if
14+
and when backward incompatible changes are introduced, such as removing mandatory
15+
fields or changing the semantics or data type of existing fields. The introduction of
16+
backward incompatible changes will follow the usual pip processes such as the
17+
deprecation cycle or feature flags. Tools must check this field to ensure they support
18+
the corresponding version.
19+
20+
- `pip_version`: a string with the version of pip used to produce the report.
21+
22+
- `installed`: an array of `InspectReportItem` (see below) representing the
23+
distribution packages that are installed.
24+
25+
- `environment`: an object describing the environment where the installation report was
26+
generated. See [PEP 508 environment
27+
markers](https://peps.python.org/pep-0508/#environment-markers) for more information.
28+
Values have a string type.
29+
30+
An `InspectReportItem` is an object describing a (to be) installed distribution
31+
package with the following properties:
32+
33+
- `metadata`: the metadata of the distribution, converted to a JSON object according to
34+
the [PEP 566
35+
transformation](https://www.python.org/dev/peps/pep-0566/#json-compatible-metadata).
36+
37+
- `metadata_location`: the location of the metadata of the installed distribution. Most
38+
of the time this is the `.dist-info` directory. For legacy installs it is the
39+
`.egg-info` directory.
40+
41+
```{warning}
42+
This field may not necessary point to a directory, for instance, in the case of older
43+
`.egg` installs.
44+
```
45+
46+
- `direct_url`: Information about the direct URL that was used for installation, if any,
47+
using the [direct
48+
URL](https://packaging.python.org/en/latest/specifications/direct-url/) data
49+
structure. In most case, this field the `direct_url.json` metadata, except for legacy
50+
editable installs, where it is emulated.
51+
52+
- `requested`: `true` if the `REQUESTED` metadata is present, `false` otherwise. This
53+
field is only present for modern `.dist-info` installations.
54+
55+
```{note}
56+
The `REQUESTED` metadata may not be generated by all installers.
57+
It is generated by pip since version 20.2.
58+
```
59+
60+
- `installer`: the content of the `INSTALLER` metadata, if present and not empty.
61+
62+
## Example
63+
64+
The following command:
65+
66+
```console
67+
pip inspect
68+
```
69+
70+
will produce an output similar to this (metadata abriged for brevity):
71+
72+
```json
73+
{
74+
"version": "0",
75+
"pip_version": "22.2",
76+
"installed": [],
77+
"environment": {}
78+
}
79+
```

news/11245.feature.rst

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add ``pip inspect`` command to obtain the list of installed distributions and other
2+
information about the Python environment, in JSON format.

src/pip/_internal/commands/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@
3838
"FreezeCommand",
3939
"Output installed packages in requirements format.",
4040
),
41+
"inspect": CommandInfo(
42+
"pip._internal.commands.inspect",
43+
"InspectCommand",
44+
"Inspect the python environment.",
45+
),
4146
"list": CommandInfo(
4247
"pip._internal.commands.list",
4348
"ListCommand",

src/pip/_internal/commands/inspect.py

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from optparse import Values
2+
from typing import Any, Dict, List
3+
4+
from pip._vendor import rich
5+
from pip._vendor.packaging.markers import default_environment
6+
from pip._vendor.rich.json import JSON
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+
rich.print(JSON.from_data(output))
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

Comments
 (0)