Add pip inspect command

This commit is contained in:
Stéphane Bidoul 2022-07-10 19:29:46 +02:00
parent 7f46f94d04
commit 585136494e
No known key found for this signature in database
GPG Key ID: BCAB2555446B5B92
8 changed files with 389 additions and 0 deletions

View File

@ -16,6 +16,7 @@ pip
pip_install
pip_uninstall
pip_inspect
pip_list
pip_show
pip_freeze

View File

@ -0,0 +1,32 @@
.. _`pip inspect`:
===========
pip inspect
===========
Usage
=====
.. tab:: Unix/macOS
.. pip-command-usage:: inspect "python -m pip"
.. tab:: Windows
.. pip-command-usage:: inspect "py -m pip"
Description
===========
.. pip-command-description:: inspect
The format of the JSON output is described in :doc:`../reference/inspect-report`.
Options
=======
.. pip-command-options:: inspect

View File

@ -10,4 +10,5 @@ build-system/index
requirement-specifiers
requirements-file-format
installation-report
inspect-report
```

View File

@ -0,0 +1,214 @@
# `pip inspect` JSON output specification
The `pip inspect` command produces a detailed JSON report of the Python
environment, including installed distributions.
## Specification
The report is a JSON object with the following properties:
- `version`: the string `0`, denoting that the inspect command is an experimental
feature. This value will change to `1`, when the feature is deemed stable after
gathering user feedback (likely in pip 22.3 or 23.0). Backward incompatible changes
may be introduced in version `1` without notice. After that, it will change only if
and when backward incompatible changes are introduced, such as removing mandatory
fields or changing the semantics or data type of existing fields. The introduction of
backward incompatible changes will follow the usual pip processes such as the
deprecation cycle or feature flags. Tools must check this field to ensure they support
the corresponding version.
- `pip_version`: a string with the version of pip used to produce the report.
- `installed`: an array of [InspectReportItem](InspectReportItem) representing the
distribution packages that are installed.
- `environment`: an object describing the environment where the installation report was
generated. See [PEP 508 environment
markers](https://peps.python.org/pep-0508/#environment-markers) for more information.
Values have a string type.
(InspectReportItem)=
An `InspectReportItem` is an object describing an installed distribution package with
the following properties:
- `metadata`: the metadata of the distribution, converted to a JSON object according to
the [PEP 566
transformation](https://www.python.org/dev/peps/pep-0566/#json-compatible-metadata).
- `metadata_location`: the location of the metadata of the installed distribution. Most
of the time this is the `.dist-info` directory. For legacy installs it is the
`.egg-info` directory.
```{warning}
This field may not necessary point to a directory, for instance, in the case of older
`.egg` installs.
```
- `direct_url`: Information about the direct URL that was used for installation, if any,
using the [direct
URL](https://packaging.python.org/en/latest/specifications/direct-url/) data
structure. In most case, this field corresponds to the `direct_url.json` metadata,
except for legacy editable installs, where it is emulated.
- `requested`: `true` if the `REQUESTED` metadata is present, `false` otherwise. This
field is only present for modern `.dist-info` installations.
```{note}
The `REQUESTED` metadata may not be generated by all installers.
It is generated by pip since version 20.2.
```
- `installer`: the content of the `INSTALLER` metadata, if present and not empty.
## Example
Running the ``pip inspect`` command, in an environment where `pip` is installed in
editable mode and `packaging` is installed as well, will produce an output similar to
this (metadata abriged for brevity):
```json
{
"version": "0",
"pip_version": "22.2.dev0",
"installed": [
{
"metadata": {
"metadata_version": "2.1",
"name": "pyparsing",
"version": "3.0.9",
"summary": "pyparsing module - Classes and methods to define and execute parsing grammars",
"description_content_type": "text/x-rst",
"author_email": "Paul McGuire <ptmcg.gm+pyparsing@gmail.com>",
"classifier": [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Typing :: Typed"
],
"requires_dist": [
"railroad-diagrams ; extra == \"diagrams\"",
"jinja2 ; extra == \"diagrams\""
],
"requires_python": ">=3.6.8",
"project_url": [
"Homepage, https://github.com/pyparsing/pyparsing/"
],
"provides_extra": [
"diagrams"
],
"description": "..."
},
"metadata_location": "/home/me/.virtualenvs/demoenv/lib/python3.8/site-packages/pyparsing-3.0.9.dist-info",
"installer": "pip",
"requested": false
},
{
"metadata": {
"metadata_version": "2.1",
"name": "packaging",
"version": "21.3",
"platform": [
"UNKNOWN"
],
"summary": "Core utilities for Python packages",
"description_content_type": "text/x-rst",
"home_page": "https://github.com/pypa/packaging",
"author": "Donald Stufft and individual contributors",
"author_email": "donald@stufft.io",
"license": "BSD-2-Clause or Apache-2.0",
"classifier": [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"License :: OSI Approved :: BSD License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy"
],
"requires_dist": [
"pyparsing (!=3.0.5,>=2.0.2)"
],
"requires_python": ">=3.6",
"description": "..."
},
"metadata_location": "/home/me/.virtualenvs/demoenv/lib/python3.8/site-packages/packaging-21.3.dist-info",
"installer": "pip",
"requested": true
},
{
"metadata": {
"metadata_version": "2.1",
"name": "pip",
"version": "22.2.dev0",
"summary": "The PyPA recommended tool for installing Python packages.",
"home_page": "https://pip.pypa.io/",
"author": "The pip developers",
"author_email": "distutils-sig@python.org",
"license": "MIT",
"classifier": [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Topic :: Software Development :: Build Tools",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy"
],
"requires_python": ">=3.7",
"project_url": [
"Documentation, https://pip.pypa.io",
"Source, https://github.com/pypa/pip",
"Changelog, https://pip.pypa.io/en/stable/news/"
],
"description": "..."
},
"metadata_location": "/home/me/pip/src/pip.egg-info",
"direct_url": {
"url": "file:///home/me/pip/src",
"dir_info": {
"editable": true
}
}
}
],
"environment": {
"implementation_name": "cpython",
"implementation_version": "3.8.10",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_release": "5.13-generic",
"platform_system": "Linux",
"platform_version": "...",
"python_full_version": "3.8.10",
"platform_python_implementation": "CPython",
"python_version": "3.8",
"sys_platform": "linux"
}
}
```

2
news/11245.feature.rst Normal file
View File

@ -0,0 +1,2 @@
Add ``pip inspect`` command to obtain the list of installed distributions and other
information about the Python environment, in JSON format.

View File

@ -38,6 +38,11 @@ commands_dict: Dict[str, CommandInfo] = {
"FreezeCommand",
"Output installed packages in requirements format.",
),
"inspect": CommandInfo(
"pip._internal.commands.inspect",
"InspectCommand",
"Inspect the python environment.",
),
"list": CommandInfo(
"pip._internal.commands.list",
"ListCommand",

View File

@ -0,0 +1,89 @@
from optparse import Values
from typing import Any, Dict, List
from pip._vendor.packaging.markers import default_environment
from pip._vendor.rich import print_json
from pip import __version__
from pip._internal.cli import cmdoptions
from pip._internal.cli.req_command import Command
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.metadata import BaseDistribution, get_environment
from pip._internal.utils.compat import stdlib_pkgs
from pip._internal.utils.urls import path_to_url
class InspectCommand(Command):
"""
Inspect the content of a Python environment and produce a report in JSON format.
"""
ignore_require_venv = True
usage = """
%prog [options]"""
def add_options(self) -> None:
self.cmd_opts.add_option(
"--local",
action="store_true",
default=False,
help=(
"If in a virtualenv that has global access, do not list "
"globally-installed packages."
),
)
self.cmd_opts.add_option(
"--user",
dest="user",
action="store_true",
default=False,
help="Only output packages installed in user-site.",
)
self.cmd_opts.add_option(cmdoptions.list_path())
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options: Values, args: List[str]) -> int:
cmdoptions.check_list_path_option(options)
dists = get_environment(options.path).iter_installed_distributions(
local_only=options.local,
user_only=options.user,
skip=set(stdlib_pkgs),
)
output = {
"version": "0",
"pip_version": __version__,
"installed": [self._dist_to_dict(dist) for dist in dists],
"environment": default_environment(),
# TODO tags? scheme?
}
print_json(data=output)
return SUCCESS
def _dist_to_dict(self, dist: BaseDistribution) -> Dict[str, Any]:
res: Dict[str, Any] = {
"metadata": dist.metadata_dict,
"metadata_location": dist.info_location,
}
# direct_url. Note that we don't have download_info (as in the installation
# report) since it is not recorded in installed metadata.
direct_url = dist.direct_url
if direct_url is not None:
res["direct_url"] = direct_url.to_dict()
else:
# Emulate direct_url for legacy editable installs.
editable_project_location = dist.editable_project_location
if editable_project_location is not None:
res["direct_url"] = {
"url": path_to_url(editable_project_location),
"dir_info": {
"editable": True,
},
}
# installer
installer = dist.installer
if dist.installer:
res["installer"] = installer
# requested
if dist.installed_with_dist_info:
res["requested"] = dist.requested
return res

View File

@ -0,0 +1,45 @@
import json
import pytest
from tests.conftest import ScriptFactory
from tests.lib import PipTestEnvironment, TestData
@pytest.fixture(scope="session")
def simple_script(
tmpdir_factory: pytest.TempPathFactory,
script_factory: ScriptFactory,
shared_data: TestData,
) -> PipTestEnvironment:
tmpdir = tmpdir_factory.mktemp("pip_test_package")
script = script_factory(tmpdir.joinpath("workspace"))
script.pip(
"install",
"-f",
shared_data.find_links,
"--no-index",
"simplewheel==1.0",
)
return script
def test_inspect_basic(simple_script: PipTestEnvironment) -> None:
"""
Test default behavior of inspect command.
"""
result = simple_script.pip("inspect")
report = json.loads(result.stdout)
installed = report["installed"]
assert len(installed) == 4
installed_by_name = {i["metadata"]["name"]: i for i in installed}
assert installed_by_name.keys() == {
"pip",
"setuptools",
"coverage",
"simplewheel",
}
assert installed_by_name["simplewheel"]["metadata"]["version"] == "1.0"
assert installed_by_name["simplewheel"]["requested"] is True
assert installed_by_name["simplewheel"]["installer"] == "pip"
assert "environment" in report