Skip to content

Commit 3b9abdb

Browse files
committed
Add pip inspect command
1 parent 20ed685 commit 3b9abdb

File tree

8 files changed

+392
-0
lines changed

8 files changed

+392
-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

+216
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
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.dev0",
76+
"installed": [
77+
{
78+
"metadata": {
79+
"metadata_version": "2.1",
80+
"name": "pyparsing",
81+
"version": "3.0.9",
82+
"summary": "pyparsing module - Classes and methods to define and execute parsing grammars",
83+
"description_content_type": "text/x-rst",
84+
"author_email": "Paul McGuire <[email protected]>",
85+
"classifier": [
86+
"Development Status :: 5 - Production/Stable",
87+
"Intended Audience :: Developers",
88+
"Intended Audience :: Information Technology",
89+
"License :: OSI Approved :: MIT License",
90+
"Operating System :: OS Independent",
91+
"Programming Language :: Python",
92+
"Programming Language :: Python :: 3",
93+
"Programming Language :: Python :: 3.6",
94+
"Programming Language :: Python :: 3.7",
95+
"Programming Language :: Python :: 3.8",
96+
"Programming Language :: Python :: 3.9",
97+
"Programming Language :: Python :: 3.10",
98+
"Programming Language :: Python :: 3 :: Only",
99+
"Programming Language :: Python :: Implementation :: CPython",
100+
"Programming Language :: Python :: Implementation :: PyPy",
101+
"Typing :: Typed"
102+
],
103+
"requires_dist": [
104+
"railroad-diagrams ; extra == \"diagrams\"",
105+
"jinja2 ; extra == \"diagrams\""
106+
],
107+
"requires_python": ">=3.6.8",
108+
"project_url": [
109+
"Homepage, https://github.com/pyparsing/pyparsing/"
110+
],
111+
"provides_extra": [
112+
"diagrams"
113+
],
114+
"description": "..."
115+
},
116+
"metadata_location": "/home/me/.virtualenvs/demoenv/lib/python3.8/site-packages/pyparsing-3.0.9.dist-info",
117+
"installer": "pip",
118+
"requested": false
119+
},
120+
{
121+
"metadata": {
122+
"metadata_version": "2.1",
123+
"name": "packaging",
124+
"version": "21.3",
125+
"platform": [
126+
"UNKNOWN"
127+
],
128+
"summary": "Core utilities for Python packages",
129+
"description_content_type": "text/x-rst",
130+
"home_page": "https://github.com/pypa/packaging",
131+
"author": "Donald Stufft and individual contributors",
132+
"author_email": "[email protected]",
133+
"license": "BSD-2-Clause or Apache-2.0",
134+
"classifier": [
135+
"Development Status :: 5 - Production/Stable",
136+
"Intended Audience :: Developers",
137+
"License :: OSI Approved :: Apache Software License",
138+
"License :: OSI Approved :: BSD License",
139+
"Programming Language :: Python",
140+
"Programming Language :: Python :: 3",
141+
"Programming Language :: Python :: 3 :: Only",
142+
"Programming Language :: Python :: 3.6",
143+
"Programming Language :: Python :: 3.7",
144+
"Programming Language :: Python :: 3.8",
145+
"Programming Language :: Python :: 3.9",
146+
"Programming Language :: Python :: 3.10",
147+
"Programming Language :: Python :: Implementation :: CPython",
148+
"Programming Language :: Python :: Implementation :: PyPy"
149+
],
150+
"requires_dist": [
151+
"pyparsing (!=3.0.5,>=2.0.2)"
152+
],
153+
"requires_python": ">=3.6",
154+
"description": "..."
155+
},
156+
"metadata_location": "/home/me/.virtualenvs/demoenv/lib/python3.8/site-packages/packaging-21.3.dist-info",
157+
"installer": "pip",
158+
"requested": true
159+
},
160+
{
161+
"metadata": {
162+
"metadata_version": "2.1",
163+
"name": "pip",
164+
"version": "22.2.dev0",
165+
"summary": "The PyPA recommended tool for installing Python packages.",
166+
"home_page": "https://pip.pypa.io/",
167+
"author": "The pip developers",
168+
"author_email": "[email protected]",
169+
"license": "MIT",
170+
"classifier": [
171+
"Development Status :: 5 - Production/Stable",
172+
"Intended Audience :: Developers",
173+
"License :: OSI Approved :: MIT License",
174+
"Topic :: Software Development :: Build Tools",
175+
"Programming Language :: Python",
176+
"Programming Language :: Python :: 3",
177+
"Programming Language :: Python :: 3 :: Only",
178+
"Programming Language :: Python :: 3.7",
179+
"Programming Language :: Python :: 3.8",
180+
"Programming Language :: Python :: 3.9",
181+
"Programming Language :: Python :: 3.10",
182+
"Programming Language :: Python :: Implementation :: CPython",
183+
"Programming Language :: Python :: Implementation :: PyPy"
184+
],
185+
"requires_python": ">=3.7",
186+
"project_url": [
187+
"Documentation, https://pip.pypa.io",
188+
"Source, https://github.com/pypa/pip",
189+
"Changelog, https://pip.pypa.io/en/stable/news/"
190+
],
191+
"description": "..."
192+
},
193+
"metadata_location": "/home/me/pip/src/pip.egg-info",
194+
"direct_url": {
195+
"url": "file:///home/me/pip/src",
196+
"dir_info": {
197+
"editable": true
198+
}
199+
}
200+
}
201+
],
202+
"environment": {
203+
"implementation_name": "cpython",
204+
"implementation_version": "3.8.10",
205+
"os_name": "posix",
206+
"platform_machine": "x86_64",
207+
"platform_release": "5.13-generic",
208+
"platform_system": "Linux",
209+
"platform_version": "...",
210+
"python_full_version": "3.8.10",
211+
"platform_python_implementation": "CPython",
212+
"python_version": "3.8",
213+
"sys_platform": "linux"
214+
}
215+
}
216+
```

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

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

0 commit comments

Comments
 (0)