Skip to content

Commit 5851364

Browse files
committed
Add pip inspect command
1 parent 7f46f94 commit 5851364

File tree

8 files changed

+389
-0
lines changed

8 files changed

+389
-0
lines changed

docs/html/cli/index.md

Lines changed: 1 addition & 0 deletions
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

Lines changed: 32 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ build-system/index
1010
requirement-specifiers
1111
requirements-file-format
1212
installation-report
13+
inspect-report
1314
```

docs/html/reference/inspect-report.md

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
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](InspectReportItem) 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+
(InspectReportItem)=
31+
32+
An `InspectReportItem` is an object describing an installed distribution package with
33+
the following properties:
34+
35+
- `metadata`: the metadata of the distribution, converted to a JSON object according to
36+
the [PEP 566
37+
transformation](https://www.python.org/dev/peps/pep-0566/#json-compatible-metadata).
38+
39+
- `metadata_location`: the location of the metadata of the installed distribution. Most
40+
of the time this is the `.dist-info` directory. For legacy installs it is the
41+
`.egg-info` directory.
42+
43+
```{warning}
44+
This field may not necessary point to a directory, for instance, in the case of older
45+
`.egg` installs.
46+
```
47+
48+
- `direct_url`: Information about the direct URL that was used for installation, if any,
49+
using the [direct
50+
URL](https://packaging.python.org/en/latest/specifications/direct-url/) data
51+
structure. In most case, this field corresponds to the `direct_url.json` metadata,
52+
except for legacy editable installs, where it is emulated.
53+
54+
- `requested`: `true` if the `REQUESTED` metadata is present, `false` otherwise. This
55+
field is only present for modern `.dist-info` installations.
56+
57+
```{note}
58+
The `REQUESTED` metadata may not be generated by all installers.
59+
It is generated by pip since version 20.2.
60+
```
61+
62+
- `installer`: the content of the `INSTALLER` metadata, if present and not empty.
63+
64+
## Example
65+
66+
Running the ``pip inspect`` command, in an environment where `pip` is installed in
67+
editable mode and `packaging` is installed as well, will produce an output similar to
68+
this (metadata abriged for brevity):
69+
70+
```json
71+
{
72+
"version": "0",
73+
"pip_version": "22.2.dev0",
74+
"installed": [
75+
{
76+
"metadata": {
77+
"metadata_version": "2.1",
78+
"name": "pyparsing",
79+
"version": "3.0.9",
80+
"summary": "pyparsing module - Classes and methods to define and execute parsing grammars",
81+
"description_content_type": "text/x-rst",
82+
"author_email": "Paul McGuire <[email protected]>",
83+
"classifier": [
84+
"Development Status :: 5 - Production/Stable",
85+
"Intended Audience :: Developers",
86+
"Intended Audience :: Information Technology",
87+
"License :: OSI Approved :: MIT License",
88+
"Operating System :: OS Independent",
89+
"Programming Language :: Python",
90+
"Programming Language :: Python :: 3",
91+
"Programming Language :: Python :: 3.6",
92+
"Programming Language :: Python :: 3.7",
93+
"Programming Language :: Python :: 3.8",
94+
"Programming Language :: Python :: 3.9",
95+
"Programming Language :: Python :: 3.10",
96+
"Programming Language :: Python :: 3 :: Only",
97+
"Programming Language :: Python :: Implementation :: CPython",
98+
"Programming Language :: Python :: Implementation :: PyPy",
99+
"Typing :: Typed"
100+
],
101+
"requires_dist": [
102+
"railroad-diagrams ; extra == \"diagrams\"",
103+
"jinja2 ; extra == \"diagrams\""
104+
],
105+
"requires_python": ">=3.6.8",
106+
"project_url": [
107+
"Homepage, https://github.com/pyparsing/pyparsing/"
108+
],
109+
"provides_extra": [
110+
"diagrams"
111+
],
112+
"description": "..."
113+
},
114+
"metadata_location": "/home/me/.virtualenvs/demoenv/lib/python3.8/site-packages/pyparsing-3.0.9.dist-info",
115+
"installer": "pip",
116+
"requested": false
117+
},
118+
{
119+
"metadata": {
120+
"metadata_version": "2.1",
121+
"name": "packaging",
122+
"version": "21.3",
123+
"platform": [
124+
"UNKNOWN"
125+
],
126+
"summary": "Core utilities for Python packages",
127+
"description_content_type": "text/x-rst",
128+
"home_page": "https://github.com/pypa/packaging",
129+
"author": "Donald Stufft and individual contributors",
130+
"author_email": "[email protected]",
131+
"license": "BSD-2-Clause or Apache-2.0",
132+
"classifier": [
133+
"Development Status :: 5 - Production/Stable",
134+
"Intended Audience :: Developers",
135+
"License :: OSI Approved :: Apache Software License",
136+
"License :: OSI Approved :: BSD License",
137+
"Programming Language :: Python",
138+
"Programming Language :: Python :: 3",
139+
"Programming Language :: Python :: 3 :: Only",
140+
"Programming Language :: Python :: 3.6",
141+
"Programming Language :: Python :: 3.7",
142+
"Programming Language :: Python :: 3.8",
143+
"Programming Language :: Python :: 3.9",
144+
"Programming Language :: Python :: 3.10",
145+
"Programming Language :: Python :: Implementation :: CPython",
146+
"Programming Language :: Python :: Implementation :: PyPy"
147+
],
148+
"requires_dist": [
149+
"pyparsing (!=3.0.5,>=2.0.2)"
150+
],
151+
"requires_python": ">=3.6",
152+
"description": "..."
153+
},
154+
"metadata_location": "/home/me/.virtualenvs/demoenv/lib/python3.8/site-packages/packaging-21.3.dist-info",
155+
"installer": "pip",
156+
"requested": true
157+
},
158+
{
159+
"metadata": {
160+
"metadata_version": "2.1",
161+
"name": "pip",
162+
"version": "22.2.dev0",
163+
"summary": "The PyPA recommended tool for installing Python packages.",
164+
"home_page": "https://pip.pypa.io/",
165+
"author": "The pip developers",
166+
"author_email": "[email protected]",
167+
"license": "MIT",
168+
"classifier": [
169+
"Development Status :: 5 - Production/Stable",
170+
"Intended Audience :: Developers",
171+
"License :: OSI Approved :: MIT License",
172+
"Topic :: Software Development :: Build Tools",
173+
"Programming Language :: Python",
174+
"Programming Language :: Python :: 3",
175+
"Programming Language :: Python :: 3 :: Only",
176+
"Programming Language :: Python :: 3.7",
177+
"Programming Language :: Python :: 3.8",
178+
"Programming Language :: Python :: 3.9",
179+
"Programming Language :: Python :: 3.10",
180+
"Programming Language :: Python :: Implementation :: CPython",
181+
"Programming Language :: Python :: Implementation :: PyPy"
182+
],
183+
"requires_python": ">=3.7",
184+
"project_url": [
185+
"Documentation, https://pip.pypa.io",
186+
"Source, https://github.com/pypa/pip",
187+
"Changelog, https://pip.pypa.io/en/stable/news/"
188+
],
189+
"description": "..."
190+
},
191+
"metadata_location": "/home/me/pip/src/pip.egg-info",
192+
"direct_url": {
193+
"url": "file:///home/me/pip/src",
194+
"dir_info": {
195+
"editable": true
196+
}
197+
}
198+
}
199+
],
200+
"environment": {
201+
"implementation_name": "cpython",
202+
"implementation_version": "3.8.10",
203+
"os_name": "posix",
204+
"platform_machine": "x86_64",
205+
"platform_release": "5.13-generic",
206+
"platform_system": "Linux",
207+
"platform_version": "...",
208+
"python_full_version": "3.8.10",
209+
"platform_python_implementation": "CPython",
210+
"python_version": "3.8",
211+
"sys_platform": "linux"
212+
}
213+
}
214+
```

news/11245.feature.rst

Lines changed: 2 additions & 0 deletions
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

Lines changed: 5 additions & 0 deletions
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

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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 and produce a report in JSON format.
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+
"--local",
28+
action="store_true",
29+
default=False,
30+
help=(
31+
"If in a virtualenv that has global access, do not list "
32+
"globally-installed packages."
33+
),
34+
)
35+
self.cmd_opts.add_option(
36+
"--user",
37+
dest="user",
38+
action="store_true",
39+
default=False,
40+
help="Only output packages installed in user-site.",
41+
)
42+
self.cmd_opts.add_option(cmdoptions.list_path())
43+
self.parser.insert_option_group(0, self.cmd_opts)
44+
45+
def run(self, options: Values, args: List[str]) -> int:
46+
cmdoptions.check_list_path_option(options)
47+
dists = get_environment(options.path).iter_installed_distributions(
48+
local_only=options.local,
49+
user_only=options.user,
50+
skip=set(stdlib_pkgs),
51+
)
52+
output = {
53+
"version": "0",
54+
"pip_version": __version__,
55+
"installed": [self._dist_to_dict(dist) for dist in dists],
56+
"environment": default_environment(),
57+
# TODO tags? scheme?
58+
}
59+
print_json(data=output)
60+
return SUCCESS
61+
62+
def _dist_to_dict(self, dist: BaseDistribution) -> Dict[str, Any]:
63+
res: Dict[str, Any] = {
64+
"metadata": dist.metadata_dict,
65+
"metadata_location": dist.info_location,
66+
}
67+
# direct_url. Note that we don't have download_info (as in the installation
68+
# report) since it is not recorded in installed metadata.
69+
direct_url = dist.direct_url
70+
if direct_url is not None:
71+
res["direct_url"] = direct_url.to_dict()
72+
else:
73+
# Emulate direct_url for legacy editable installs.
74+
editable_project_location = dist.editable_project_location
75+
if editable_project_location is not None:
76+
res["direct_url"] = {
77+
"url": path_to_url(editable_project_location),
78+
"dir_info": {
79+
"editable": True,
80+
},
81+
}
82+
# installer
83+
installer = dist.installer
84+
if dist.installer:
85+
res["installer"] = installer
86+
# requested
87+
if dist.installed_with_dist_info:
88+
res["requested"] = dist.requested
89+
return res

0 commit comments

Comments
 (0)