Metadata-Version: 2.4
Name: parawave
Version: 0.1.0
Summary: One decorator turns any function into a durable parallel runner.
License-Expression: MIT
License-File: LICENSE
Keywords: api,async,batch,concurrency,decorator,durable-execution,llm,local-temporal,openai,parallel,parallel-execution,rate-limiting,resume,retry,synthetic-data
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.9
Provides-Extra: all
Requires-Dist: aiofiles>=23.0; extra == 'all'
Requires-Dist: aiosqlite>=0.19; extra == 'all'
Requires-Dist: nest-asyncio>=1.5; extra == 'all'
Provides-Extra: dev
Requires-Dist: aiofiles>=23.0; extra == 'dev'
Requires-Dist: aiosqlite>=0.19; extra == 'dev'
Requires-Dist: hatch>=1.15; extra == 'dev'
Requires-Dist: nest-asyncio>=1.5; extra == 'dev'
Requires-Dist: papermill>=2.6; extra == 'dev'
Requires-Dist: pytest-asyncio<1.0,>=0.23; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: tox-uv>=1.0; extra == 'dev'
Requires-Dist: tox>=4.0; extra == 'dev'
Requires-Dist: tqdm>=4.60; extra == 'dev'
Requires-Dist: twine>=6.0; extra == 'dev'
Provides-Extra: examples
Requires-Dist: aiofiles>=23.0; extra == 'examples'
Requires-Dist: aiosqlite>=0.19; extra == 'examples'
Requires-Dist: datasets>=3.0; extra == 'examples'
Requires-Dist: ipykernel>=6.0; extra == 'examples'
Requires-Dist: jinja2>=3.0; extra == 'examples'
Requires-Dist: nest-asyncio>=1.5; extra == 'examples'
Requires-Dist: openai>=1.0; extra == 'examples'
Requires-Dist: papermill>=2.6; extra == 'examples'
Provides-Extra: notebook
Requires-Dist: nest-asyncio>=1.5; extra == 'notebook'
Provides-Extra: notebook-test
Requires-Dist: aiofiles>=23.0; extra == 'notebook-test'
Requires-Dist: aiosqlite>=0.19; extra == 'notebook-test'
Requires-Dist: hatch>=1.15; extra == 'notebook-test'
Requires-Dist: ipykernel>=6.0; extra == 'notebook-test'
Requires-Dist: nbval>=0.11; extra == 'notebook-test'
Requires-Dist: nest-asyncio>=1.5; extra == 'notebook-test'
Requires-Dist: papermill>=2.6; extra == 'notebook-test'
Requires-Dist: pytest-asyncio<1.0,>=0.23; extra == 'notebook-test'
Requires-Dist: pytest-cov>=4.0; extra == 'notebook-test'
Requires-Dist: pytest>=8.0; extra == 'notebook-test'
Requires-Dist: tox-uv>=1.0; extra == 'notebook-test'
Requires-Dist: tox>=4.0; extra == 'notebook-test'
Requires-Dist: tqdm>=4.60; extra == 'notebook-test'
Requires-Dist: twine>=6.0; extra == 'notebook-test'
Provides-Extra: sqlite
Requires-Dist: aiofiles>=23.0; extra == 'sqlite'
Requires-Dist: aiosqlite>=0.19; extra == 'sqlite'
Description-Content-Type: text/markdown

# ParaWave: One decorator turns any function into a durable parallel runner.

