Skip to main content

parlane

PyPI Python Tests License: MIT

Dead-simple parallel data processing for Python.

parlane gives you parallel map, filter, and for-each in one line. On GIL-free Python (3.13t+), it uses threads automatically. On standard Python, it falls back to processes. You don't need to think about it.

from parlane import pmap

results = pmap(process, items)  # That's it.

Why parlane?

parlane joblib concurrent.futures
Lines of code 1 1 (but 3 concepts) 3-4
GIL-aware Auto No No
Async support Built-in No Manual
Progress bar Built-in No Manual
Pipeline API Built-in No No
Dependencies Zero (core) numpy, etc. stdlib
Type hints Full (py.typed) Partial Partial
__main__ guard Not needed Not needed Required (macOS/Win)

Install

pip install parlane

# With progress bar support
pip install parlane[progress]

Requires Python 3.10+. Zero core dependencies.

Quick Start

from parlane import pmap, pfilter, pfor

# Parallel map
results = pmap(lambda x: x ** 2, range(1000))

# Parallel filter
evens = pfilter(lambda x: x % 2 == 0, range(1000))

# Parallel for-each (side effects)
pfor(save_to_db, records)

# With options
results = pmap(fetch, urls, workers=16, backend="thread", timeout=30.0)

Progress Bar

Add real-time progress display with a single parameter. Requires tqdm (pip install parlane[progress]).

from parlane import pmap, pfilter

# Enable with description
results = pmap(process, images, backend="thread", progress="Processing")
# Processing: 100%|██████████| 500/500 [00:03<00:00, 160.2it/s]

# Enable without description
results = pmap(process, images, progress=True)

# Works with all sync functions
pfilter(is_valid, records, progress="Validating")

No progress overhead when progress=False (default) — the fast executor.map() path is preserved.

Async API

Native async support for I/O-bound workloads. Uses asyncio.Semaphore for concurrency control — no executor needed.

import asyncio
from parlane import apmap, apfilter, apfor

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.text()

# Async parallel map
pages = await apmap(fetch, urls, workers=20)

# Async parallel filter
async def is_alive(url):
    ...  # return True/False

alive = await apfilter(is_alive, urls, workers=10)

# Async for-each
await apfor(send_notification, users, workers=5)

# With progress
pages = await apmap(fetch, urls, workers=20, progress="Fetching")

Pipeline API

Chain operations fluently with lazy evaluation. Nothing executes until a terminal method is called.

from parlane import pipeline

# Lazy chain — executes on .collect()
results = (
    pipeline(raw_data)
    .map(parse)
    .filter(is_valid)
    .map(transform)
    .collect()
)

# With progress
results = (
    pipeline(images)
    .progress("ETL")
    .map(resize)
    .filter(has_face)
    .map(classify)
    .collect()
)

# Flat map + batch
words = pipeline(documents).flat_map(tokenize).batch(100).map(embed).collect()

# Terminal methods
pipeline(items).map(fn).count()          # -> int
pipeline(items).map(fn).first()          # -> T | None
pipeline(items).map(fn).reduce(sum)      # -> R

# Configuration
pipeline(items).workers(8).backend("thread").on_error("skip").map(fn).collect()

Pipelines are immutable — each method returns a new pipeline, so the original can be reused:

base = pipeline(data).map(normalize)
train = base.filter(is_train).collect()
test  = base.filter(is_test).collect()

Benchmarks

Measured on Apple M-series (8 cores), Python 3.12:

I/O-bound: 100 tasks x 50ms sleep

Method Time Speedup
Sequential for loop 5.35s 1.0x
parlane pmap 0.48s 11.2x
concurrent.futures 0.49s 11.0x

CPU-bound: 200 tasks x heavy math

Method Time Speedup
Sequential for loop 1.26s 1.0x
parlane pmap 0.28s 4.5x
concurrent.futures 0.29s 4.4x

Zero overhead. parlane uses concurrent.futures under the hood with smart defaults that match or beat manual configuration.

Run benchmarks yourself:

python benchmarks/bench_vs_stdlib.py

API Reference

Sync Functions

pmap(fn, items, **options) -> list

