everbar 0.1.0__py3-none-any.whl
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.
- everbar/__init__.py +7 -0
- everbar/_backends.py +174 -0
- everbar/_detect.py +80 -0
- everbar/_progress.py +98 -0
- everbar-0.1.0.dist-info/METADATA +83 -0
- everbar-0.1.0.dist-info/RECORD +7 -0
- everbar-0.1.0.dist-info/WHEEL +4 -0
everbar/__init__.py
ADDED
everbar/_backends.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Progress backends.
|
|
2
|
+
|
|
3
|
+
Each backend implements the same minimal surface:
|
|
4
|
+
|
|
5
|
+
__enter__ / __exit__ — context-manager use
|
|
6
|
+
__iter__ — iterator-wrapper use
|
|
7
|
+
update(n=1) — manual advance
|
|
8
|
+
|
|
9
|
+
Backends are constructed lazily by ``Progress``. Optional third-party
|
|
10
|
+
dependencies (``tqdm``, ``marimo``) are imported only inside the backend
|
|
11
|
+
that needs them, so ``everbar`` itself has zero required deps.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
import time
|
|
16
|
+
from collections.abc import Iterable, Iterator
|
|
17
|
+
from contextlib import nullcontext
|
|
18
|
+
from typing import Any, Self
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _len_or_none(obj: Any) -> int | None:
|
|
22
|
+
try:
|
|
23
|
+
return len(obj)
|
|
24
|
+
except (TypeError, AttributeError):
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class NullBackend(nullcontext):
|
|
29
|
+
"""No-op backend used when ``disable=True``."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, iterable: Iterable[Any] | None = None, **_: Any) -> None:
|
|
32
|
+
super().__init__()
|
|
33
|
+
self._iterable = iterable
|
|
34
|
+
|
|
35
|
+
def __iter__(self) -> Iterator[Any]:
|
|
36
|
+
return iter(self._iterable or ())
|
|
37
|
+
|
|
38
|
+
def update(self, n: int = 1) -> None: # noqa: ARG002 — protocol shape
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class FallbackBackend:
|
|
43
|
+
r"""Log-line backend for non-TTY environments.
|
|
44
|
+
|
|
45
|
+
Emits one line every ``min_interval`` seconds. Suitable for CI logs,
|
|
46
|
+
Kubernetes, CloudWatch — anywhere ``\r`` would just produce spam.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
iterable: Iterable[Any] | None = None,
|
|
52
|
+
total: int | None = None,
|
|
53
|
+
desc: str = "",
|
|
54
|
+
min_interval: float = 2.0,
|
|
55
|
+
stream: Any = None,
|
|
56
|
+
**_: Any,
|
|
57
|
+
) -> None:
|
|
58
|
+
self._iterable = iterable
|
|
59
|
+
self._total = total if total is not None else _len_or_none(iterable)
|
|
60
|
+
self._desc = desc
|
|
61
|
+
self._min_interval = min_interval
|
|
62
|
+
self._stream = stream if stream is not None else sys.stderr
|
|
63
|
+
self._n = 0
|
|
64
|
+
self._t0 = 0.0
|
|
65
|
+
self._last_log = 0.0
|
|
66
|
+
self._entered = False
|
|
67
|
+
|
|
68
|
+
def __enter__(self) -> Self:
|
|
69
|
+
self._t0 = time.monotonic()
|
|
70
|
+
self._last_log = self._t0
|
|
71
|
+
self._entered = True
|
|
72
|
+
self._log(final=False)
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
76
|
+
self._log(final=True)
|
|
77
|
+
self._entered = False
|
|
78
|
+
|
|
79
|
+
def __iter__(self) -> Iterator[Any]:
|
|
80
|
+
with self:
|
|
81
|
+
for item in self._iterable or ():
|
|
82
|
+
yield item
|
|
83
|
+
self.update(1)
|
|
84
|
+
|
|
85
|
+
def update(self, n: int = 1) -> None:
|
|
86
|
+
self._n += n
|
|
87
|
+
now = time.monotonic()
|
|
88
|
+
if now - self._last_log >= self._min_interval:
|
|
89
|
+
self._last_log = now
|
|
90
|
+
self._log(final=False)
|
|
91
|
+
|
|
92
|
+
def _log(self, *, final: bool) -> None:
|
|
93
|
+
elapsed = time.monotonic() - self._t0 if self._t0 else 0.0
|
|
94
|
+
if self._total:
|
|
95
|
+
pct = f"{100 * self._n / self._total:.0f}%"
|
|
96
|
+
total_str = str(self._total)
|
|
97
|
+
else:
|
|
98
|
+
pct = "?"
|
|
99
|
+
total_str = "?"
|
|
100
|
+
marker = "done" if final else "progress"
|
|
101
|
+
desc = f" {self._desc}" if self._desc else ""
|
|
102
|
+
line = (
|
|
103
|
+
f"[{marker}]{desc} {self._n}/{total_str}"
|
|
104
|
+
f" ({pct}) elapsed={elapsed:.1f}s"
|
|
105
|
+
)
|
|
106
|
+
print(line, file=self._stream, flush=True)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class TqdmBackend:
|
|
110
|
+
"""Wraps ``tqdm``.
|
|
111
|
+
|
|
112
|
+
Aggregates rather than subclasses so Marimo's function-style monkey-patch
|
|
113
|
+
of ``tqdm_notebook`` (#4016) can't break us.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
iterable: Iterable[Any] | None = None,
|
|
119
|
+
total: int | None = None,
|
|
120
|
+
desc: str = "",
|
|
121
|
+
*,
|
|
122
|
+
notebook: bool = False,
|
|
123
|
+
**kwargs: Any,
|
|
124
|
+
) -> None:
|
|
125
|
+
if notebook:
|
|
126
|
+
from tqdm.notebook import tqdm as _tqdm
|
|
127
|
+
else:
|
|
128
|
+
from tqdm import tqdm as _tqdm
|
|
129
|
+
self._inner = _tqdm(iterable, total=total, desc=desc, **kwargs)
|
|
130
|
+
|
|
131
|
+
def __enter__(self) -> Self:
|
|
132
|
+
self._inner.__enter__()
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Any:
|
|
136
|
+
return self._inner.__exit__(exc_type, exc_val, exc_tb)
|
|
137
|
+
|
|
138
|
+
def __iter__(self) -> Iterator[Any]:
|
|
139
|
+
return iter(self._inner)
|
|
140
|
+
|
|
141
|
+
def update(self, n: int = 1) -> None:
|
|
142
|
+
self._inner.update(n)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class MarimoBackend:
|
|
146
|
+
"""Marimo-native bar via ``marimo.status.progress_bar``."""
|
|
147
|
+
|
|
148
|
+
def __init__(
|
|
149
|
+
self,
|
|
150
|
+
iterable: Iterable[Any] | None = None,
|
|
151
|
+
total: int | None = None,
|
|
152
|
+
desc: str = "",
|
|
153
|
+
**_: Any,
|
|
154
|
+
) -> None:
|
|
155
|
+
import marimo as mo
|
|
156
|
+
|
|
157
|
+
self._inner = mo.status.progress_bar(
|
|
158
|
+
iterable,
|
|
159
|
+
total=total if total is not None else _len_or_none(iterable),
|
|
160
|
+
title=desc or None,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def __enter__(self) -> Self:
|
|
164
|
+
self._inner.__enter__()
|
|
165
|
+
return self
|
|
166
|
+
|
|
167
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Any:
|
|
168
|
+
return self._inner.__exit__(exc_type, exc_val, exc_tb)
|
|
169
|
+
|
|
170
|
+
def __iter__(self) -> Iterator[Any]:
|
|
171
|
+
return iter(self._inner)
|
|
172
|
+
|
|
173
|
+
def update(self, n: int = 1) -> None:
|
|
174
|
+
self._inner.update(n)
|
everbar/_detect.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Runtime environment detection.
|
|
2
|
+
|
|
3
|
+
Heuristic — the caller can override via the ``backend`` argument on ``Progress``
|
|
4
|
+
or the ``EVERBAR_BACKEND`` environment variable.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Literal
|
|
10
|
+
|
|
11
|
+
Environment = Literal[
|
|
12
|
+
"marimo",
|
|
13
|
+
"colab",
|
|
14
|
+
"kaggle",
|
|
15
|
+
"vscode_notebook",
|
|
16
|
+
"jupyter",
|
|
17
|
+
"jupyter_qt",
|
|
18
|
+
"spyder",
|
|
19
|
+
"databricks",
|
|
20
|
+
"pyodide",
|
|
21
|
+
"ipython_terminal",
|
|
22
|
+
"terminal",
|
|
23
|
+
"non_tty",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def detect_environment() -> Environment:
|
|
28
|
+
"""Return a string identifying the runtime environment."""
|
|
29
|
+
# 1. Marimo — official API
|
|
30
|
+
try:
|
|
31
|
+
import marimo # type: ignore
|
|
32
|
+
|
|
33
|
+
if marimo.running_in_notebook():
|
|
34
|
+
return "marimo"
|
|
35
|
+
except Exception:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
# 2. Pyodide / JupyterLite
|
|
39
|
+
if "pyodide" in sys.modules or sys.platform == "emscripten":
|
|
40
|
+
return "pyodide"
|
|
41
|
+
|
|
42
|
+
# 3. Databricks
|
|
43
|
+
if "DATABRICKS_RUNTIME_VERSION" in os.environ:
|
|
44
|
+
return "databricks"
|
|
45
|
+
|
|
46
|
+
# 4. IPython-based environments
|
|
47
|
+
try:
|
|
48
|
+
from IPython import get_ipython # type: ignore
|
|
49
|
+
|
|
50
|
+
ip = get_ipython()
|
|
51
|
+
except Exception:
|
|
52
|
+
ip = None
|
|
53
|
+
|
|
54
|
+
if ip is not None:
|
|
55
|
+
shell = ip.__class__.__name__
|
|
56
|
+
mods = sys.modules
|
|
57
|
+
|
|
58
|
+
if "google.colab" in mods:
|
|
59
|
+
return "colab"
|
|
60
|
+
if "kaggle_secrets" in mods or "KAGGLE_KERNEL_RUN_TYPE" in os.environ:
|
|
61
|
+
return "kaggle"
|
|
62
|
+
if "VSCODE_PID" in os.environ or (
|
|
63
|
+
hasattr(ip, "user_ns") and "__vsc_ipynb_file__" in ip.user_ns
|
|
64
|
+
):
|
|
65
|
+
return "vscode_notebook"
|
|
66
|
+
if "spyder_kernels" in mods or "SPY_TESTING" in os.environ:
|
|
67
|
+
return "spyder"
|
|
68
|
+
if shell == "ZMQInteractiveShell":
|
|
69
|
+
return "jupyter"
|
|
70
|
+
if shell == "TerminalInteractiveShell":
|
|
71
|
+
return "ipython_terminal"
|
|
72
|
+
|
|
73
|
+
# 5. Plain Python
|
|
74
|
+
if sys.stdout is not None and hasattr(sys.stdout, "isatty"):
|
|
75
|
+
try:
|
|
76
|
+
if sys.stdout.isatty():
|
|
77
|
+
return "terminal"
|
|
78
|
+
except Exception:
|
|
79
|
+
pass
|
|
80
|
+
return "non_tty"
|
everbar/_progress.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Public ``Progress`` facade. Picks a backend and delegates."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from collections.abc import Iterable, Iterator
|
|
5
|
+
from typing import Any, Self
|
|
6
|
+
|
|
7
|
+
from everbar._detect import detect_environment
|
|
8
|
+
|
|
9
|
+
_DEFAULT_BACKEND: str | None = None
|
|
10
|
+
|
|
11
|
+
_NOTEBOOK_ENVS = {"jupyter", "colab", "kaggle", "vscode_notebook", "databricks"}
|
|
12
|
+
_TQDM_STD_ENVS = {"terminal", "ipython_terminal", "spyder", "jupyter_qt"}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def set_default_backend(name: str | None) -> None:
|
|
16
|
+
"""Pin the backend globally. Pass ``None`` to restore auto-detection.
|
|
17
|
+
|
|
18
|
+
Valid names: ``"marimo"``, ``"jupyter"``, ``"colab"``, ``"kaggle"``,
|
|
19
|
+
``"vscode_notebook"``, ``"jupyter_qt"``, ``"spyder"``, ``"databricks"``,
|
|
20
|
+
``"ipython_terminal"``, ``"terminal"``, ``"pyodide"``, ``"non_tty"``.
|
|
21
|
+
"""
|
|
22
|
+
global _DEFAULT_BACKEND # noqa: PLW0603 — module-level pin is the API
|
|
23
|
+
_DEFAULT_BACKEND = name
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Progress:
|
|
27
|
+
"""A progress bar that adapts to its environment.
|
|
28
|
+
|
|
29
|
+
Iterator form:
|
|
30
|
+
|
|
31
|
+
for x in Progress(items, desc="Loading"):
|
|
32
|
+
work(x)
|
|
33
|
+
|
|
34
|
+
Context-manager form:
|
|
35
|
+
|
|
36
|
+
with Progress(total=100, desc="Steps") as bar:
|
|
37
|
+
for _ in range(100):
|
|
38
|
+
do_step()
|
|
39
|
+
bar.update(1)
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
iterable: Iterable[Any] | None = None,
|
|
45
|
+
total: int | None = None,
|
|
46
|
+
desc: str = "",
|
|
47
|
+
backend: str | None = None,
|
|
48
|
+
*,
|
|
49
|
+
disable: bool = False,
|
|
50
|
+
**kwargs: Any,
|
|
51
|
+
) -> None:
|
|
52
|
+
self._iterable = iterable
|
|
53
|
+
self._total = total
|
|
54
|
+
self._desc = desc
|
|
55
|
+
self._kwargs = kwargs
|
|
56
|
+
|
|
57
|
+
chosen = (
|
|
58
|
+
backend or os.environ.get("EVERBAR_BACKEND") or _DEFAULT_BACKEND
|
|
59
|
+
)
|
|
60
|
+
self._env: str = chosen or detect_environment()
|
|
61
|
+
self._impl = self._make_impl(disable=disable)
|
|
62
|
+
|
|
63
|
+
def _make_impl(self, *, disable: bool) -> Any:
|
|
64
|
+
from everbar import _backends
|
|
65
|
+
|
|
66
|
+
if disable:
|
|
67
|
+
return _backends.NullBackend(iterable=self._iterable)
|
|
68
|
+
|
|
69
|
+
common = {"total": self._total, "desc": self._desc, **self._kwargs}
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
if self._env == "marimo":
|
|
73
|
+
return _backends.MarimoBackend(self._iterable, **common)
|
|
74
|
+
if self._env in _NOTEBOOK_ENVS:
|
|
75
|
+
return _backends.TqdmBackend(
|
|
76
|
+
self._iterable, notebook=True, **common
|
|
77
|
+
)
|
|
78
|
+
if self._env in _TQDM_STD_ENVS:
|
|
79
|
+
return _backends.TqdmBackend(
|
|
80
|
+
self._iterable, notebook=False, **common
|
|
81
|
+
)
|
|
82
|
+
except ImportError:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
return _backends.FallbackBackend(self._iterable, **common)
|
|
86
|
+
|
|
87
|
+
def __iter__(self) -> Iterator[Any]:
|
|
88
|
+
return iter(self._impl)
|
|
89
|
+
|
|
90
|
+
def __enter__(self) -> Self:
|
|
91
|
+
self._impl.__enter__()
|
|
92
|
+
return self
|
|
93
|
+
|
|
94
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Any:
|
|
95
|
+
return self._impl.__exit__(exc_type, exc_val, exc_tb)
|
|
96
|
+
|
|
97
|
+
def update(self, n: int = 1) -> None:
|
|
98
|
+
self._impl.update(n)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: everbar
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A progress bar that works everywhere — terminal, Jupyter, VS Code, Colab, Marimo.
|
|
5
|
+
Project-URL: Homepage, https://github.com/mluttikh/everbar
|
|
6
|
+
Project-URL: Issues, https://github.com/mluttikh/everbar/issues
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: jupyter,marimo,notebook,progress,progressbar,tqdm
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Classifier: Topic :: Terminals
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Provides-Extra: all
|
|
23
|
+
Requires-Dist: ipywidgets>=8.0; extra == 'all'
|
|
24
|
+
Requires-Dist: marimo>=0.10; extra == 'all'
|
|
25
|
+
Requires-Dist: rich>=13.0; extra == 'all'
|
|
26
|
+
Requires-Dist: tqdm>=4.65; extra == 'all'
|
|
27
|
+
Provides-Extra: marimo
|
|
28
|
+
Requires-Dist: marimo>=0.10; extra == 'marimo'
|
|
29
|
+
Provides-Extra: notebook
|
|
30
|
+
Requires-Dist: ipywidgets>=8.0; extra == 'notebook'
|
|
31
|
+
Requires-Dist: tqdm>=4.65; extra == 'notebook'
|
|
32
|
+
Provides-Extra: rich
|
|
33
|
+
Requires-Dist: rich>=13.0; extra == 'rich'
|
|
34
|
+
Provides-Extra: tqdm
|
|
35
|
+
Requires-Dist: tqdm>=4.65; extra == 'tqdm'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# everbar
|
|
39
|
+
|
|
40
|
+
A progress bar that works **everywhere** — terminal, Jupyter, JupyterLab, VS Code notebooks, Google Colab, Marimo, Pyodide, and CI logs. One API, the right backend per environment.
|
|
41
|
+
|
|
42
|
+
> Status: 0.1.0 — alpha. API may shift.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install everbar # core only; uses text fallback if nothing else is installed
|
|
48
|
+
pip install "everbar[tqdm]" # terminal + Jupyter via tqdm
|
|
49
|
+
pip install "everbar[all]" # everything (tqdm, rich, ipywidgets, marimo)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Use
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from everbar import Progress
|
|
56
|
+
|
|
57
|
+
for x in Progress(items, desc="Loading"):
|
|
58
|
+
work(x)
|
|
59
|
+
|
|
60
|
+
with Progress(total=100, desc="Steps") as bar:
|
|
61
|
+
for _ in range(100):
|
|
62
|
+
do_step()
|
|
63
|
+
bar.update(1)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Overrides
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
Progress(items, backend="terminal") # per-call
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
EVERBAR_BACKEND=terminal python script.py # env var
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
import everbar
|
|
78
|
+
everbar.set_default_backend("terminal") # module-wide
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## How it picks a backend
|
|
82
|
+
|
|
83
|
+
`everbar.detect_environment()` returns one of: `marimo`, `colab`, `kaggle`, `vscode_notebook`, `jupyter`, `jupyter_qt`, `spyder`, `databricks`, `pyodide`, `ipython_terminal`, `terminal`, `non_tty`. Each maps to a backend, with graceful fallback to a log-line text mode when nothing better is available.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
everbar/__init__.py,sha256=ohr1mGbK02QG2Jnfwv2vJVEuGuTf9RGQw8fenN4Q_Iw,255
|
|
2
|
+
everbar/_backends.py,sha256=8fOuAfVtd2qSfMEy3G4dktr1AwgDwT-fIi8SNacw_qc,5025
|
|
3
|
+
everbar/_detect.py,sha256=qHqnDmmD6Y7gxwGmFx9zyWSiPi-K2Pmb9cs4WO_kWwM,2041
|
|
4
|
+
everbar/_progress.py,sha256=H5wsugx7lbZhXGQljAETu01rCApZbWkjFr6nMlQRlBQ,2986
|
|
5
|
+
everbar-0.1.0.dist-info/METADATA,sha256=AUQBbVkDSTUgdIF0hX5tyRM9N-yzvAZ5jk8uQHV4QYs,2795
|
|
6
|
+
everbar-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
7
|
+
everbar-0.1.0.dist-info/RECORD,,
|