Skip to content

Commit 390fd51

Browse files
committed
Split PipProgress into 2 classes
1 for sequential downloads and 1 for parallel downloads.
1 parent 5b7e6d7 commit 390fd51

File tree

1 file changed

+109
-39
lines changed

1 file changed

+109
-39
lines changed

src/pip/_internal/cli/progress_bars.py

Lines changed: 109 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import functools
2+
from logging import Logger
23
from typing import (
34
Any,
45
Callable,
@@ -41,6 +42,10 @@
4142

4243

4344
class RenderableLine:
45+
"""
46+
A wrapper for a single row, renderable by `Console` methods.
47+
"""
48+
4449
def __init__(self, line_items: List[Union[Text, ProgressBar]]):
4550
self.line_items = line_items
4651

@@ -57,6 +62,10 @@ def __rich_console__(
5762

5863

5964
class RenderableLines:
65+
"""
66+
A wrapper for multiple rows, renderable by `Console` methods.
67+
"""
68+
6069
def __init__(self, lines: Iterable[RenderableLine]):
6170
self.lines = lines
6271

@@ -71,6 +80,21 @@ def __rich_console__(
7180

7281

7382
class PipProgress(Progress):
83+
"""
84+
Custom Progress bar for sequential downloads.
85+
"""
86+
87+
def __init__(
88+
self,
89+
refresh_per_second: int,
90+
progress_disabled: bool = False,
91+
logger: Optional[Logger] = None,
92+
) -> None:
93+
super().__init__(refresh_per_second=refresh_per_second)
94+
self.progress_disabled = progress_disabled
95+
self.log_download_description = True
96+
self.logger = logger
97+
7498
@classmethod
7599
def get_default_columns(cls) -> Tuple[ProgressColumn, ...]:
76100
"""
@@ -100,53 +124,37 @@ def get_indefinite_columns(cls) -> Tuple[ProgressColumn, ...]:
100124
TimeElapsedColumn(),
101125
)
102126

103-
@classmethod
104-
def get_description_columns(cls) -> Tuple[ProgressColumn, ...]:
105-
"""
106-
Get the columns to use for the log message, i.e. the task description
107-
"""
108-
# These columns will be the "Downloading"/"Using cached" message
109-
# This message needs to be columns because,logging this message interferes
110-
# with parallel progress bars, and if we want the message to remain next
111-
# to the progress bar even when there are multiple tasks, then it needs
112-
# to be a part of the progress bar
113-
indentation = get_indentation()
114-
if indentation:
115-
return (
116-
TextColumn(" " * get_indentation()),
117-
TextColumn("{task.description}"),
118-
)
119-
return (TextColumn("{task.description}"),)
120-
121127
def get_renderable(self) -> RenderableType:
122128
"""
123-
Get the renderable representation of the progress bars of all tasks
129+
Get the renderable representation of the progress of all tasks
124130
"""
125131
renderables: List[RenderableLine] = []
126132
for task in self.tasks:
127-
if task.visible:
128-
renderables.extend(self.make_task_group(task))
133+
if not task.visible:
134+
continue
135+
task_renderable = [x for x in self.make_task_group(task) if x is not None]
136+
renderables.extend(task_renderable)
129137
return RenderableLines(renderables)
130138

131-
def make_task_group(self, task: Task) -> Iterable[RenderableLine]:
139+
def make_task_group(self, task: Task) -> Iterable[Optional[RenderableLine]]:
132140
"""
133-
Create a representation for a task, including both the description line
134-
and the progress line.
141+
Create a representation for a task, i.e. it's progress bar.
135142
136143
Parameters:
137144
- task (Task): The task for which to generate the representation.
138145
139146
Returns:
140-
- Iterable[RenderableLine]: An iterable of renderable lines containing the
141-
description and (optionally) progress lines,
147+
- Optional[Group]: text representation of a Progress Column,
142148
"""
143-
columns = self.columns if task.total else self.get_indefinite_columns()
144-
description_row = self.make_task_row(self.get_description_columns(), task)
145-
# Only print description if download isn't large enough
146-
if task.total is not None and not task.total > (40 * 1000):
147-
return (description_row,)
149+
150+
hide_progress = task.fields["hide_progress"]
151+
if self.progress_disabled or hide_progress:
152+
return (None,)
153+
columns = (
154+
self.columns if task.total is not None else self.get_indefinite_columns()
155+
)
148156
progress_row = self.make_task_row(columns, task)
149-
return (description_row, progress_row)
157+
return (progress_row,)
150158

151159
def make_task_row(
152160
self, columns: Tuple[Union[str, ProgressColumn], ...], task: Task
@@ -167,8 +175,6 @@ def merge_text_objects(
167175
) -> List[Union[Text, ProgressBar]]:
168176
"""
169177
Merge adjacent Text objects in the given row into a single Text object.
170-
This is required to prevent newlines from being rendered between
171-
Text objects
172178
"""
173179
merged_row: List[Union[Text, ProgressBar]] = []
174180
markup_to_merge: List[str] = []
@@ -186,6 +192,68 @@ def merge_text_objects(
186192
merged_row.append(Text.from_markup(merged_markup))
187193
return merged_row
188194

195+
def add_task(
196+
self,
197+
description: str,
198+
start: bool = True,
199+
total: Optional[float] = 100.0,
200+
completed: int = 0,
201+
visible: bool = True,
202+
**fields: Any,
203+
) -> TaskID:
204+
"""
205+
Reimplementation of Progress.add_task with description logging
206+
"""
207+
if visible and self.log_download_description and self.logger:
208+
indentation = " " * get_indentation()
209+
log_statement = f"{indentation}{description}"
210+
self.logger.info(log_statement)
211+
return super().add_task(
212+
description=description, total=total, visible=visible, **fields
213+
)
214+
215+
216+
class PipParallelProgress(PipProgress):
217+
def __init__(self, refresh_per_second: int, progress_disabled: bool = True):
218+
super().__init__(refresh_per_second=refresh_per_second)
219+
# Overrides behaviour of logging description on add_task from PipProgress
220+
self.log_download_description = False
221+
222+
@classmethod
223+
def get_description_columns(cls) -> Tuple[ProgressColumn, ...]:
224+
"""
225+
Get the columns to use for the log message, i.e. the task description
226+
"""
227+
# These columns will be the "Downloading"/"Using cached" message
228+
# This message needs to be columns because,logging this message interferes
229+
# with parallel progress bars, and if we want the message to remain next
230+
# to the progress bar even when there are multiple tasks, then it needs
231+
# to be a part of the progress bar
232+
indentation = get_indentation()
233+
if indentation:
234+
return (
235+
TextColumn(" " * get_indentation()),
236+
TextColumn("{task.description}"),
237+
)
238+
return (TextColumn("{task.description}"),)
239+
240+
def make_task_group(self, task: Task) -> Iterable[Optional[RenderableLine]]:
241+
"""
242+
Create a representation for a task, including both the description row
243+
and the progress row.
244+
245+
Parameters:
246+
- task (Task): The task for which to generate the representation.
247+
248+
Returns:
249+
- Iterable[Optional[RenderableLine]]: An Iterable containing the
250+
description and progress rows,
251+
"""
252+
progress_row = super().make_task_group(task)
253+
254+
description_row = self.make_task_row(self.get_description_columns(), task)
255+
return (description_row, *progress_row)
256+
189257
def sort_tasks(self) -> None:
190258
"""
191259
Sort tasks
@@ -196,7 +264,7 @@ def sort_tasks(self) -> None:
196264
tasks = []
197265
for task_id in self._tasks:
198266
task = self._tasks[task_id]
199-
if task.finished and len(self._tasks) > 3:
267+
if task.finished and len(self._tasks) > 1:
200268
# Remove and log the finished task if there are too many active
201269
# tasks to reduce the number of things to be rendered
202270
# If there are too many actice tasks on screen rich renders the
@@ -205,8 +273,10 @@ def sort_tasks(self) -> None:
205273
# If we remove every task on completion, it adds an extra newline
206274
# for sequential downloads due to self.live on __exit__
207275
if task.visible:
208-
task_group = RenderableLines(self.make_task_group(task))
209-
self.console.print(task_group)
276+
task_group = [
277+
x for x in self.make_task_group(task) if x is not None
278+
]
279+
self.console.print(RenderableLines(task_group))
210280
else:
211281
tasks.append((task_id, self._tasks[task_id]))
212282
# Sorting by finished ensures that all active downloads remain together
@@ -226,8 +296,8 @@ def update(
226296
**fields: Any,
227297
) -> None:
228298
"""
229-
A copy of Progress' implementation of update, with sorting of self.tasks
230-
when a task is completed
299+
A copy of Progress' implementation of update, with sorting of
300+
self.tasks when a task is completed
231301
"""
232302
with self._lock:
233303
task = self._tasks[task_id]

0 commit comments

Comments
 (0)