progress-table 1.2.1__tar.gz → 1.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {progress-table-1.2.1 → progress-table-1.2.3}/PKG-INFO +1 -2
- progress-table-1.2.3/progress_table/v0/__init__.py +0 -0
- progress-table-1.2.3/progress_table/v0/progress_table.py +588 -0
- progress-table-1.2.3/progress_table/v0/symbols.py +160 -0
- progress-table-1.2.3/progress_table/v1/__init__.py +0 -0
- progress-table-1.2.3/progress_table/v1/progress_table.py +695 -0
- progress-table-1.2.3/progress_table/v1/styles.py +152 -0
- {progress-table-1.2.1 → progress-table-1.2.3}/progress_table.egg-info/PKG-INFO +1 -2
- {progress-table-1.2.1 → progress-table-1.2.3}/progress_table.egg-info/SOURCES.txt +7 -1
- {progress-table-1.2.1 → progress-table-1.2.3}/setup.py +3 -3
- {progress-table-1.2.1 → progress-table-1.2.3}/LICENSE.txt +0 -0
- {progress-table-1.2.1 → progress-table-1.2.3}/README.md +0 -0
- {progress-table-1.2.1 → progress-table-1.2.3}/progress_table/__init__.py +0 -0
- {progress-table-1.2.1 → progress-table-1.2.3}/progress_table/test_docs_automated.py +0 -0
- {progress-table-1.2.1 → progress-table-1.2.3}/progress_table.egg-info/dependency_links.txt +0 -0
- {progress-table-1.2.1 → progress-table-1.2.3}/progress_table.egg-info/requires.txt +0 -0
- {progress-table-1.2.1 → progress-table-1.2.3}/progress_table.egg-info/top_level.txt +0 -0
- {progress-table-1.2.1 → progress-table-1.2.3}/pyproject.toml +0 -0
- {progress-table-1.2.1 → progress-table-1.2.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: progress-table
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.3
|
|
4
4
|
Summary: Display progress as a pretty table in the command line.
|
|
5
5
|
Home-page: https://github.com/gahaalt/progress-table.git
|
|
6
6
|
Author: Szymon Mikler
|
|
@@ -17,7 +17,6 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
17
17
|
Requires-Python: >=3.7
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
License-File: LICENSE.txt
|
|
20
|
-
Requires-Dist: colorama
|
|
21
20
|
|
|
22
21
|
> Note: versions 1.X introduced new features and changes of default behaviour.
|
|
23
22
|
>
|
|
File without changes
|
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
# Copyright (c) 2022 Szymon Mikler
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import logging
|
|
7
|
+
import math
|
|
8
|
+
import shutil
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
from builtins import KeyError, staticmethod
|
|
12
|
+
from collections import defaultdict
|
|
13
|
+
from typing import Any, Callable, Dict, List, Tuple
|
|
14
|
+
|
|
15
|
+
from colorama import Fore, Style
|
|
16
|
+
|
|
17
|
+
from . import symbols
|
|
18
|
+
|
|
19
|
+
ALL_COLOR_NAMES = [x for x in dir(Fore) if not x.startswith("__")]
|
|
20
|
+
ALL_STYLE_NAMES = [x for x in dir(Style) if not x.startswith("__")]
|
|
21
|
+
ALL_COLORS_STYLES_NAMES = ALL_COLOR_NAMES + ALL_STYLE_NAMES
|
|
22
|
+
|
|
23
|
+
ALL_COLORS = [getattr(Fore, x) for x in ALL_COLOR_NAMES]
|
|
24
|
+
ALL_STYLES = [getattr(Style, x) for x in ALL_STYLE_NAMES]
|
|
25
|
+
ALL_COLORS_STYLES = ALL_COLORS + ALL_STYLES
|
|
26
|
+
|
|
27
|
+
ITERATOR_LENGTH_UNKNOWN_WARNED_ONCE = False
|
|
28
|
+
ITERATOR_LENGTH_CACHE: Dict[int, int] = {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ProgressTableV0:
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
columns: Tuple | List = (),
|
|
35
|
+
refresh_rate: int = 10,
|
|
36
|
+
num_decimal_places: int = 4,
|
|
37
|
+
default_column_width: int = 8,
|
|
38
|
+
default_column_alignment: str = "center",
|
|
39
|
+
default_show_throughput: bool = True,
|
|
40
|
+
default_show_progress: bool = False,
|
|
41
|
+
print_row_on_update: bool = True,
|
|
42
|
+
reprint_header_every_n_rows: int = 30,
|
|
43
|
+
custom_format: Callable[[Any], Any] | None = None,
|
|
44
|
+
embedded_progress_bar: bool = False,
|
|
45
|
+
table_style: str = "normal",
|
|
46
|
+
file=sys.stdout,
|
|
47
|
+
):
|
|
48
|
+
"""Progress Table instance.
|
|
49
|
+
|
|
50
|
+
Columns have to be specified before displaying the first row or launching a progress bar!
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
columns: Columns that will be displayed in the header. Columns can be provided directly in
|
|
54
|
+
`__init__` or through methods `add_column` and `add_columns`. Columns added through
|
|
55
|
+
`__init__` will have default settings like alignment, color and width, while
|
|
56
|
+
columns added through methods can have those customized.
|
|
57
|
+
refresh_rate: The maximal number of times per second the last row of the Table will be refreshed.
|
|
58
|
+
This applies only when using Progress Bar or when `print_row_on_update = True`.
|
|
59
|
+
num_decimal_places: This is only applicable when using the default formatting. This won't be used
|
|
60
|
+
if `custom_format` is set. If applicable, for every displayed value except
|
|
61
|
+
integers there will be an attempt to round it.
|
|
62
|
+
default_column_width: Width of the column excluding cell padding. This can be overwritten by
|
|
63
|
+
columns by using an argument in `add_column` method.
|
|
64
|
+
default_column_alignment: Alignment in the column. Can be aligned either to `left`, `right` or
|
|
65
|
+
`center` (default). This can be overwritten by columns by using
|
|
66
|
+
an argument in `add_column` method.
|
|
67
|
+
print_row_on_update: True by default. If False, the current row will be displayed only when
|
|
68
|
+
it's ready. Row is considered ready after calling `.next_row` or `.close`
|
|
69
|
+
methods.
|
|
70
|
+
reprint_header_every_n_rows: 30 by default. When table has a lot of rows, it can be useful to
|
|
71
|
+
remind what the header is. If True, hedaer will be displayed
|
|
72
|
+
periodically after the selected number of rows. 0 to supress.
|
|
73
|
+
custom_format: A function that allows specyfing custom formatting for values in cells.
|
|
74
|
+
This function should be universal and work for all datatypes as inputs.
|
|
75
|
+
It takes one value as an input and returns one value as an output.
|
|
76
|
+
embedded_progress_bar: False by default. If True, changes the way the progress bar looks.
|
|
77
|
+
Embedded version is more subtle, but does not prevent the current row
|
|
78
|
+
from being displayed. If False, the progress bar covers the current
|
|
79
|
+
row, preventing the user from seeing values that are being updated
|
|
80
|
+
until the progress bar finishes.
|
|
81
|
+
table_style: Change the borders of the table. Cause KeyError to see all the available styles.
|
|
82
|
+
file: Redirect the output to another stream. There can be multiple streams at once passed as
|
|
83
|
+
a list or a tuple. Defaults to sys.stdout.
|
|
84
|
+
"""
|
|
85
|
+
self.refresh_rate = refresh_rate
|
|
86
|
+
self.default_width = default_column_width
|
|
87
|
+
self.default_alignment = default_column_alignment
|
|
88
|
+
self.default_show_throughput = default_show_throughput
|
|
89
|
+
self.default_show_progress = default_show_progress
|
|
90
|
+
self.print_row_on_update = print_row_on_update
|
|
91
|
+
self.reprint_header_every_n_rows = reprint_header_every_n_rows
|
|
92
|
+
self.custom_format = custom_format or self.get_default_format_func(num_decimal_places)
|
|
93
|
+
self.embedded_progress_bar = embedded_progress_bar
|
|
94
|
+
|
|
95
|
+
if not isinstance(file, list) and not isinstance(file, tuple):
|
|
96
|
+
file = (file,)
|
|
97
|
+
self.files = file
|
|
98
|
+
|
|
99
|
+
# Helpers
|
|
100
|
+
self._widths: Dict[str, int] = {}
|
|
101
|
+
self._colors: Dict[str, str] = {}
|
|
102
|
+
self._alignment: Dict[str, str] = {}
|
|
103
|
+
self._aggregate: Dict[str, str | None] = {}
|
|
104
|
+
self._new_row: Dict[str, Any] = defaultdict(str)
|
|
105
|
+
self._aggregate_n: Dict[str, int] = defaultdict(int)
|
|
106
|
+
|
|
107
|
+
self.num_rows = 0
|
|
108
|
+
self.columns: List[str] = []
|
|
109
|
+
self.finished_rows: List[Dict[Any, Any]] = []
|
|
110
|
+
|
|
111
|
+
self._needs_line_ending = False
|
|
112
|
+
self._last_time_row_printed = 0
|
|
113
|
+
self._last_header_printed_at_row_count = 0
|
|
114
|
+
|
|
115
|
+
self.header_printed = False
|
|
116
|
+
self.progress_bar_active = False
|
|
117
|
+
self._refresh_progress_bar: Callable = lambda: None
|
|
118
|
+
self._needs_splitter = False
|
|
119
|
+
|
|
120
|
+
self._last_row_content = None
|
|
121
|
+
|
|
122
|
+
allowed_styles = [getattr(symbols, class_name) for class_name in dir(symbols)]
|
|
123
|
+
allowed_styles = [x for x in allowed_styles if inspect.isclass(x) and issubclass(x, symbols.Symbols)]
|
|
124
|
+
style_name_to_style = {x.name: x for x in allowed_styles if hasattr(x, "name")}
|
|
125
|
+
|
|
126
|
+
if table_style in style_name_to_style:
|
|
127
|
+
self._symbols = style_name_to_style[table_style]
|
|
128
|
+
else:
|
|
129
|
+
allowed_style_names = list(style_name_to_style)
|
|
130
|
+
raise KeyError(f"Style '{table_style}' not in {allowed_style_names}!")
|
|
131
|
+
|
|
132
|
+
for column in columns:
|
|
133
|
+
self.add_column(column)
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def get_default_format_func(decimal_places):
|
|
137
|
+
def fmt(x):
|
|
138
|
+
if isinstance(x, int):
|
|
139
|
+
return x
|
|
140
|
+
else:
|
|
141
|
+
try:
|
|
142
|
+
return format(x, f".{decimal_places}f")
|
|
143
|
+
except Exception:
|
|
144
|
+
return x
|
|
145
|
+
|
|
146
|
+
return fmt
|
|
147
|
+
|
|
148
|
+
def add_column(self, name, *, width=None, alignment=None, color=None, aggregate=None):
|
|
149
|
+
"""Add column to the table.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
name: Name of the column. Will be displayed in the header.
|
|
153
|
+
width: Width of the column. If width is smaller than the name, width will be automatically
|
|
154
|
+
increased to the smallest possible value. This means setting `width=0` will set the column
|
|
155
|
+
to the smallest possible width allowed by `name`.
|
|
156
|
+
alignment: Alignment of the data in the column.
|
|
157
|
+
color: Color of the header and the data in the column.
|
|
158
|
+
aggregate: By default, there's no aggregation. But if this is for example 'mean', then
|
|
159
|
+
after every update in the current row, the mean of the provided values will be
|
|
160
|
+
displayed. Aggregation is reset at every new row.
|
|
161
|
+
"""
|
|
162
|
+
assert not self.header_printed, "Columns cannot be modified after displaying the first row!"
|
|
163
|
+
|
|
164
|
+
if name in self.columns:
|
|
165
|
+
logging.info(f"Column '{name}' already exists!")
|
|
166
|
+
else:
|
|
167
|
+
self.columns.append(name)
|
|
168
|
+
|
|
169
|
+
self._colors.setdefault(name, "")
|
|
170
|
+
if color is not None:
|
|
171
|
+
if isinstance(color, str):
|
|
172
|
+
color = [color]
|
|
173
|
+
for str_color in color:
|
|
174
|
+
byte_color = self._maybe_convert_to_colorama(str_color)
|
|
175
|
+
self._check_color(byte_color, str_color)
|
|
176
|
+
self._colors[name] += byte_color
|
|
177
|
+
|
|
178
|
+
alignment = alignment if alignment is not None else self.default_alignment
|
|
179
|
+
self._alignment[name] = alignment
|
|
180
|
+
|
|
181
|
+
assert aggregate in [
|
|
182
|
+
None,
|
|
183
|
+
"mean",
|
|
184
|
+
"sum",
|
|
185
|
+
"max",
|
|
186
|
+
"min",
|
|
187
|
+
], "Allowed aggregate types: [None, 'mean', 'sum', 'max', 'min']"
|
|
188
|
+
self._aggregate[name] = aggregate
|
|
189
|
+
|
|
190
|
+
width = width if width is not None else self.default_width
|
|
191
|
+
if width < len(name):
|
|
192
|
+
width = len(name)
|
|
193
|
+
self._widths[name] = width
|
|
194
|
+
|
|
195
|
+
def add_columns(self, iterable, **kwds):
|
|
196
|
+
"""Add multiple columns to the table."""
|
|
197
|
+
for column in iterable:
|
|
198
|
+
self.add_column(column, **kwds)
|
|
199
|
+
|
|
200
|
+
def next_row(self, save=True, split=False, header=False):
|
|
201
|
+
"""End the current row."""
|
|
202
|
+
self._print_row()
|
|
203
|
+
self._maybe_line_ending()
|
|
204
|
+
if self.progress_bar_active:
|
|
205
|
+
self._refresh_progress_bar()
|
|
206
|
+
sys.stdout.flush()
|
|
207
|
+
|
|
208
|
+
if save and len(self._new_row) > 0:
|
|
209
|
+
self.finished_rows.append(self._new_row)
|
|
210
|
+
self.num_rows += 1
|
|
211
|
+
|
|
212
|
+
self._new_row = defaultdict(str)
|
|
213
|
+
self._aggregate_n = defaultdict(int)
|
|
214
|
+
|
|
215
|
+
if split:
|
|
216
|
+
self._needs_splitter = True
|
|
217
|
+
|
|
218
|
+
if header:
|
|
219
|
+
self._print_header(top=False)
|
|
220
|
+
|
|
221
|
+
def close(self):
|
|
222
|
+
"""End the table and close it."""
|
|
223
|
+
self._needs_splitter = False
|
|
224
|
+
|
|
225
|
+
self.next_row()
|
|
226
|
+
self._print_bottom_bar()
|
|
227
|
+
self.header_printed = False
|
|
228
|
+
self._print()
|
|
229
|
+
|
|
230
|
+
def to_list(self):
|
|
231
|
+
"""Convert to Python nested list."""
|
|
232
|
+
return [[row[col] for col in self.columns] for row in self.finished_rows]
|
|
233
|
+
|
|
234
|
+
def to_numpy(self):
|
|
235
|
+
"""Convert to numpy array."""
|
|
236
|
+
import numpy as np
|
|
237
|
+
|
|
238
|
+
return np.array(self.to_list())
|
|
239
|
+
|
|
240
|
+
def to_df(self):
|
|
241
|
+
"""Convert to pandas DataFrame."""
|
|
242
|
+
import pandas as pd
|
|
243
|
+
|
|
244
|
+
return pd.DataFrame(self.to_list(), columns=self.columns)
|
|
245
|
+
|
|
246
|
+
def display(self):
|
|
247
|
+
"""Display the whole table. Can be used after closing the table."""
|
|
248
|
+
if self.header_printed:
|
|
249
|
+
self.close()
|
|
250
|
+
self._display_custom(self.to_list())
|
|
251
|
+
|
|
252
|
+
def update(self, key, value, *, weight=1):
|
|
253
|
+
"""Update value in the current row."""
|
|
254
|
+
assert key in self.columns, f"Column '{key}' not in {self.columns}"
|
|
255
|
+
|
|
256
|
+
if self._aggregate[key] == "sum":
|
|
257
|
+
aggr_value = self._new_row[key] if self._aggregate_n[key] > 0 else 0
|
|
258
|
+
self._new_row[key] = aggr_value + value * weight
|
|
259
|
+
self._aggregate_n[key] += weight
|
|
260
|
+
|
|
261
|
+
elif self._aggregate[key] == "mean":
|
|
262
|
+
n = self._aggregate_n[key]
|
|
263
|
+
aggr_value = self._new_row[key] if n > 0 else 0
|
|
264
|
+
self._new_row[key] = (aggr_value * n + value * weight) / (n + weight)
|
|
265
|
+
self._aggregate_n[key] += weight
|
|
266
|
+
|
|
267
|
+
elif self._aggregate[key] == "max":
|
|
268
|
+
n = self._aggregate_n[key]
|
|
269
|
+
aggr_value = self._new_row[key] if n > 0 else -float("inf")
|
|
270
|
+
self._new_row[key] = max(aggr_value, value)
|
|
271
|
+
self._aggregate_n[key] += 1
|
|
272
|
+
|
|
273
|
+
elif self._aggregate[key] == "min":
|
|
274
|
+
n = self._aggregate_n[key]
|
|
275
|
+
aggr_value = self._new_row[key] if n > 0 else float("inf")
|
|
276
|
+
self._new_row[key] = min(aggr_value, value)
|
|
277
|
+
self._aggregate_n[key] += 1
|
|
278
|
+
|
|
279
|
+
else:
|
|
280
|
+
self._new_row[key] = value
|
|
281
|
+
|
|
282
|
+
t0 = time.time()
|
|
283
|
+
td = t0 - self._last_time_row_printed
|
|
284
|
+
if self.print_row_on_update and td > 1 / self.refresh_rate:
|
|
285
|
+
self._last_time_row_printed = t0
|
|
286
|
+
|
|
287
|
+
if not self.progress_bar_active:
|
|
288
|
+
self._print_row()
|
|
289
|
+
elif self.embedded_progress_bar:
|
|
290
|
+
self._refresh_progress_bar()
|
|
291
|
+
|
|
292
|
+
def update_from_dict(self, dictionary):
|
|
293
|
+
"""Update multiple values in the current row."""
|
|
294
|
+
for key, value in dictionary.items():
|
|
295
|
+
self.update(key, value)
|
|
296
|
+
|
|
297
|
+
def _apply_cell_formatting(self, str_value: str, column: str):
|
|
298
|
+
width = self._widths[column]
|
|
299
|
+
|
|
300
|
+
alignment = self._alignment[column]
|
|
301
|
+
if alignment == "center":
|
|
302
|
+
str_value = str_value.center(width)
|
|
303
|
+
elif alignment == "left":
|
|
304
|
+
str_value = str_value.ljust(width)
|
|
305
|
+
elif alignment == "right":
|
|
306
|
+
str_value = str_value.rjust(width)
|
|
307
|
+
else:
|
|
308
|
+
allowed_alignments = ["center", "left", "right"]
|
|
309
|
+
raise KeyError(f"Alignment '{alignment}' not in {allowed_alignments}!")
|
|
310
|
+
|
|
311
|
+
clipped = len(str_value) > width
|
|
312
|
+
str_value = "".join(
|
|
313
|
+
[
|
|
314
|
+
" ", # space at the beginning of the row
|
|
315
|
+
str_value[:width].center(width),
|
|
316
|
+
self._symbols.dots if clipped else " ",
|
|
317
|
+
]
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
if self._colors[column] is not None:
|
|
321
|
+
str_value = f"{self._colors[column]}{str_value}{Style.RESET_ALL}"
|
|
322
|
+
return str_value
|
|
323
|
+
|
|
324
|
+
def _bar(self, left: str, center: str, right: str):
|
|
325
|
+
content_list: List[str] = []
|
|
326
|
+
for col in self.columns:
|
|
327
|
+
content_list.append(self._symbols.horizontal * (self._widths[col] + 2))
|
|
328
|
+
|
|
329
|
+
center = center.join(content_list)
|
|
330
|
+
content = ["\r", left, center, right]
|
|
331
|
+
self._print("".join(content), end="")
|
|
332
|
+
|
|
333
|
+
def _bar_custom_center(self, left: str, center: List[str] | str, right: str):
|
|
334
|
+
"""UNUSED"""
|
|
335
|
+
content_list: List[str] = []
|
|
336
|
+
|
|
337
|
+
if isinstance(center, str):
|
|
338
|
+
center = [center] * (len(self.columns) - 1)
|
|
339
|
+
assert len(center) == len(self.columns) - 1
|
|
340
|
+
|
|
341
|
+
for col in self.columns:
|
|
342
|
+
content_list.append(self._symbols.horizontal * (self._widths[col] + 2))
|
|
343
|
+
if center:
|
|
344
|
+
content_list.append(center.pop(0))
|
|
345
|
+
|
|
346
|
+
center = "".join(content_list)
|
|
347
|
+
content = ["\r", left, center, right]
|
|
348
|
+
self._print("".join(content), end="")
|
|
349
|
+
|
|
350
|
+
def _print_transition_bar(self, previous_n_cols, new_n_cols):
|
|
351
|
+
"""UNUSED"""
|
|
352
|
+
assert previous_n_cols > 0
|
|
353
|
+
assert new_n_cols > previous_n_cols
|
|
354
|
+
added_cols = new_n_cols - previous_n_cols
|
|
355
|
+
|
|
356
|
+
symbols = [
|
|
357
|
+
*([self._symbols.all] * previous_n_cols),
|
|
358
|
+
*([self._symbols.no_up] * (added_cols - 1)),
|
|
359
|
+
]
|
|
360
|
+
return self._bar_custom_center(left=self._symbols.no_left, center=symbols, right=self._symbols.down_left)
|
|
361
|
+
|
|
362
|
+
def _print_top_bar(self):
|
|
363
|
+
return self._bar(left=self._symbols.down_right, center=self._symbols.no_up, right=self._symbols.down_left)
|
|
364
|
+
|
|
365
|
+
def _print_bottom_bar(self):
|
|
366
|
+
return self._bar(left=self._symbols.up_right, center=self._symbols.no_down, right=self._symbols.up_left)
|
|
367
|
+
|
|
368
|
+
def _print_center_bar(self):
|
|
369
|
+
return self._bar(left=self._symbols.no_left, center=self._symbols.all, right=self._symbols.no_right)
|
|
370
|
+
|
|
371
|
+
@staticmethod
|
|
372
|
+
def _maybe_convert_to_colorama(color):
|
|
373
|
+
if isinstance(color, str):
|
|
374
|
+
if hasattr(Fore, color.upper()):
|
|
375
|
+
return getattr(Fore, color.upper())
|
|
376
|
+
if hasattr(Style, color.upper()):
|
|
377
|
+
return getattr(Style, color.upper())
|
|
378
|
+
return color
|
|
379
|
+
|
|
380
|
+
@staticmethod
|
|
381
|
+
def _check_color(color, color_str=None):
|
|
382
|
+
if color_str is None:
|
|
383
|
+
color_str = color
|
|
384
|
+
assert color in ALL_COLORS_STYLES, f"Only colorama colors are allowed, not '{color_str}'! Available: {ALL_COLORS_STYLES_NAMES}"
|
|
385
|
+
|
|
386
|
+
def _print_header(self, top=True):
|
|
387
|
+
assert self.columns, "Columns are required! Use .add_column method or specify them in __init__!"
|
|
388
|
+
|
|
389
|
+
self._needs_splitter = False
|
|
390
|
+
|
|
391
|
+
if top:
|
|
392
|
+
self._print_top_bar()
|
|
393
|
+
else:
|
|
394
|
+
self._print_center_bar()
|
|
395
|
+
self._print()
|
|
396
|
+
|
|
397
|
+
content = []
|
|
398
|
+
for column in self.columns:
|
|
399
|
+
value = self._apply_cell_formatting(column, column)
|
|
400
|
+
content.append(value)
|
|
401
|
+
s = "".join(["\r", self._symbols.vertical, self._symbols.vertical.join(content), self._symbols.vertical])
|
|
402
|
+
self._print(s)
|
|
403
|
+
|
|
404
|
+
self._last_header_printed_at_row_count = self.num_rows
|
|
405
|
+
self.header_printed = True
|
|
406
|
+
self._print_center_bar()
|
|
407
|
+
self._print()
|
|
408
|
+
self._last_row_content = "header"
|
|
409
|
+
|
|
410
|
+
def _print_splitter(self):
|
|
411
|
+
if self._last_row_content == "row":
|
|
412
|
+
self._print_center_bar()
|
|
413
|
+
self._print()
|
|
414
|
+
|
|
415
|
+
self._needs_splitter = False
|
|
416
|
+
self._last_row_content = "splitter"
|
|
417
|
+
|
|
418
|
+
def _get_row(self):
|
|
419
|
+
content = []
|
|
420
|
+
for column in self.columns:
|
|
421
|
+
value = self._new_row[column]
|
|
422
|
+
value = self.custom_format(value)
|
|
423
|
+
value = self._apply_cell_formatting(str_value=str(value), column=column)
|
|
424
|
+
content.append(value)
|
|
425
|
+
return "".join(["\r", self._symbols.vertical, self._symbols.vertical.join(content), self._symbols.vertical])
|
|
426
|
+
|
|
427
|
+
def _print_row(self):
|
|
428
|
+
if not self.header_printed:
|
|
429
|
+
self._print_header(top=True)
|
|
430
|
+
|
|
431
|
+
if self.reprint_header_every_n_rows != 0:
|
|
432
|
+
if self.num_rows - self._last_header_printed_at_row_count >= self.reprint_header_every_n_rows:
|
|
433
|
+
self._print_header(top=False)
|
|
434
|
+
|
|
435
|
+
if self._needs_splitter:
|
|
436
|
+
self._print_splitter()
|
|
437
|
+
|
|
438
|
+
if len(self._new_row) == 0:
|
|
439
|
+
return
|
|
440
|
+
self._needs_line_ending = True
|
|
441
|
+
self._print(self._get_row(), end="")
|
|
442
|
+
|
|
443
|
+
def _print_progress_bar(self, i, n, show_before=" ", show_after=" ", embedded=False):
|
|
444
|
+
i = min(i, n) # clip the iteration number to be not bigger than the total number of iterations
|
|
445
|
+
terminal_width = shutil.get_terminal_size(fallback=(0, 0)).columns or float("inf")
|
|
446
|
+
|
|
447
|
+
if not embedded:
|
|
448
|
+
tot_width = sum(self._widths.values()) + 3 * (len(self._widths) - 1) + 2
|
|
449
|
+
if tot_width >= terminal_width - 1:
|
|
450
|
+
tot_width = terminal_width - 2
|
|
451
|
+
|
|
452
|
+
tot_width = tot_width - len(show_before) - len(show_after)
|
|
453
|
+
num_hashes = math.ceil(i / n * tot_width)
|
|
454
|
+
num_empty = tot_width - num_hashes
|
|
455
|
+
|
|
456
|
+
self._print(
|
|
457
|
+
self._symbols.vertical,
|
|
458
|
+
show_before,
|
|
459
|
+
self._symbols.pbar_filled * num_hashes,
|
|
460
|
+
self._symbols.pbar_empty * num_empty,
|
|
461
|
+
show_after,
|
|
462
|
+
self._symbols.vertical,
|
|
463
|
+
end="\r",
|
|
464
|
+
sep="",
|
|
465
|
+
)
|
|
466
|
+
else:
|
|
467
|
+
row = self._get_row()
|
|
468
|
+
new_row = []
|
|
469
|
+
for idx, letter in enumerate(row):
|
|
470
|
+
if idx / len(row) <= i / n:
|
|
471
|
+
if letter == " ":
|
|
472
|
+
letter = self._symbols.embedded_pbar_filled
|
|
473
|
+
new_row.append(letter)
|
|
474
|
+
row = "".join(new_row)
|
|
475
|
+
self._print(row, end="\r")
|
|
476
|
+
sys.stdout.flush()
|
|
477
|
+
|
|
478
|
+
def _maybe_line_ending(self):
|
|
479
|
+
if self._needs_line_ending:
|
|
480
|
+
self._print()
|
|
481
|
+
self._needs_splitter = False
|
|
482
|
+
self._needs_line_ending = False
|
|
483
|
+
self._last_row_content = "row"
|
|
484
|
+
|
|
485
|
+
def _display_custom(self, data):
|
|
486
|
+
if self.header_printed:
|
|
487
|
+
self.close()
|
|
488
|
+
|
|
489
|
+
for row in data:
|
|
490
|
+
assert len(row) == len(self.columns)
|
|
491
|
+
for key, value in zip(self.columns, row):
|
|
492
|
+
self._new_row[key] = value
|
|
493
|
+
self._print_row()
|
|
494
|
+
self.next_row(save=False)
|
|
495
|
+
self.close()
|
|
496
|
+
self._last_row_content = "close"
|
|
497
|
+
|
|
498
|
+
def _print(self, *args, **kwds):
|
|
499
|
+
for file in self.files:
|
|
500
|
+
print(*args, **kwds, file=file)
|
|
501
|
+
|
|
502
|
+
def __call__(self, iterator, *range_args, length=None, prefix="", show_throughput=None, show_progress=None):
|
|
503
|
+
"""Display progress bar over the iterator. Try to figure out the iterator length."""
|
|
504
|
+
global ITERATOR_LENGTH_UNKNOWN_WARNED_ONCE, ITERATOR_LENGTH_CACHE
|
|
505
|
+
|
|
506
|
+
if not self.header_printed:
|
|
507
|
+
self._print_header()
|
|
508
|
+
|
|
509
|
+
if show_throughput is None:
|
|
510
|
+
show_throughput = self.default_show_throughput
|
|
511
|
+
if show_progress is None:
|
|
512
|
+
show_progress = self.default_show_progress
|
|
513
|
+
|
|
514
|
+
if isinstance(iterator, int):
|
|
515
|
+
iterator = range(iterator, *range_args)
|
|
516
|
+
else:
|
|
517
|
+
assert len(range_args) == 0, "Unnamed args are not allowed here!"
|
|
518
|
+
|
|
519
|
+
if length is None and not hasattr(iterator, "__len__"):
|
|
520
|
+
if not ITERATOR_LENGTH_UNKNOWN_WARNED_ONCE:
|
|
521
|
+
logging.warning("Iterator length is unknown!")
|
|
522
|
+
ITERATOR_LENGTH_UNKNOWN_WARNED_ONCE = True
|
|
523
|
+
|
|
524
|
+
# We have a back-up mechanism in case of unknown-length iterators.
|
|
525
|
+
# If we progress over the same unknown length iterator more than once,
|
|
526
|
+
# we will use its length from the previous launch.
|
|
527
|
+
save_length_to_cache = True
|
|
528
|
+
|
|
529
|
+
if id(iterator) in ITERATOR_LENGTH_CACHE:
|
|
530
|
+
length = ITERATOR_LENGTH_CACHE[id(iterator)]
|
|
531
|
+
else:
|
|
532
|
+
length = 1
|
|
533
|
+
else:
|
|
534
|
+
save_length_to_cache = False
|
|
535
|
+
length = length or len(iterator)
|
|
536
|
+
|
|
537
|
+
t_last_printed = -float("inf")
|
|
538
|
+
t_beginning = time.time()
|
|
539
|
+
|
|
540
|
+
self.progress_bar_active = True
|
|
541
|
+
|
|
542
|
+
idx = 0
|
|
543
|
+
for idx, element in enumerate(iterator):
|
|
544
|
+
if time.time() - t_last_printed > 1 / self.refresh_rate:
|
|
545
|
+
# Reenable here, in case of nested progress bars
|
|
546
|
+
self.progress_bar_active = True
|
|
547
|
+
|
|
548
|
+
self._print(end="\r")
|
|
549
|
+
s = time.time() - t_beginning
|
|
550
|
+
throughput = idx / s if s > 0 else 0.0
|
|
551
|
+
|
|
552
|
+
full_prefix = [" [", prefix]
|
|
553
|
+
|
|
554
|
+
inside_brackets = []
|
|
555
|
+
if show_throughput:
|
|
556
|
+
inside_brackets.append(f"{throughput: <.2f} it/s")
|
|
557
|
+
if show_progress:
|
|
558
|
+
inside_brackets.append(f"{idx}/{length}")
|
|
559
|
+
inside_brackets = ", ".join(inside_brackets)
|
|
560
|
+
full_prefix.append(inside_brackets)
|
|
561
|
+
|
|
562
|
+
full_prefix.append("] ")
|
|
563
|
+
full_prefix = "".join(full_prefix)
|
|
564
|
+
full_prefix = full_prefix if full_prefix != " [] " else " "
|
|
565
|
+
|
|
566
|
+
self._refresh_progress_bar = lambda: self._print_progress_bar(
|
|
567
|
+
idx,
|
|
568
|
+
length,
|
|
569
|
+
show_before=full_prefix,
|
|
570
|
+
embedded=self.embedded_progress_bar,
|
|
571
|
+
)
|
|
572
|
+
self._refresh_progress_bar()
|
|
573
|
+
|
|
574
|
+
t_last_printed = time.time()
|
|
575
|
+
yield element
|
|
576
|
+
if save_length_to_cache:
|
|
577
|
+
ITERATOR_LENGTH_CACHE[id(iterator)] = idx
|
|
578
|
+
|
|
579
|
+
self.progress_bar_active = False
|
|
580
|
+
self._print_row()
|
|
581
|
+
|
|
582
|
+
def __setitem__(self, key, value):
|
|
583
|
+
self.update(key, value, weight=1)
|
|
584
|
+
|
|
585
|
+
def __getitem__(self, key):
|
|
586
|
+
assert key in self.columns, f"Column '{key}' not in {self.columns}"
|
|
587
|
+
|
|
588
|
+
return self._new_row[key]
|