Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/13194.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a structured ``--json`` output to ``pip index versions``
8 changes: 8 additions & 0 deletions src/pip/_internal/cli/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,14 @@ def _handle_config_settings(
"pip only finds stable versions.",
)

json: Callable[..., Option] = partial(
Option,
"--json",
action="store_true",
default=False,
help="Output data in a machine-readable JSON format.",
)

disable_pip_version_check: Callable[..., Option] = partial(
Option,
"--disable-pip-version-check",
Expand Down
18 changes: 18 additions & 0 deletions src/pip/_internal/commands/index.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import logging
from optparse import Values
from typing import Any, Iterable, List, Optional
Expand All @@ -11,6 +12,7 @@
from pip._internal.exceptions import CommandError, DistributionNotFound, PipError
from pip._internal.index.collector import LinkCollector
from pip._internal.index.package_finder import PackageFinder
from pip._internal.metadata import get_default_environment
from pip._internal.models.selection_prefs import SelectionPreferences
from pip._internal.models.target_python import TargetPython
from pip._internal.network.session import PipSession
Expand All @@ -34,6 +36,7 @@ def add_options(self) -> None:

self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
self.cmd_opts.add_option(cmdoptions.pre())
self.cmd_opts.add_option(cmdoptions.json())
self.cmd_opts.add_option(cmdoptions.no_binary())
self.cmd_opts.add_option(cmdoptions.only_binary())

Expand Down Expand Up @@ -134,6 +137,21 @@ def get_available_package_versions(self, options: Values, args: List[Any]) -> No
formatted_versions = [str(ver) for ver in sorted(versions, reverse=True)]
latest = formatted_versions[0]

if options.json:
env = get_default_environment()
dist = env.get_distribution(query)
structured_output = {
"name": query,
"versions": formatted_versions,
"latest": latest,
}

if dist is not None:
structured_output["installed_version"] = str(dist.version)

write_output(json.dumps(structured_output))
return

write_output(f"{query} ({latest})")
write_output("Available versions: {}".format(", ".join(formatted_versions)))
print_dist_installation_info(query, latest)
32 changes: 32 additions & 0 deletions tests/functional/test_index.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json

import pytest

from pip._internal.cli.status_codes import ERROR, SUCCESS
Expand All @@ -6,6 +8,36 @@
from tests.lib import PipTestEnvironment


@pytest.mark.network
def test_json_structured_output(script: PipTestEnvironment) -> None:
"""
Test that --json flag returns structured output
"""
output = script.pip("index", "versions", "pip", "--json", allow_stderr_warning=True)
structured_output = json.loads(output.stdout)

assert isinstance(structured_output, dict)
assert "name" in structured_output
assert structured_output["name"] == "pip"
assert "latest" in structured_output
assert isinstance(structured_output["latest"], str)
assert "versions" in structured_output
assert isinstance(structured_output["versions"], list)
assert (
"20.2.3, 20.2.2, 20.2.1, 20.2, 20.1.1, 20.1, 20.0.2"
", 20.0.1, 19.3.1, 19.3, 19.2.3, 19.2.2, 19.2.1, 19.2, 19.1.1"
", 19.1, 19.0.3, 19.0.2, 19.0.1, 19.0, 18.1, 18.0, 10.0.1, 10.0.0, "
"9.0.3, 9.0.2, 9.0.1, 9.0.0, 8.1.2, 8.1.1, "
"8.1.0, 8.0.3, 8.0.2, 8.0.1, 8.0.0, 7.1.2, 7.1.1, 7.1.0, 7.0.3, "
"7.0.2, 7.0.1, 7.0.0, 6.1.1, 6.1.0, 6.0.8, 6.0.7, 6.0.6, 6.0.5, "
"6.0.4, 6.0.3, 6.0.2, 6.0.1, 6.0, 1.5.6, 1.5.5, 1.5.4, 1.5.3, "
"1.5.2, 1.5.1, 1.5, 1.4.1, 1.4, 1.3.1, 1.3, 1.2.1, 1.2, 1.1, 1.0.2,"
" 1.0.1, 1.0, 0.8.3, 0.8.2, 0.8.1, 0.8, 0.7.2, 0.7.1, 0.7, 0.6.3, "
"0.6.2, 0.6.1, 0.6, 0.5.1, 0.5, 0.4, 0.3.1, "
"0.3, 0.2.1, 0.2" in ", ".join(structured_output["versions"])
)


@pytest.mark.network
def test_list_all_versions_basic_search(script: PipTestEnvironment) -> None:
"""
Expand Down
Loading