diff --git a/src/pip/_internal/cli/progress_bars.py b/src/pip/_internal/cli/progress_bars.py index 7199d6391c1..36a79c2b450 100644 --- a/src/pip/_internal/cli/progress_bars.py +++ b/src/pip/_internal/cli/progress_bars.py @@ -79,6 +79,7 @@ def _rich_progress_bar( *, bar_type: str, size: int, + quiet: bool, ) -> Generator[bytes, None, None]: assert bar_type == "on", "This should only be used in the default mode." @@ -89,7 +90,9 @@ def _rich_progress_bar( total = size columns = _known_size_columns() - progress = Progress(*columns, console=Console(stderr=True), refresh_per_second=5) + progress = Progress( + *columns, console=Console(stderr=True, quiet=quiet), refresh_per_second=5 + ) task_id = progress.add_task(_progress_task_prefix(), total=total) with progress: for chunk in iterable: @@ -101,12 +104,15 @@ def _raw_progress_bar( iterable: Iterable[bytes], *, size: Optional[int], + quiet: bool, ) -> Generator[bytes, None, None]: prefix = _progress_task_prefix() def write_progress(current: int, total: int) -> None: - sys.stdout.write(f"{prefix}Progress {current} of {total}\n") - sys.stdout.flush() + if quiet: + return + sys.stderr.write(f"{prefix}Progress {current} of {total}\n") + sys.stderr.flush() current = 0 total = size or 0 @@ -122,16 +128,21 @@ def write_progress(current: int, total: int) -> None: def get_download_progress_renderer( - *, bar_type: str, size: Optional[int] = None + *, + bar_type: str, + size: Optional[int] = None, + quiet: bool = False, ) -> DownloadProgressRenderer: """Get an object that can be used to render the download progress. Returns a callable, that takes an iterable to "wrap". """ if bar_type == "on": - return functools.partial(_rich_progress_bar, bar_type=bar_type, size=size) + return functools.partial( + _rich_progress_bar, bar_type=bar_type, size=size, quiet=quiet + ) elif bar_type == "raw": - return functools.partial(_raw_progress_bar, size=size) + return functools.partial(_raw_progress_bar, size=size, quiet=quiet) else: return iter # no-op, when passed an iterator @@ -158,7 +169,10 @@ def __exit__(self, ty: Any, val: Any, tb: Any) -> None: ... @classmethod @abc.abstractmethod def create( - cls, num_tasks: int, known_total_length: Optional[int] + cls, + num_tasks: int, + known_total_length: Optional[int], + quiet: bool, ) -> "BatchedProgress": ... @classmethod @@ -191,7 +205,10 @@ def __exit__(self, ty: Any, val: Any, tb: Any) -> None: @classmethod def create( - cls, num_tasks: int, known_total_length: Optional[int] + cls, + num_tasks: int, + known_total_length: Optional[int], + quiet: bool, ) -> "BatchedNoOpProgressBar": return cls() @@ -201,13 +218,15 @@ def __init__( self, total_bytes: Optional[int], prefix: str, + quiet: bool, ) -> None: self._total_bytes = total_bytes self._prefix = prefix self._total_progress = 0 self._subtasks: List[Tuple[str, Optional[int]]] = [] self._rate_limiter = RateLimiter(0.25) - self._stream = sys.stdout + self._stream = sys.stderr + self._quiet = quiet def add_subtask(self, description: str, total: Optional[int]) -> TaskID: task_id = len(self._subtasks) @@ -215,6 +234,8 @@ def add_subtask(self, description: str, total: Optional[int]) -> TaskID: return TaskID(task_id) def _write_immediate(self, line: str) -> None: + if self._quiet: + return self._stream.write(f"{self._prefix}{line}\n") self._stream.flush() @@ -266,10 +287,10 @@ def __exit__(self, ty: Any, val: Any, tb: Any) -> None: @classmethod def create( - cls, num_tasks: int, known_total_length: Optional[int] + cls, num_tasks: int, known_total_length: Optional[int], quiet: bool ) -> "BatchedRawProgressBar": prefix = _progress_task_prefix() - return cls(known_total_length, prefix) + return cls(known_total_length, prefix, quiet=quiet) class BatchedRichProgressBar(BatchedProgress): @@ -279,11 +300,13 @@ def __init__( total_task_id: TaskID, progress: Progress, total_bytes_task_id: TaskID, + quiet: bool, ) -> None: self._task_progress = task_progress self._total_task_id = total_task_id self._progress = progress self._total_bytes_task_id = total_bytes_task_id + self._quiet = quiet self._live: Optional[Live] = None _TRIM_LEN = 20 @@ -328,7 +351,9 @@ def __enter__(self) -> "BatchedRichProgressBar": padding=(0, 0), ) ) - self._live = Live(table, console=Console(stderr=True), refresh_per_second=5) + self._live = Live( + table, console=Console(stderr=True, quiet=self._quiet), refresh_per_second=5 + ) self._task_progress.start_task(self._total_task_id) self._progress.start_task(self._total_bytes_task_id) self._live.__enter__() @@ -340,7 +365,7 @@ def __exit__(self, ty: Any, val: Any, tb: Any) -> None: @classmethod def create( - cls, num_tasks: int, known_total_length: Optional[int] + cls, num_tasks: int, known_total_length: Optional[int], quiet: bool ) -> "BatchedRichProgressBar": task_columns = _task_columns() task_progress = Progress(*task_columns) @@ -365,4 +390,6 @@ def create( total=total, ) - return cls(task_progress, total_task_id, progress, total_bytes_task_id) + return cls( + task_progress, total_task_id, progress, total_bytes_task_id, quiet=quiet + ) diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py index e2db69d46e4..0a40a23539a 100644 --- a/src/pip/_internal/cli/req_command.py +++ b/src/pip/_internal/cli/req_command.py @@ -142,6 +142,7 @@ def make_requirement_preparer( use_user_site=use_user_site, lazy_wheel=lazy_wheel, verbosity=verbosity, + quietness=options.quiet, batch_download_parallelism=batch_download_parallelism, legacy_resolver=legacy_resolver, ) diff --git a/src/pip/_internal/network/download.py b/src/pip/_internal/network/download.py index ed98f12e779..82f6e6ec5f8 100644 --- a/src/pip/_internal/network/download.py +++ b/src/pip/_internal/network/download.py @@ -64,6 +64,7 @@ def _prepare_download( resp: Response, link: Link, progress_bar: str, + quiet: bool = False, ) -> Iterable[bytes]: total_length = _get_http_response_size(resp) @@ -85,7 +86,9 @@ def _prepare_download( if not show_progress: return chunks - renderer = get_download_progress_renderer(bar_type=progress_bar, size=total_length) + renderer = get_download_progress_renderer( + bar_type=progress_bar, size=total_length, quiet=quiet + ) return renderer(chunks) @@ -163,9 +166,11 @@ def __init__( self, session: PipSession, progress_bar: str, + quiet: bool = False, ) -> None: self._session = session self._progress_bar = progress_bar + self._quiet = quiet def __call__(self, link: Link, location: str) -> Tuple[str, str]: """Download the file given by link into location.""" @@ -181,7 +186,7 @@ def __call__(self, link: Link, location: str) -> Tuple[str, str]: filename = _get_http_response_filename(resp.headers, resp.url, link) filepath = os.path.join(location, filename) - chunks = _prepare_download(resp, link, self._progress_bar) + chunks = _prepare_download(resp, link, self._progress_bar, self._quiet) with open(filepath, "wb") as content_file: for chunk in chunks: content_file.write(chunk) @@ -291,10 +296,12 @@ def __init__( self, session: PipSession, progress_bar: str, + quiet: bool = False, max_parallelism: Optional[int] = None, ) -> None: self._session = session self._progress_bar = progress_bar + self._quiet = quiet if max_parallelism is None: max_parallelism = 1 @@ -331,7 +338,11 @@ def __call__( semaphore = Semaphore(value=self._max_parallelism) batched_progress = BatchedProgress.select_progress_bar( self._progress_bar - ).create(num_tasks=total_downloads, known_total_length=total_length) + ).create( + num_tasks=total_downloads, + known_total_length=total_length, + quiet=self._quiet, + ) # Distribute request i/o across equivalent threads. # NB: event-based/async is likely a better model than thread-per-request, but diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index ef5268d6cc5..62e45853996 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -215,7 +215,7 @@ def _check_download_dir( class RequirementPreparer: """Prepares a Requirement""" - def __init__( + def __init__( # noqa: PLR0913 self, build_dir: str, download_dir: Optional[str], @@ -230,6 +230,7 @@ def __init__( use_user_site: bool, lazy_wheel: bool, verbosity: int, + quietness: int, batch_download_parallelism: Optional[int], legacy_resolver: bool, ) -> None: @@ -239,9 +240,12 @@ def __init__( self.build_dir = build_dir self.build_tracker = build_tracker self._session = session - self._download = Downloader(session, progress_bar) + self._download = Downloader(session, progress_bar, quiet=quietness > 0) self._batch_download = BatchDownloader( - session, progress_bar, max_parallelism=batch_download_parallelism + session, + progress_bar, + quiet=quietness > 0, + max_parallelism=batch_download_parallelism, ) self.finder = finder diff --git a/tests/unit/test_req.py b/tests/unit/test_req.py index 9d0e2021866..e9951493243 100644 --- a/tests/unit/test_req.py +++ b/tests/unit/test_req.py @@ -106,6 +106,7 @@ def _basic_resolver( use_user_site=False, lazy_wheel=False, verbosity=0, + quietness=0, batch_download_parallelism=None, legacy_resolver=True, )