mirror of https://github.com/pypa/pip
Add pip inspect command
This commit is contained in:
parent
7f46f94d04
commit
585136494e
|
@ -16,6 +16,7 @@ pip
|
|||
|
||||
pip_install
|
||||
pip_uninstall
|
||||
pip_inspect
|
||||
pip_list
|
||||
pip_show
|
||||
pip_freeze
|
||||
|
|
|
@ -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
|
|
@ -10,4 +10,5 @@ build-system/index
|
|||
requirement-specifiers
|
||||
requirements-file-format
|
||||
installation-report
|
||||
inspect-report
|
||||
```
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
```
|
|
@ -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.
|
|
@ -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",
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue