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

Add machine readable download progress option #12084

Closed
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d7a76d3
Add machine readable download progress option
joeyballentine Jun 13, 2023
13c0431
Adds news file
joeyballentine Jun 13, 2023
0045fd8
Fix mypy error
joeyballentine Jun 13, 2023
5f7ab61
lint
joeyballentine Jun 13, 2023
5127329
line length
joeyballentine Jun 13, 2023
813aac6
add newline
joeyballentine Jun 13, 2023
98742ea
use json
joeyballentine Jun 13, 2023
9080d82
fix import sorting
joeyballentine Jun 13, 2023
70ae242
format
joeyballentine Jun 13, 2023
13c11ee
PR suggestions
joeyballentine Jun 13, 2023
d0d96d9
use logger
joeyballentine Jun 13, 2023
f4ca354
add docs to user guide
joeyballentine Jun 13, 2023
dfc4d98
add test
joeyballentine Jun 13, 2023
d1d6a69
fix test, maybe
joeyballentine Jun 13, 2023
5f7782a
maybe this time
joeyballentine Jun 14, 2023
71f3444
attempting just using a network package
joeyballentine Jun 14, 2023
14eb09d
lint
joeyballentine Jun 14, 2023
7567e2a
PR suggestions
joeyballentine Jun 15, 2023
c0fbf13
I didn't see the comment about using -u
joeyballentine Jun 15, 2023
31dee38
lint + add a test (that may or may not work)
joeyballentine Jun 15, 2023
d73336b
remove bad test
joeyballentine Jun 15, 2023
a3d3ebf
Merge branch 'main' into machine-readable-progress
joeyballentine Jun 16, 2023
15f0c8a
Update with more realistic example
joeyballentine Jun 24, 2023
23f95d2
update null verbiage
joeyballentine Jun 24, 2023
fd16cbc
error suggestion
joeyballentine Jun 24, 2023
e88444e
Merge remote-tracking branch 'origin/machine-readable-progress' into …
joeyballentine Jun 24, 2023
d81de49
ruff
joeyballentine Jun 24, 2023
0d6bfc3
Merge branch 'main' into machine-readable-progress
joeyballentine Jun 25, 2023
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
14 changes: 14 additions & 0 deletions docs/html/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,20 @@ We are using `freeze`_ here which outputs installed packages in requirements for

reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze'])

Since pip's progress bar gets hidden when running in a subprocess, you can use
the ``--progress-bar=json`` option for easily parsable progress information::

subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'my_package', '--progress-bar=json'])
joeyballentine marked this conversation as resolved.
Show resolved Hide resolved

Which will give the following output after it processes each download chunk:

``PROGRESS:{"current": ######, "total": ######}``

Here, ``PROGRESS:`` indicates it is download progress. The rest of the message is JSON
with the ``current`` number of bytes downloaded and ``total`` .whl size as key/value pairs.
joeyballentine marked this conversation as resolved.
Show resolved Hide resolved
This can be used to build your own progress bar, or report progress in other ways.
joeyballentine marked this conversation as resolved.
Show resolved Hide resolved
This feature cannot be used unless pip is invoked in a subprocess.

If you don't want to use pip's command line functionality, but are rather
trying to implement code that works with Python packages, their metadata, or
PyPI, then you should consider other, supported, packages that offer this type
Expand Down
1 change: 1 addition & 0 deletions news/11508.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a new progress_bar type that allows machine-readable (json) download progress
7 changes: 5 additions & 2 deletions src/pip/_internal/cli/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,12 @@ class PipOption(Option):
"--progress-bar",
dest="progress_bar",
type="choice",
choices=["on", "off"],
choices=["on", "off", "json"],
default="on",
help="Specify whether the progress bar should be used [on, off] (default: on)",
help=(
"Specify whether the progress bar should be used"
" [on, off, json] (default: on)"
),
)

log: Callable[..., Option] = partial(
Expand Down
35 changes: 35 additions & 0 deletions src/pip/_internal/cli/progress_bars.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import functools
import json
import logging
import sys
from typing import Callable, Generator, Iterable, Iterator, Optional, Tuple

from pip._vendor.rich.progress import (
Expand All @@ -16,6 +19,8 @@

from pip._internal.utils.logging import get_indentation

logger = logging.getLogger(__name__)

DownloadProgressRenderer = Callable[[Iterable[bytes]], Iterator[bytes]]


Expand Down Expand Up @@ -55,6 +60,29 @@ def _rich_progress_bar(
progress.update(task_id, advance=len(chunk))


class _MachineReadableProgress:
def __init__(self, iterable: Iterable[bytes], size: Optional[int]) -> None:
self._iterable = iter(iterable)
self._size = size
self._progress = 0

def __iter__(self) -> Iterator[bytes]:
return self

def __next__(self) -> bytes:
chunk = next(self._iterable)
self._progress += len(chunk)
progress_info = {
"current": self._progress,
"total": self._size,
}
logger.info(
joeyballentine marked this conversation as resolved.
Show resolved Hide resolved
"PROGRESS:%s",
json.dumps(progress_info),
)
return chunk


def get_download_progress_renderer(
*, bar_type: str, size: Optional[int] = None
) -> DownloadProgressRenderer:
Expand All @@ -64,5 +92,12 @@ def get_download_progress_renderer(
"""
if bar_type == "on":
return functools.partial(_rich_progress_bar, bar_type=bar_type, size=size)
elif bar_type == "json":
# We don't want regular users to use this progress_bar type
# so only use if not a TTY
assert (
not sys.stdout.isatty()
), 'The "json" progress_bar type should only be used inside subprocesses.'
joeyballentine marked this conversation as resolved.
Show resolved Hide resolved
return functools.partial(_MachineReadableProgress, size=size)
joeyballentine marked this conversation as resolved.
Show resolved Hide resolved
else:
return iter # no-op, when passed an iterator
42 changes: 42 additions & 0 deletions tests/functional/test_install_progress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import pytest
from tests.lib import (
PipTestEnvironment,
)
import subprocess


@pytest.mark.network
def test_install_with_json_progress_cli(script: PipTestEnvironment) -> None:
"""
Test installing a package using pip install --progress-bar=json
but not as a subprocess
"""
result = script.pip(
"install",
"opencv-python",
"--progress-bar=json",
expect_error=True,
)
assert (
'The "json" progress_bar type should only be used inside subprocesses.'
in result.stderr
)


@pytest.mark.network
def test_install_with_json_progress_subproc(_script: PipTestEnvironment) -> None:
"""
Test installing a package using pip install --progress-bar=json
but not as a subprocess
"""
result = subprocess.check_output(
[
"python",
"-m",
"pip",
"install",
"opencv-python",
"--progress-bar=json",
]
)
assert "PROGRESS:" in result.decode("utf-8")