Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide structured JSON output for pip index versions #13194

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
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]
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved

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

KrishanBhasin marked this conversation as resolved.
Show resolved Hide resolved
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