-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbar.py
More file actions
206 lines (175 loc) · 7.33 KB
/
bar.py
File metadata and controls
206 lines (175 loc) · 7.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import sys
import asyncio
import numpy as np
import numpy.core.defchararray as chars
import traceback
from typing import Dict, Optional, Union
import warnings
class Bar:
"""An async progress bar that doesn't look terrible in your terminal.
Shows progress with a visual bar, percentage complete, timing info, and
any custom metrics you want to display. Built for async/await and handles
cleanup automatically when done.
Attributes:
iterations (int): Total number of items to process.
title (str): Display name for this progress bar.
steps (int): Width of the progress bar in characters.
items (Dict[str, str]): Additional metrics to display alongside progress.
"""
def __init__(
self, iterations: int, title: str = "Loading", steps: int = 40
) -> None:
"""Initialize a new progress bar.
Args:
iterations: Total count of items to process. Must be positive.
title: Label for this operation, appears as "Training: [####...]".
steps: Character width of the progress bar. Higher values give
smoother visual updates but with slight performance cost.
Raises:
ValueError: When iterations is not a positive integer.
"""
# Total items we need to process
self.iterations: int = iterations
# Display label for this operation
self.title: str = title
# Character width of the progress bar
self.steps: int = steps
# Dictionary for storing custom metrics to display
self.items: Dict[str, str] = {}
async def update(self, batch: int, time: float, final: bool = False) -> None:
"""Refresh the progress bar with current progress information.
Recalculates completion percentage, processing speed, estimated time
remaining, and redraws the entire progress display.
Args:
batch: Number of items completed so far.
time: Start timestamp (from time.time() or event loop timer).
final: When True, adds newline after final update.
Note:
Uses asyncio event loop time for consistent timing calculations.
"""
# Calculate how long this has been running
elapsed: float = np.subtract(
asyncio.get_event_loop().time(), time
)
# Calculate completion percentage
percentage: float = np.divide(batch, self.iterations)
# Calculate processing rate (items per second)
throughput: np.array = np.where(
np.greater(elapsed, 0),
np.floor_divide(batch, elapsed),
0
)
# Estimate remaining time based on current progress
eta: np.array = np.where(
np.greater(batch, 0),
np.divide(
np.multiply(elapsed, np.subtract(self.iterations, batch)),
batch
),
0,
)
# Construct the visual progress bar
bar: str = chars.add(
"|",
chars.add(
# Fill completed portion with hash marks
"".join(np.repeat("#", np.ceil(np.multiply(percentage, self.steps)))),
chars.add(
# Fill remaining portion with spaces
"".join(
np.repeat(
" ",
np.subtract(
self.steps,
np.ceil(np.multiply(percentage, self.steps))
),
)
),
# Add progress counter like 042/100
f"| {batch:03d}/{self.iterations:03d}",
),
),
)
# Build complete output line
sys.stdout.write(
chars.add(
chars.add(
chars.add(
# Core progress info: title, bar, percentage, timing
f"\r{self.title}: {bar} [{np.multiply(percentage, 100):.2f}%] in {elapsed:.1f}s "
f"({throughput:.1f}/s, ETA: {eta:.1f}s)",
# Append custom metrics if any exist
np.where(
np.greater(np.size(self.items), 0),
chars.add(
" (",
chars.add(
# Format custom metrics as "key: value, key: value"
", ".join(
[
f"{name}: {value}"
for name, value in self.items.items()
]
),
")",
),
),
"",
),
),
"",
),
"",
)
)
# Add newline when completely finished
if final:
sys.stdout.write("\n")
# Force output to appear immediately
sys.stdout.flush()
async def postfix(self, **kwargs: Union[str, int, float]) -> None:
"""Update custom metrics displayed alongside the progress bar.
Useful for showing dynamic values like loss, accuracy, learning rate,
or other relevant statistics during processing.
Args:
**kwargs: Metrics to display as key=value pairs. Example:
postfix(loss=0.42, accuracy=0.89, lr=0.001)
"""
# Update our metrics dictionary with new values
self.items.update(kwargs)
async def __aenter__(self) -> "Bar":
"""Enable usage with async context managers.
Returns:
The Bar instance for use within the async with block.
Example:
async with Bar(100, "Processing") as pbar:
for i in range(100):
await pbar.update(i+1, start_time)
"""
return self
async def __aexit__(
self,
exc_type: Optional[type],
exc_val: Optional[BaseException],
exc_tb: Optional[traceback.TracebackException],
) -> None:
"""Handle cleanup when exiting the async context manager.
On normal completion, displays final 100% progress update.
On exceptions, shows a warning about the error that occurred.
Args:
exc_type: Exception class if an error occurred, None otherwise.
exc_val: The exception instance that was raised.
exc_tb: Traceback object containing error details.
"""
if exc_type is None:
# Normal completion - show final progress update
await self.update(
self.iterations,
asyncio.get_event_loop().time(),
final=True
)
else:
# Exception occurred - warn user about the error
warnings.warn(
f"\n{self.title} encountered an error: {exc_val}"
)