Terminal progress bars via decorators
Project description
⏱ timeo
Terminal progress bars for Python functions — just add a decorator.
✓ process_files ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 12.4s
download_data ━━━━━━━━━━━━━━━━━━━━━━━ 72% 0:00:04
compress_output ━━━━━━ 20% 0:00:31
Why timeo?
Most progress bar libraries ask you to wrap your loops manually and manage the display yourself. timeo gets out of the way — decorate a function, iterate normally, done.
# before
for item in items:
process(item)
# after
@timeo.track
def run(items):
for item in timeo.iter(items):
process(item)
Installation
pip install timeo
Usage
Basic progress bar
@timeo.track wraps any function with a live progress bar. The total is inferred automatically from the first argument with a len(). Use timeo.iter() to advance the bar on each iteration — no manual bookkeeping needed.
import timeo
@timeo.track
def process_files(files):
for f in timeo.iter(files):
do_work(f)
process_files(my_files)
Prefer manual control? Use timeo.advance() instead:
@timeo.track
def process_files(files):
for f in files:
do_work(f)
timeo.advance()
Multiple concurrent functions
Every decorated function gets its own bar. They render together in a single live display. Finished bars collapse to a compact summary line so the output stays clean.
@timeo.track
def process_files(files):
for f in timeo.iter(files):
do_work(f)
@timeo.track
def download_data(urls):
for url in timeo.iter(urls):
fetch(url)
process_files(my_files)
download_data(my_urls)
✓ process_files ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 12.4s
download_data ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 72% 0:00:04
Concurrent execution (threads) is fully supported — each bar tracks its own function independently.
Learn mode
Add learn=True and timeo will remember how long your function takes across runs, building an EMA (exponential moving average) of its runtime. Instead of counting steps, the bar fills over the expected duration.
@timeo.track(learn=True)
def run_pipeline(data):
heavy_computation(data)
more_work(data)
| Run | Behaviour |
|---|---|
| First | Indeterminate spinner with run_pipeline (learning...) label |
| Subsequent | Determinate bar filling over the expected duration |
| After code change | Cache invalidates automatically — learning restarts |
The cache key is a hash of the function's bytecode — not its name — so renaming a function preserves its history, and changing its implementation resets it.
Estimate accuracy
timeo uses several strategies to keep timing estimates accurate over time:
Decaying alpha — Early runs use a higher smoothing factor so the estimate converges quickly from a cold start (alpha = max(0.2, 1 / run_count)), giving a true running average for the first few runs before settling at the steady-state weight.
Drift detection — If the last 3 actual runtimes collectively deviate from the stored estimate by more than 25%, the entry resets automatically and learning restarts. This handles cases where a function you call internally changed — even if your decorated function's own code didn't.
Explicit dependencies — For finer control, declare which helper functions affect your runtime with depends_on. If any listed function's bytecode changes, the cache key changes and learning restarts:
@timeo.track(learn=True, depends_on=[helper_fn, another_fn])
def run_pipeline(data):
helper_fn(data)
another_fn(data)
By default timing data is stored in your platform's user cache directory (e.g. ~/Library/Caches/timeo/timings.json on macOS). Use cache="project" to store it in .timeo/timings.json relative to the current directory instead — useful for per-project isolation or sharing timings with a team via version control:
@timeo.track(learn=True, cache="project")
def run_pipeline(data):
...
Note: If using
cache="project", add.timeo/to your.gitignoreunless you intentionally want to commit timing data.
cache= |
Location | Best for |
|---|---|---|
"user" (default) |
~/.cache/timeo/timings.json |
Personal scripts, cross-project reuse |
"project" |
.timeo/timings.json |
Per-project isolation, shared team timings |
Explicit display control
The live display starts and stops automatically in most cases. For complex scripts with branching logic or long-running setup, use timeo.live() to pin the display lifetime explicitly:
with timeo.live():
process_files(my_files)
download_data(my_urls)
# display stays open until the with-block exits
CLI
timeo ships a command-line tool for inspecting and managing the learn-mode cache.
View cache contents
timeo cache info # user cache (default)
timeo cache info --cache project # project cache
Output:
Cache location: /Users/you/Library/Caches/timeo/timings.json
Entries: 2
╭─────────────────────┬──────────────┬──────┬─────────────────────╮
│ Function │ EMA Duration │ Runs │ Last Updated │
├─────────────────────┼──────────────┼──────┼─────────────────────┤
│ module.run_pipeline │ 12.40s │ 7 │ 2026-04-10 09:21:00 │
│ module.process_data │ 3.20s │ 3 │ 2026-04-09 14:05:12 │
╰─────────────────────┴──────────────┴──────┴─────────────────────╯
Reset the cache
timeo cache reset # delete entire user cache (default)
timeo cache reset --cache project # delete entire project cache
You will be prompted to confirm. Pass --yes to skip the prompt:
timeo cache reset --yes
To remove only entries last updated before a specific date, use --before:
timeo cache reset --before 2025-01-01 # remove entries older than Jan 1 2025
timeo cache reset --before 2025-01-01 --yes # skip confirmation
This leaves newer entries intact — useful for pruning stale data without losing recent timing history.
How it works
| Piece | Role |
|---|---|
@timeo.track |
Wraps the function, infers total from Sized args, manages the task lifecycle |
ProgressManager |
Singleton owning the rich live display; reference-counted so it tears down only when all tasks finish |
TrackedTask |
Dataclass holding per-function progress state |
timeo.iter() |
Thin generator wrapper that calls advance() on each item |
ContextVar |
Ensures timeo.advance() always updates the right bar, even across threads |
The display is built on rich.progress and rich.live.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file timeo-0.5.1.tar.gz.
File metadata
- Download URL: timeo-0.5.1.tar.gz
- Upload date:
- Size: 39.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f701502847f8d257b3762788bec135b9f6172d7df1b2a4082faaf94f2ab2b796
|
|
| MD5 |
7cd4b28f8cc56cefc4961587fe22c30e
|
|
| BLAKE2b-256 |
b471c551196111f0fd13a5188f468f1e9f7671bef0c91221f2d7f4424cbc90fd
|
Provenance
The following attestation bundles were made for timeo-0.5.1.tar.gz:
Publisher:
publish.yml on wtewalt/timeo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
timeo-0.5.1.tar.gz -
Subject digest:
f701502847f8d257b3762788bec135b9f6172d7df1b2a4082faaf94f2ab2b796 - Sigstore transparency entry: 1272076722
- Sigstore integration time:
-
Permalink:
wtewalt/timeo@5bde88fa3be26a26ca031a7863dc61c17e4d94d1 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/wtewalt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5bde88fa3be26a26ca031a7863dc61c17e4d94d1 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file timeo-0.5.1-py3-none-any.whl.
File metadata
- Download URL: timeo-0.5.1-py3-none-any.whl
- Upload date:
- Size: 16.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ac2099d8b821b72f84418826d9f79499eced8562f4bde570f2475e3442f69404
|
|
| MD5 |
e4c98e943cda107f553c4c5ac27c1f03
|
|
| BLAKE2b-256 |
856f52ad6a1c6ae0d9ce8d7263c78423b498ce6fce1d5f21b2a6f124a2601ebb
|
Provenance
The following attestation bundles were made for timeo-0.5.1-py3-none-any.whl:
Publisher:
publish.yml on wtewalt/timeo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
timeo-0.5.1-py3-none-any.whl -
Subject digest:
ac2099d8b821b72f84418826d9f79499eced8562f4bde570f2475e3442f69404 - Sigstore transparency entry: 1272076841
- Sigstore integration time:
-
Permalink:
wtewalt/timeo@5bde88fa3be26a26ca031a7863dc61c17e4d94d1 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/wtewalt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5bde88fa3be26a26ca031a7863dc61c17e4d94d1 -
Trigger Event:
workflow_dispatch
-
Statement type: