Skip to content

Commit 39e6f21

Browse files
committed
Add pip inspect command
1 parent 20ed685 commit 39e6f21

File tree

8 files changed

+388
-0
lines changed

8 files changed

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

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

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 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+
"-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)