1
1
import functools
2
+ from logging import Logger
2
3
from typing import (
3
4
Any ,
4
5
Callable ,
41
42
42
43
43
44
class RenderableLine :
45
+ """
46
+ A wrapper for a single row, renderable by `Console` methods.
47
+ """
48
+
44
49
def __init__ (self , line_items : List [Union [Text , ProgressBar ]]):
45
50
self .line_items = line_items
46
51
@@ -57,6 +62,10 @@ def __rich_console__(
57
62
58
63
59
64
class RenderableLines :
65
+ """
66
+ A wrapper for multiple rows, renderable by `Console` methods.
67
+ """
68
+
60
69
def __init__ (self , lines : Iterable [RenderableLine ]):
61
70
self .lines = lines
62
71
@@ -71,6 +80,21 @@ def __rich_console__(
71
80
72
81
73
82
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
+
74
98
@classmethod
75
99
def get_default_columns (cls ) -> Tuple [ProgressColumn , ...]:
76
100
"""
@@ -100,53 +124,37 @@ def get_indefinite_columns(cls) -> Tuple[ProgressColumn, ...]:
100
124
TimeElapsedColumn (),
101
125
)
102
126
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
-
121
127
def get_renderable (self ) -> RenderableType :
122
128
"""
123
- Get the renderable representation of the progress bars of all tasks
129
+ Get the renderable representation of the progress of all tasks
124
130
"""
125
131
renderables : List [RenderableLine ] = []
126
132
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 )
129
137
return RenderableLines (renderables )
130
138
131
- def make_task_group (self , task : Task ) -> Iterable [RenderableLine ]:
139
+ def make_task_group (self , task : Task ) -> Iterable [Optional [ RenderableLine ] ]:
132
140
"""
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.
135
142
136
143
Parameters:
137
144
- task (Task): The task for which to generate the representation.
138
145
139
146
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,
142
148
"""
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
+ )
148
156
progress_row = self .make_task_row (columns , task )
149
- return (description_row , progress_row )
157
+ return (progress_row , )
150
158
151
159
def make_task_row (
152
160
self , columns : Tuple [Union [str , ProgressColumn ], ...], task : Task
@@ -167,8 +175,6 @@ def merge_text_objects(
167
175
) -> List [Union [Text , ProgressBar ]]:
168
176
"""
169
177
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
172
178
"""
173
179
merged_row : List [Union [Text , ProgressBar ]] = []
174
180
markup_to_merge : List [str ] = []
@@ -186,6 +192,68 @@ def merge_text_objects(
186
192
merged_row .append (Text .from_markup (merged_markup ))
187
193
return merged_row
188
194
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
+
189
257
def sort_tasks (self ) -> None :
190
258
"""
191
259
Sort tasks
@@ -196,7 +264,7 @@ def sort_tasks(self) -> None:
196
264
tasks = []
197
265
for task_id in self ._tasks :
198
266
task = self ._tasks [task_id ]
199
- if task .finished and len (self ._tasks ) > 3 :
267
+ if task .finished and len (self ._tasks ) > 1 :
200
268
# Remove and log the finished task if there are too many active
201
269
# tasks to reduce the number of things to be rendered
202
270
# If there are too many actice tasks on screen rich renders the
@@ -205,8 +273,10 @@ def sort_tasks(self) -> None:
205
273
# If we remove every task on completion, it adds an extra newline
206
274
# for sequential downloads due to self.live on __exit__
207
275
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 ))
210
280
else :
211
281
tasks .append ((task_id , self ._tasks [task_id ]))
212
282
# Sorting by finished ensures that all active downloads remain together
@@ -226,8 +296,8 @@ def update(
226
296
** fields : Any ,
227
297
) -> None :
228
298
"""
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
231
301
"""
232
302
with self ._lock :
233
303
task = self ._tasks [task_id ]
0 commit comments