Skip to content

Commit a7a35dc

Browse files
authored
Merge pull request #11245 from sbidoul/pip-inspect-sbi
Add pip inspect command
2 parents e5898ab + 44d340a commit a7a35dc

File tree

9 files changed

+405
-0
lines changed

9 files changed

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

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

docs/html/reference/index.md

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

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

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

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

src/pip/_internal/metadata/base.py

+4
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,10 @@ def installer(self) -> str:
311311
return cleaned_line
312312
return ""
313313

314+
@property
315+
def requested(self) -> bool:
316+
return self.is_file("REQUESTED")
317+
314318
@property
315319
def editable(self) -> bool:
316320
return bool(self.editable_project_location)

0 commit comments

Comments
 (0)