Apply fn to each item in parallel. Returns results in order.

results = pmap(process_image, images)

pfilter(fn, items, **options) -> list

Keep items where fn returns True. Parallel evaluation.

valid = pfilter(is_valid, records)

pfor(fn, items, **options) -> None

Apply fn to each item for side effects.

pfor(send_notification, users)

pstarmap(fn, items, **options) -> list

Like pmap, but unpacks each item as arguments.

results = pstarmap(pow, [(2, 10), (3, 5), (10, 3)])
# [1024, 243, 1000]

Async Functions

await apmap(fn, items, **options) -> list

Async parallel map with semaphore-based concurrency control.

await apfilter(fn, items, **options) -> list

Async parallel filter.

await apfor(fn, items, **options) -> None

Async parallel for-each.

Options

Sync options

Parameter Type Default Description
workers int auto Thread: cpu+4, Process: cpu (capped at item count)
backend str "auto" "auto", "thread", or "process"
timeout float None Per-task timeout in seconds
chunksize int None Chunk size (process backend)
on_error str "raise" "raise", "skip", or "collect"
progress bool | str False True, False, or description string

Async options

Parameter Type Default Description
workers int auto Max concurrent tasks (capped at 32)
on_error str "raise" "raise", "skip", or "collect"
progress bool | str False True, False, or description string

Error Handling

# Default: raise on first error
pmap(risky_fn, items)  # raises immediately

# Skip errors silently
results = pmap(risky_fn, items, on_error="skip")

# Collect all results (Ok/Err)
results = pmap(risky_fn, items, on_error="collect")
for r in results:
    if r.is_ok():
        print(r.unwrap())
    else:
        print(f"Error: {r.exception}")

Error handling works the same way in async functions:

results = await apmap(risky_fn, items, on_error="collect")

GIL Detection

from parlane import is_gil_disabled, recommended_backend

print(is_gil_disabled())      # True on 3.13t+, False otherwise
print(recommended_backend())  # "thread" or "process"

How It Works

  1. Detect GIL state at import time (cached)
  2. Choose backend automatically:
    • GIL disabled -> ThreadPoolExecutor (true parallelism, no serialization overhead)
    • GIL enabled -> ProcessPoolExecutor (bypass GIL via multiprocessing)
  3. Pick optimal worker count: threads get cpu+4, processes get cpu (never more than items)
  4. Execute with the chosen backend
  5. Return results in input order

Users can override with backend="thread" or backend="process".

For async functions, asyncio.Semaphore controls concurrency directly — no executor needed.

Development

git clone https://github.com/owl-tech-sui/parlane
cd parlane
pip install -e ".[dev]"

# Run tests
pytest -v

# Lint
ruff check src/ tests/
ruff format --check src/ tests/

# Type check
mypy src/parlane/ --strict

# Benchmarks
python benchmarks/bench_vs_stdlib.py

License

MIT

Metadata

Release files for parlane 0.3.0

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

Source distribution (sdist)

Source distribution for parlane 0.3.0
File Size Uploaded
parlane-0.3.0.tar.gz 23.6 kB Details

Built distribution (wheel)

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

Total release size: 41.9 kB

Release files / parlane-0.3.0.tar.gz

Download URL parlane-0.3.0.tar.gz
Size 23.6 kB
Tags Source
SHA-256 checksum
How to use checksums
cd74ec67cd3f44c65b40d9cddb91665243c2e96fb3a99bd01e056120fec6d2e1
BLAKE2b-256 checksum
How to use checksums
c23de772f238a6a3bf0abb278b30e577b4ae66f37ea83a95d3e0b00aed63092a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Feb 7, 2026.

Transparency log

Release files / parlane-0.3.0-py3-none-any.whl

Download URL parlane-0.3.0-py3-none-any.whl
Size 18.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
90597c2fa371e0cbfd2a16793535716068b55d9ab96c1454211ed1fc48cdc418
BLAKE2b-256 checksum
How to use checksums
aa18b85690af89a80bdacdc1c7ac32c774112e61dbd27fd3e7b321d20b7e1b63
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Feb 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 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