Skip to content

Commit 9bc4dca

Browse files
committed
Add pip inspect command
1 parent 20ed685 commit 9bc4dca

File tree

3 files changed

+98
-0
lines changed

3 files changed

+98
-0
lines changed

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+
import json
2+
import sys
3+
from optparse import Values
4+
from typing import Any, Dict, List
5+
6+
from pip._vendor.packaging.markers import default_environment
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+
}
60+
json.dump(output, sys.stdout, indent=2, ensure_ascii=True)
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+
"location": dist.location, # TODO is this the best location to report ?
67+
}
68+
# direct_url
69+
direct_url = dist.direct_url
70+
if direct_url is not None:
71+
res["direct_url"] = direct_url.to_dict()
72+
else:
73+
# Handle 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+
# is_direct is redundant but present for symmetry with the installation report.
83+
res["is_direct"] = "direct_url" in res
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)