Skip to main content

pygolem mascot

pygolem

Spawn tiny little servants, running things in parallel.

PyPI version Python versions License Tests PRs Welcome

Before

import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed

def fetch_all(fn, items, workers=10, max_retries=3, rate_limit=5):
    values = [None] * len(items)
    errors = {}
    remaining = list(enumerate(items))
    timestamps = []
    lock = threading.Lock()

    def rate_limited(item):
        while True:
            with lock:
                now = time.monotonic()
                timestamps[:] = [t for t in timestamps if now - t < 1]
                if len(timestamps) < rate_limit:
                    timestamps.append(now)
                    break
            time.sleep(0.05)
        return fn(item)

    for attempt in range(max_retries + 1):
        with ThreadPoolExecutor(max_workers=workers) as executor:
            futures = {executor.submit(rate_limited, item): i for i, item in remaining}
            remaining = []
            for future in as_completed(futures):
                index = futures[future]
                try:
                    values[index] = future.result()
                except Exception as e:
                    remaining.append((index, items[index]))
                    errors[index] = e
        if not remaining or attempt == max_retries:
            break
        time.sleep(2 ** attempt)

    return values, errors

results, errors = fetch_all(fetch_url, urls)

Roughly 40 lines, and this version doesn't even validate its own inputs, guard against the bool-is-int trap, or cap thread count safely at scale.

After

from pygolem import threads

results = threads.map_parallel(fetch_url, urls, workers=10, retries=3, rate_limit=("token", 5))

results.values   # successful outputs, in original input order
results.errors   # {index: {"type": ..., "message": ...}} for items that failed permanently

Four lines. Same results. Ordered output, isolated per-item failures, retries with backoff, and a global rate limit.


What it is

pygolem wraps concurrent.futures.ThreadPoolExecutor with the things a real parallel workload eventually needs:

  1. Ordered results: .values always matches your input order, even though threads finish in whatever order they finish in.
  2. Per-item error isolation: one failing item never crashes the batch.
  3. Retries with backoff, plus retry_if to skip retrying failures you already know are permanent.
  4. Global rate limiting: cap total throughput across the entire pool, not per-worker, with a choice of token bucket, leaky bucket, or sliding window.

Dependency-free, just the standard library.


Installation

pip install pygolem

Quick example

from pygolem import threads
import requests

def fetch(url):
    return requests.get(url, timeout=5).status_code

urls = [
    "https://example.com",
    "https://httpbin.org/status/500",   # will fail, then retry
    "https://github.com",
]

result = threads.map_parallel(
    fetch, urls,
    workers=3,
    retries=2,
    backoff=("exponential", 0.5),
    rate_limit=("token", 5),
)

print(result.values)   # [200, 500, 200]
print(result.errors)   # {1: {"type": "ConnectionError", "message": "..."}}

API

threads.map_parallel(fn, items, workers=None, retries=None, retry_if=None, backoff=("linear", 1), rate_limit=None)
Parameter Type Default Description
fn callable — Function to call on each item.
items list / tuple — Inputs to process. Must be non-empty.
workers int min(32, len(items)) Max concurrent threads.
retries int 0 Retry attempts per item after a failure.
retry_if callable None callable(exception) -> bool — return False to stop retrying that item.
backoff (mode, delay) ("linear", 1) mode is "linear" or "exponential".
rate_limit (algorithm, value) None algorithm is "token", "leaky", or "sliding"; value is calls/sec across all workers.

Returns a Data object: .values (results in input order, None for permanent failures) and .errors ({index: {"type": str, "message": str}}).


Known limitations

  • A stuck fn hangs the whole call. There's no timeout yet, and Python can't force-kill a thread, so one item that never returns means map_parallel never returns either.
  • Retries can repeat side effects. If fn does something real (like a request) and then fails afterward, retrying does that thing again. Use retry_if to skip retrying failures you know already went through.
  • One slow retry delays everyone. The backoff wait happens between rounds, so already-successful items still sit and wait for it.
  • More workers won't speed up CPU-heavy code. Threads share one GIL, extra workers mainly help waiting on network/disk, not crunching numbers.

Benchmarks

I/O-bound workload (time.sleep(0.01) per call), measured with time.perf_counter(). Full breakdown in tests/02_speed.

n sequential pygolem speedup
100 1.014s 0.064s 15.8x
1000 10.260s 0.525s 19.5x

Overhead over a raw ThreadPoolExecutor stays under 1% at scale. The retry/error/ordering machinery is close to free.

The workers default is capped at 32 for a reason: an earlier version that defaulted to workers=len(items) crashed at 100,000 items (RuntimeError: can't start new thread). The capped default handles the same load in 33s without crashing.


Tested

125 pytest tests, 0 failures — input validation, ordering, error isolation, retries, all three rate-limit algorithms, and a dedicated suite that proves out the known limitations. Full breakdown in tests/TEST_REPORTS.md.

pytest tests/ -v --tb=short

Roadmap

  • Token-bucket and leaky-bucket rate limiting (alongside sliding-window)
  • Conditional retries via retry_if
  • pygolem.aio: asyncio engine, with item-level backoff
  • pygolem.processes: multiprocessing engine, with real cancellation of hung workers
  • Per-item timeout support
  • Optional progress callback

Contributing

Contributions are welcome — see CONTRIBUTING.md for setup instructions, PR guidelines, and how to report bugs.


About the name

Inspired by Minecraft's copper golem. A small automaton that potters around doing small repetitive tasks on its own, tirelessly, until it needs a little maintenance. Not a bad description of a background worker.


License

Apache License 2.0. see LICENSE.

Metadata

Release files for pygolem 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pygolem 0.1.1
File Size Uploaded
pygolem-0.1.1.tar.gz 22.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pygolem 0.1.1
File Interpreter ABI Platform
pygolem-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 35.8 kB

Release files / pygolem-0.1.1.tar.gz

Download URL pygolem-0.1.1.tar.gz
Size 22.8 kB
Tags Source
SHA-256 checksum
How to use checksums
4e907231659bbeedd960792f2db1e0c4ef5eea9ddaf85e4076b6005d25094073
BLAKE2b-256 checksum
How to use checksums
8a38002bd28889d4bb42fe272b2e76ed60836e9b83e71e435c9308b67f362c44
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release files / pygolem-0.1.1-py3-none-any.whl

Download URL pygolem-0.1.1-py3-none-any.whl
Size 12.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ef0ec35ccf0b10303381e85d8789c6e6b0c0fb404e0616c5e70f7506523ae27d
BLAKE2b-256 checksum
How to use checksums
f9bbe24a95da275a90cab84bbbc3c981e120cdae08ff7253a05b7dc46a379bdb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page