Skip to main content

A small execution layer over ThreadPoolExecutor: bounded memory, progress, retries, timeouts, cancellation.

Project description

tanda

CI PyPI Python versions License: MIT

A small execution layer over concurrent.futures.ThreadPoolExecutor for running many I/O-bound jobs at once — with bounded memory, progress, retries, timeouts, cancellation, and a Ctrl+C that actually works.

Tanda is Spanish for a batch, a round of work.

from tanda import Pool

with Pool() as pool:
    results = pool.map(files, process)

Why

ThreadPoolExecutor gives you submit() and map(), and stops there. Everything a real batch job needs on top — a progress line, retrying transient failures, per-task timeouts, cancelling cleanly on Ctrl+C, not materializing a 10-million-item generator into a list of futures — gets reimplemented in every script, slightly differently and slightly wrong each time.

tanda is not a prettier wrapper around submit(). It resolves the full lifecycle of a concurrent batch, consistently:

submission → concurrency → progress → retries → timeout
          → cancellation → errors → shutdown

The basic case needs zero configuration. Everything else is opt-in:

from tanda import Pool, RetryPolicy

with Pool(workers=16) as pool:
    results = pool.map(
        files,
        process,
        progress=True,
        retry=RetryPolicy(max_attempts=3, retry_on=(OSError,)),
        task_timeout=30,
    )

What you get

  • Ordered results by default. pool.map(["a", "b", "c"], fn) returns results in input order, regardless of completion order. imap() streams them in input order as they become ready; imap_unordered() yields in completion order when input order doesn't matter.
  • Bounded submission. Input iterables are consumed lazily; only a limited window of tasks (roughly workers × 4) is in flight at any time. A 100-million-item generator runs in constant memory instead of creating 100 million Future objects up front.
  • Progress that counts items, not attempts. A task that fails twice and succeeds on the third try advances progress by 1. Unsized iterables still get a count, rate, and elapsed time — the iterable is never materialized just to obtain a len().
  • Explicit retries. RetryPolicy(max_attempts=3, retry_on=(OSError,)) — attempt count instead of an ambiguous retries=, retry only for the exception types you list, with fixed or exponential backoff.
  • Honest timeouts. task_timeout marks a task as timed out and applies your retry/error policy, but Python cannot kill a running thread — the underlying call may keep executing. tanda documents this instead of pretending otherwise (see When not to use tanda).
  • Cancellation and Ctrl+C. Pending tasks are cancelled, running tasks are asked to stop, the executor shuts down, and KeyboardInterrupt is re-raised — no hung process, no wall of stack traces.
  • A shutdown you can bound. Leaving the with block cancels queued work and waits for what is still running. Pool(shutdown_timeout=30) caps that wait and raises ShutdownTimeout naming the stuck workers, instead of hanging on a task that never returns.
  • Errors that fail loudly. By default the first definitive failure raises a TaskError carrying the item, its index, the underlying exception, the attempt count, and elapsed time. error_policy="collect" instead returns a BatchResult with successes and failures separated — never a mixed list of results and exceptions.

When not to use tanda

  • CPU-bound pure-Python work. Threads share the GIL; a thread pool will not speed up numeric loops, compression, or image transforms written in Python. Use processes — unless your library releases the GIL (NumPy, most I/O libraries).
  • Hard timeouts. If a task must be forcibly killed at the deadline, threads are the wrong tool; use subprocesses.
  • Task orchestration. Scheduling, DAGs, priorities, rate limiting, distributed execution, and persistent queues are explicit non-goals — that's Celery's job, not tanda's.

Installation

pip install tanda           # no dependencies
pip install "tanda[tqdm]"   # adds TqdmProgress

Python 3.10+. The package ships type information (py.typed), so your type checker sees the annotated API without stubs.

Design

The design decisions — API surface, retry and timeout semantics, backpressure, the task state machine — are written down before the code:

Performance

On I/O-bound work — what tanda is for — the coordination overhead disappears into the waiting: 1000 tasks of 5–20 ms land within 1% of raw ThreadPoolExecutor.map. On 50,000 no-op tasks, where there is nothing to wait for, tanda is 45% slower; that is the price of the lifecycle, and it is published rather than hidden. Over a 1M-item input, submit-all peaks at 1.7 GB of futures against tanda's 0.1 MB.

Full numbers, methodology, and the frozen overhead budget: BENCHMARKS.md.

Status

The batch lifecycle described above is implemented and tested: bounded submission, map(), imap(), and imap_unordered(), retries, task_timeout and overall_timeout, cooperative cancellation, error policies, progress reporting, and graceful shutdown. The suite is adversarial rather than happy-path — 10k-item batches, the timeout-versus-completion and error-versus-cancellation races, backpressure, reentrancy — and runs on 3.10–3.13 across Linux and Windows. Measured overhead and the frozen budget are in BENCHMARKS.md.

It is 0.x: the documented surface is what tanda commits to, and a change to it comes with a minor bump and a CHANGELOG entry, not a silent redefinition. A job-handle API and opt-in stats are next — see the issues.

License

MIT

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

tanda-0.2.0.tar.gz (63.9 kB view details)

Uploaded Source

Built Distribution

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

tanda-0.2.0-py3-none-any.whl (29.5 kB view details)

Uploaded Python 3

File details

Details for the file tanda-0.2.0.tar.gz.

File metadata

  • Download URL: tanda-0.2.0.tar.gz
  • Upload date:
  • Size: 63.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.3

File hashes

Hashes for tanda-0.2.0.tar.gz
Algorithm Hash digest
SHA256 dc5120b01ac27880bdf98efd6f9f7e74e87cbcf870a55cd67a541ca56cf843ec
MD5 514bf12352ceaca74e67a2cf6c2da75e
BLAKE2b-256 b2ad1053fcd472284415854f2f17bc8a5385f9e8d6b43710c50cbf4898482283

See more details on using hashes here.

File details

Details for the file tanda-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: tanda-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 29.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.3

File hashes

Hashes for tanda-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e3736f89b31f3457f30ccdad1328333b635fd16aeec5cfd887ee9704c11111f7
MD5 f293cc4ebbdab3d398770954a56bd118
BLAKE2b-256 1c9a39779f566e7ec2bac05ea0f48e415ea06ae5f0fc910782fefa0dc5f495df

See more details on using hashes here.

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