[Python 3.9+](https://python.org)
[License: MIT](LICENSE)
[Tests]()

Parawave (short for **parallel wave**) turns any Python function — sync or async —  
into a parallel, resumable runner with retry, rate limiting, and persistence.  
Built for managing thousands of LLM API calls. Zero dependencies.

## Install

```bash
pip install parawave
```

The base install has **zero dependencies** — just parawave and the Python standard library.


| Extra      | Packages                | What it enables                          | Install                          |
| ---------- | ----------------------- | ---------------------------------------- | -------------------------------- |
| `sqlite`   | `aiosqlite`, `aiofiles` | Persistent storage, cross-session resume | `pip install parawave[sqlite]`   |
| `notebook` | `nest_asyncio`          | Jupyter / Colab support                  | `pip install parawave[notebook]` |
| `all`      | All of the above        | Everything                               | `pip install parawave[all]`      |


## Quick Start

```python
import parawave

@parawave(
    max_concurrency=20,
    rate_limit=50,
    retry=parawave.RetryPolicy(max_retries=3, backoff="exponential"),
)
async def enrich(city: str) -> dict:
    response = await openai_client.chat.completions.create(
        model="gpt-5.4-nano",
        messages=[{"role": "user", "content": f"One fun fact about {city}"}],
    )
    return {"fact": response.choices[0].message.content}

result = enrich.run(data=[{"city": c} for c in cities])
```

```
[parawave] Starting run-e914e6b685e6 | 20 items | concurrency=20
[parawave] 5/20 (5 completed) | 0.8s | 6.3 items/s
[parawave] 12/20 (10 completed, 2 failed, 3 retried) | 1.4s | 8.6 items/s
[parawave] 18/20 (14 completed, 4 failed, 6 retried) | 1.9s | 9.5 items/s
[parawave] 20/20 (15 completed, 5 failed, 10 retried) | 2.1s | 9.6 items/s
[parawave] Completed run-e914e6b685e6 | 15/20 completed | 30 attempts, 10 retried, 5 failed | 2.1s
[parawave] To resume: .resume() | Cross-session: .resume("run-e914e6b685e6")
```

Some items failed — check what went wrong:

```python
for item in result.failed:
    print(f"{item.input['city']}: {item.error}")
```

```
Tokyo: RateLimitError: rate limit exceeded
Berlin: RateLimitError: rate limit exceeded
Seoul: APITimeoutError: request timed out
Mumbai: RateLimitError: rate limit exceeded
Cairo: APIConnectionError: connection reset
```

Resume to retry only the 5 failed items:

```python
result = enrich.resume()
```

```
[parawave] Resuming run-e914e6b685e6 | 15/20 previously completed | concurrency=20
[parawave] 3/5 (3 completed) | 0.1s | 30.0 items/s
[parawave] 5/5 (5 completed, 1 retried) | 0.2s | 25.0 items/s
[parawave] Completed run-e914e6b685e6 | 20/20 completed | 2.3s
```

Works with sync functions too:

```python
@parawave(max_concurrency=10)
def fetch(url: str) -> str:
    return requests.get(url).text
```

With `storage="sqlite"`, resume works across sessions — kill the process,
restart later, pick up where you left off.

## Examples


| Notebook | Description |
| -------- | ----------- |
| [01 — Quickstart](examples/01_quickstart.ipynb) | Core patterns — run, retry, resume, hooks, RunManager |
| [02 — Synthetic Data Pipeline](examples/02_synthetic_data_pipeline.ipynb) | Rate and classify HuggingFace data with an LLM |
| [03 — Advanced Synthetic Pipeline](examples/03_advanced_synthetic_pipeline.ipynb) | SharedState + Jinja templates for diverse synthetic data |


[Open in Colab](https://colab.research.google.com/github/parawaveio/parawave/blob/main/examples/01_quickstart.ipynb)

## How it works

It's dead simple — three steps:

1. **Decorate** any function with `@parawave()`
2. **Run** with `.run(data=[...])` — items fan out in parallel with bounded concurrency
3. **Resume** with `.resume()` — retries only what failed

That's it. No daemon. No worker process. No message broker. Everything runs in your Python process.

## More

Parawave also supports lifecycle hooks, shared state, warmup mode, result
export (JSON, CSV), run tagging, and run history via RunManager. See the
[quickstart notebook](examples/01_quickstart.ipynb) for the full tour.

## License

MIT