asyncprogress 0.3.5__tar.gz → 0.3.6__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.
- {asyncprogress-0.3.5 → asyncprogress-0.3.6}/PKG-INFO +4 -9
- {asyncprogress-0.3.5 → asyncprogress-0.3.6}/README.md +0 -5
- {asyncprogress-0.3.5 → asyncprogress-0.3.6}/pyproject.toml +7 -9
- {asyncprogress-0.3.5 → asyncprogress-0.3.6}/src/asyncprogress/__init__.py +1 -1
- {asyncprogress-0.3.5 → asyncprogress-0.3.6}/src/asyncprogress/_eta.py +10 -10
- {asyncprogress-0.3.5 → asyncprogress-0.3.6}/src/asyncprogress/_renderer.py +36 -45
- {asyncprogress-0.3.5 → asyncprogress-0.3.6}/src/asyncprogress/_terminal.py +4 -4
- {asyncprogress-0.3.5 → asyncprogress-0.3.6}/LICENSE +0 -0
- {asyncprogress-0.3.5 → asyncprogress-0.3.6}/src/asyncprogress/_as_completed.py +0 -0
- {asyncprogress-0.3.5 → asyncprogress-0.3.6}/src/asyncprogress/_bar.py +0 -0
- {asyncprogress-0.3.5 → asyncprogress-0.3.6}/src/asyncprogress/_gather.py +0 -0
- {asyncprogress-0.3.5 → asyncprogress-0.3.6}/src/asyncprogress/_iterator.py +0 -0
- {asyncprogress-0.3.5 → asyncprogress-0.3.6}/src/asyncprogress/_multi.py +0 -0
- {asyncprogress-0.3.5 → asyncprogress-0.3.6}/src/asyncprogress/py.typed +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: asyncprogress
|
|
3
|
-
Version: 0.3.
|
|
4
|
-
Summary: Async-aware progress bars for Python's asyncio ecosystem
|
|
3
|
+
Version: 0.3.6
|
|
4
|
+
Summary: Async-aware progress bars for Python's asyncio ecosystem
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
7
|
-
Keywords: asyncio,progress,progress-bar,
|
|
7
|
+
Keywords: async,asyncio,progress,progress-bar,tqdm
|
|
8
8
|
Author: AgentSoft
|
|
9
9
|
Author-email: agentsoft@example.com
|
|
10
10
|
Requires-Python: >=3.9,<4.0
|
|
@@ -19,9 +19,8 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
23
|
Provides-Extra: color
|
|
24
|
-
Requires-Dist: colorama (>=0.4.4) ; extra == "color"
|
|
25
24
|
Project-URL: Homepage, https://github.com/agentsoft/asyncprogress
|
|
26
25
|
Project-URL: Repository, https://github.com/agentsoft/asyncprogress
|
|
27
26
|
Description-Content-Type: text/markdown
|
|
@@ -49,7 +48,3 @@ synchronous code. `asyncprogress` is built from the ground up for `asyncio`:
|
|
|
49
48
|
## Installation
|
|
50
49
|
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
pip install asyncprogress
|
|
55
|
-
```
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "asyncprogress"
|
|
3
|
-
version = "0.3.
|
|
4
|
-
description = "Async-aware progress bars for Python's asyncio ecosystem
|
|
3
|
+
version = "0.3.6"
|
|
4
|
+
description = "Async-aware progress bars for Python's asyncio ecosystem"
|
|
5
5
|
authors = ["AgentSoft <agentsoft@example.com>"]
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
license = "MIT"
|
|
8
8
|
homepage = "https://github.com/agentsoft/asyncprogress"
|
|
9
9
|
repository = "https://github.com/agentsoft/asyncprogress"
|
|
10
|
-
keywords = ["asyncio", "progress", "progress-bar", "
|
|
10
|
+
keywords = ["async", "asyncio", "progress", "progress-bar", "tqdm"]
|
|
11
11
|
classifiers = [
|
|
12
12
|
"Development Status :: 4 - Beta",
|
|
13
13
|
"Intended Audience :: Developers",
|
|
@@ -17,14 +17,13 @@ classifiers = [
|
|
|
17
17
|
"Programming Language :: Python :: 3.10",
|
|
18
18
|
"Programming Language :: Python :: 3.11",
|
|
19
19
|
"Programming Language :: Python :: 3.12",
|
|
20
|
-
"Topic :: Software Development :: Libraries",
|
|
20
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
21
21
|
"Framework :: AsyncIO",
|
|
22
22
|
]
|
|
23
23
|
packages = [{include = "asyncprogress", from = "src"}]
|
|
24
24
|
|
|
25
25
|
[tool.poetry.dependencies]
|
|
26
26
|
python = "^3.9"
|
|
27
|
-
colorama = {version = ">=0.4.4", optional = true}
|
|
28
27
|
|
|
29
28
|
[tool.poetry.extras]
|
|
30
29
|
color = ["colorama"]
|
|
@@ -42,19 +41,18 @@ build-backend = "poetry.core.masonry.api"
|
|
|
42
41
|
[tool.pytest.ini_options]
|
|
43
42
|
asyncio_mode = "auto"
|
|
44
43
|
testpaths = ["tests"]
|
|
45
|
-
addopts = "--tb=short"
|
|
46
44
|
|
|
47
45
|
[tool.ruff]
|
|
48
46
|
line-length = 100
|
|
49
47
|
target-version = "py39"
|
|
50
48
|
|
|
51
49
|
[tool.ruff.lint]
|
|
52
|
-
select = ["E", "F", "
|
|
50
|
+
select = ["E", "F", "UP", "I"]
|
|
53
51
|
ignore = ["E501"]
|
|
54
52
|
|
|
55
53
|
[tool.coverage.run]
|
|
56
|
-
source = ["
|
|
57
|
-
|
|
54
|
+
source = ["asyncprogress"]
|
|
55
|
+
branch = true
|
|
58
56
|
|
|
59
57
|
[tool.coverage.report]
|
|
60
58
|
show_missing = true
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
EWMA-based ETA calculator for
|
|
3
|
-
|
|
4
|
-
Pure math — no I/O, fully unit-testable.
|
|
2
|
+
EWMA-based ETA calculator for async progress tracking.
|
|
5
3
|
"""
|
|
6
4
|
|
|
7
5
|
from __future__ import annotations
|
|
@@ -11,12 +9,11 @@ class EWMACalculator:
|
|
|
11
9
|
"""
|
|
12
10
|
Exponential Weighted Moving Average calculator for ETA estimation.
|
|
13
11
|
|
|
14
|
-
Uses EWMA over completion timestamps
|
|
15
|
-
which
|
|
12
|
+
Uses EWMA over completion timestamps to estimate items/second rate,
|
|
13
|
+
which handles bursty async I/O patterns better than simple linear regression.
|
|
16
14
|
|
|
17
15
|
Args:
|
|
18
|
-
alpha: Smoothing factor
|
|
19
|
-
Higher values give more weight to recent samples.
|
|
16
|
+
alpha: Smoothing factor. Higher = more weight on recent samples.
|
|
20
17
|
Recommended range: 0.1 (smooth) to 0.5 (reactive).
|
|
21
18
|
"""
|
|
22
19
|
|
|
@@ -33,15 +30,18 @@ class EWMACalculator:
|
|
|
33
30
|
if self._last_timestamp is not None and self._last_completed is not None:
|
|
34
31
|
dt = timestamp - self._last_timestamp
|
|
35
32
|
dc = completed - self._last_completed
|
|
36
|
-
if dt > 0:
|
|
33
|
+
if dt > 0 and dc >= 0:
|
|
37
34
|
instant_rate = dc / dt
|
|
38
35
|
if self._ewma_rate is None:
|
|
39
36
|
self._ewma_rate = instant_rate
|
|
40
37
|
else:
|
|
41
38
|
self._ewma_rate = (
|
|
42
|
-
self._alpha * instant_rate
|
|
39
|
+
self._alpha * instant_rate
|
|
40
|
+
+ (1 - self._alpha) * self._ewma_rate
|
|
43
41
|
)
|
|
44
|
-
|
|
42
|
+
elif dt == 0:
|
|
43
|
+
# Same timestamp — rate stays unchanged, don't update
|
|
44
|
+
pass
|
|
45
45
|
self._last_timestamp = timestamp
|
|
46
46
|
self._last_completed = completed
|
|
47
47
|
|
|
@@ -1,37 +1,43 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Terminal rendering for
|
|
3
|
-
|
|
4
|
-
String formatting only — no I/O, fully unit-testable.
|
|
2
|
+
Terminal rendering for progress bars and spinners.
|
|
5
3
|
"""
|
|
6
4
|
|
|
7
5
|
from __future__ import annotations
|
|
8
6
|
|
|
9
7
|
SPINNER_FRAMES: list[str] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
"black": "\
|
|
13
|
-
"red": "\
|
|
14
|
-
"green": "\
|
|
15
|
-
"yellow": "\
|
|
16
|
-
"blue": "\
|
|
17
|
-
"magenta": "\
|
|
18
|
-
"cyan": "\
|
|
19
|
-
"white": "\
|
|
20
|
-
"bright_black": "\
|
|
21
|
-
"bright_red": "\
|
|
22
|
-
"bright_green": "\
|
|
23
|
-
"bright_yellow": "\
|
|
24
|
-
"bright_blue": "\
|
|
25
|
-
"bright_magenta": "\
|
|
26
|
-
"bright_cyan": "\
|
|
27
|
-
"bright_white": "\
|
|
9
|
+
_ANSI_COLORS: dict[str, str] = {
|
|
10
|
+
"black": "\x1b[30m",
|
|
11
|
+
"red": "\x1b[31m",
|
|
12
|
+
"green": "\x1b[32m",
|
|
13
|
+
"yellow": "\x1b[33m",
|
|
14
|
+
"blue": "\x1b[34m",
|
|
15
|
+
"magenta": "\x1b[35m",
|
|
16
|
+
"cyan": "\x1b[36m",
|
|
17
|
+
"white": "\x1b[37m",
|
|
18
|
+
"bright_black": "\x1b[90m",
|
|
19
|
+
"bright_red": "\x1b[91m",
|
|
20
|
+
"bright_green": "\x1b[92m",
|
|
21
|
+
"bright_yellow": "\x1b[93m",
|
|
22
|
+
"bright_blue": "\x1b[94m",
|
|
23
|
+
"bright_magenta": "\x1b[95m",
|
|
24
|
+
"bright_cyan": "\x1b[96m",
|
|
25
|
+
"bright_white": "\x1b[97m",
|
|
28
26
|
}
|
|
29
|
-
|
|
27
|
+
_ANSI_RESET = "\x1b[0m"
|
|
30
28
|
|
|
31
29
|
_SI_PREFIXES = ["", "K", "M", "G", "T", "P"]
|
|
32
30
|
_BINARY_PREFIXES = ["", "Ki", "Mi", "Gi", "Ti", "Pi"]
|
|
33
31
|
|
|
34
32
|
|
|
33
|
+
def _apply_color(text: str, color: str) -> str:
|
|
34
|
+
"""Apply ANSI color to text."""
|
|
35
|
+
code = _ANSI_COLORS.get(color.lower(), "")
|
|
36
|
+
if not code:
|
|
37
|
+
return text
|
|
38
|
+
return f"{code}{text}{_ANSI_RESET}"
|
|
39
|
+
|
|
40
|
+
|
|
35
41
|
def _format_duration(seconds: float) -> str:
|
|
36
42
|
"""Format seconds as H:MM:SS or M:SS."""
|
|
37
43
|
m, s = divmod(int(seconds), 60)
|
|
@@ -41,14 +47,6 @@ def _format_duration(seconds: float) -> str:
|
|
|
41
47
|
return f"{m}:{s:02d}"
|
|
42
48
|
|
|
43
49
|
|
|
44
|
-
def _apply_color(text: str, color: str) -> str:
|
|
45
|
-
"""Apply ANSI color to text."""
|
|
46
|
-
code = ANSI_COLORS.get(color.lower(), "")
|
|
47
|
-
if not code:
|
|
48
|
-
return text
|
|
49
|
-
return f"{code}{text}{ANSI_RESET}"
|
|
50
|
-
|
|
51
|
-
|
|
52
50
|
def _scale_value(value: float, divisor: int) -> tuple[float, str]:
|
|
53
51
|
"""Return (scaled_value, prefix_string) for display."""
|
|
54
52
|
prefixes = _BINARY_PREFIXES if divisor == 1024 else _SI_PREFIXES
|
|
@@ -63,13 +61,13 @@ class BarRenderer:
|
|
|
63
61
|
"""
|
|
64
62
|
Renders a single-line progress bar string.
|
|
65
63
|
|
|
66
|
-
No I/O
|
|
64
|
+
No I/O is performed here — only string formatting.
|
|
67
65
|
"""
|
|
68
66
|
|
|
69
67
|
def _compute_percentage(self, completed: int, total: int) -> float:
|
|
70
68
|
"""Compute percentage, handling total=0 gracefully."""
|
|
71
69
|
if total == 0:
|
|
72
|
-
return 100.0
|
|
70
|
+
return 100.0
|
|
73
71
|
return min(100.0, (completed / total) * 100.0)
|
|
74
72
|
|
|
75
73
|
def _format_count(
|
|
@@ -127,11 +125,7 @@ class BarRenderer:
|
|
|
127
125
|
unit_scale: bool = False,
|
|
128
126
|
unit_divisor: int = 1000,
|
|
129
127
|
) -> str:
|
|
130
|
-
"""
|
|
131
|
-
Render a single-line deterministic progress bar string.
|
|
132
|
-
|
|
133
|
-
Returns a string ready for terminal output (no newline).
|
|
134
|
-
"""
|
|
128
|
+
"""Return a single-line progress bar string ready for terminal output."""
|
|
135
129
|
parts: list[str] = []
|
|
136
130
|
|
|
137
131
|
if description:
|
|
@@ -139,7 +133,7 @@ class BarRenderer:
|
|
|
139
133
|
|
|
140
134
|
if total is not None:
|
|
141
135
|
pct = self._compute_percentage(completed, total)
|
|
142
|
-
filled = int(bar_width *
|
|
136
|
+
filled = int(bar_width * completed / total) if total > 0 else bar_width
|
|
143
137
|
bar = fill_char * filled + empty_char * (bar_width - filled)
|
|
144
138
|
parts.append(f" {bar} ")
|
|
145
139
|
|
|
@@ -162,9 +156,10 @@ class BarRenderer:
|
|
|
162
156
|
if show_eta and eta is not None:
|
|
163
157
|
parts.append(f"ETA: {_format_duration(eta)}")
|
|
164
158
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
159
|
+
if rate is not None:
|
|
160
|
+
rate_str = self._format_rate(rate, unit, unit_scale, unit_divisor)
|
|
161
|
+
if rate_str:
|
|
162
|
+
parts.append(rate_str)
|
|
168
163
|
|
|
169
164
|
line = " ".join(parts)
|
|
170
165
|
if color:
|
|
@@ -186,11 +181,7 @@ class BarRenderer:
|
|
|
186
181
|
unit_scale: bool = False,
|
|
187
182
|
unit_divisor: int = 1000,
|
|
188
183
|
) -> str:
|
|
189
|
-
"""
|
|
190
|
-
Render a single-line spinner for indeterminate progress.
|
|
191
|
-
|
|
192
|
-
Activates automatically when total is None.
|
|
193
|
-
"""
|
|
184
|
+
"""Render a single-line spinner for indeterminate progress."""
|
|
194
185
|
spinner = SPINNER_FRAMES[frame_index % len(SPINNER_FRAMES)]
|
|
195
186
|
parts: list[str] = []
|
|
196
187
|
if description:
|
|
@@ -15,19 +15,19 @@ def is_tty(file: TextIO = sys.stderr) -> bool:
|
|
|
15
15
|
|
|
16
16
|
def move_cursor_up(n: int = 1) -> str:
|
|
17
17
|
"""Return ANSI escape sequence to move cursor up n lines."""
|
|
18
|
-
return f"\
|
|
18
|
+
return f"\x1b[{n}A"
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def erase_line() -> str:
|
|
22
22
|
"""Return ANSI escape sequence to erase the current line."""
|
|
23
|
-
return "\
|
|
23
|
+
return "\x1b[2K\r"
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def hide_cursor() -> str:
|
|
27
27
|
"""Return ANSI escape sequence to hide the cursor."""
|
|
28
|
-
return "\
|
|
28
|
+
return "\x1b[?25l"
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def show_cursor() -> str:
|
|
32
32
|
"""Return ANSI escape sequence to show the cursor."""
|
|
33
|
-
return "\
|
|
33
|
+
return "\x1b[?25h"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|