Skip to main content

Terminal progress bars via decorators

Project description

⏱ timeo

Terminal progress bars for Python functions — just add a decorator.

PyPI Python License: MIT CI GitHub

✓ 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 .gitignore unless 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.


Built with rich · MIT License

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

timeo-0.5.1.tar.gz (39.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

timeo-0.5.1-py3-none-any.whl (16.7 kB view details)

Uploaded Python 3

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

Hashes for timeo-0.5.1.tar.gz
Algorithm Hash digest
SHA256 f701502847f8d257b3762788bec135b9f6172d7df1b2a4082faaf94f2ab2b796
MD5 7cd4b28f8cc56cefc4961587fe22c30e
BLAKE2b-256 b471c551196111f0fd13a5188f468f1e9f7671bef0c91221f2d7f4424cbc90fd

See more details on using hashes here.

Provenance

The following attestation bundles were made for timeo-0.5.1.tar.gz:

Publisher: publish.yml on wtewalt/timeo

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for timeo-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ac2099d8b821b72f84418826d9f79499eced8562f4bde570f2475e3442f69404
MD5 e4c98e943cda107f553c4c5ac27c1f03
BLAKE2b-256 856f52ad6a1c6ae0d9ce8d7263c78423b498ce6fce1d5f21b2a6f124a2601ebb

See more details on using hashes here.

Provenance

The following attestation bundles were made for timeo-0.5.1-py3-none-any.whl:

Publisher: publish.yml on wtewalt/timeo

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page