Effect/ZIO-inspired structured async for Python
Project description
effectpy
Effect/ZIO-inspired structured async for Python.
Why effectpy?
asyncio is powerful but messy: exceptions leak, cancellations are tricky, resources are forgotten, and observability is bolted on later.
effectpy brings the semantics of Effect TS / ZIO into Python, giving you:
-
Structured async without spaghetti
- Every async operation is an
Effectyou can compose, transform, retry, race, zip, and run in parallel.
- Every async operation is an
-
Automatic resource safety
Layer+Scopeensure resources are acquired and released correctly, in order.
-
Structured errors, not random exceptions
- Failures are rich
Causetrees: fail, die, interrupt, both, then — with annotations and stack traces.
- Failures are rich
-
Deterministic concurrency
- Built-ins for
race,zip_par,for_each_par, cancellation masks,FiberReflocals. - Works with both
asyncioandanyio(Trio).
- Built-ins for
-
Observability baked in
instrument("name", tags={...})logs, records metrics, and traces.- Export spans/metrics to OTLP/Prometheus/OpenTelemetry.
-
Composable streaming and pipelines
StreamEwith error channel.- Channels + Pipelines with backpressure.
-
Test-friendly clocks and supervision
TestClockfor deterministic time in tests.- Supervisors restart effects with retry schedules.
Example: Scoped DB pipeline
import asyncio
from effectpy import *
class DB:
async def query(self, x: int) -> int:
await asyncio.sleep(0.01)
return x * 2
DBLayer = from_resource(DB, lambda _: DB(), lambda _: asyncio.sleep(0))
async def main():
base = Context()
scope = Scope()
env = await (LoggerLayer | MetricsLayer | TracerLayer | DBLayer).build_scoped(base, scope)
db = env.get(DB)
src, out = Channel[int](2), Channel[int](2)
async def producer():
for i in range(5):
await src.send(i)
async def stage1(x: int) -> int: return (x + 1)
async def stage2(x: int) -> int: return await db.query(x)
pipe = Pipeline[int,int](src) \ .via(stage(stage1, workers=2)) \ .via(stage(stage2, workers=2)) \ .to_channel(out)
async def consumer():
for _ in range(5):
print("OUT:", await out.receive())
run = instrument("pipeline.run", pipe, tags={"component":"db","env":"dev"})
await asyncio.gather(producer(), run._run(env), consumer())
await scope.close()
asyncio.run(main())
Output:
OUT: 2
OUT: 4
OUT: 6
OUT: 8
OUT: 10
effectpy = asyncio with guardrails and batteries:
- Guardrails: structured errors, cancellation, resource safety
- Batteries: observability, retries, pipelines, test clocks, supervision
More Examples
All examples are runnable directly:
examples/basic_effects.py: Effect composition, error handling, and instrumentation.- Python (module):
python -m examples.basic_effects - uv:
uv run python -m examples.basic_effects - Alt:
PYTHONPATH=. uv run python examples/basic_effects.py
- Python (module):
examples/layers_resource_safety.py: Layers + Scope with a fake DB resource, automatic teardown.- Python (module):
python -m examples.layers_resource_safety - uv:
uv run python -m examples.layers_resource_safety - Alt:
PYTHONPATH=. uv run python examples/layers_resource_safety.py
- Python (module):
examples/provide_layer_example.py: UseEffect.provide(...)to scope a layer to a single effect.- Python (module):
python -m examples.provide_layer_example - uv:
uv run python -m examples.provide_layer_example - Alt:
PYTHONPATH=. uv run python examples/provide_layer_example.py
- Python (module):
examples/fibers_concurrency.py: Run effects concurrently withRuntime.fork, observe exits, interrupt fibers.- Python (module):
python -m examples.fibers_concurrency - uv:
uv run python -m examples.fibers_concurrency - Alt:
PYTHONPATH=. uv run python examples/fibers_concurrency.py
- Python (module):
examples/pipelines_parallel.py: Channels + Pipelines with parallel stages and observability.- Python (module):
python -m examples.pipelines_parallel - uv:
uv run python -m examples.pipelines_parallel - Alt:
PYTHONPATH=. uv run python examples/pipelines_parallel.py
- Python (module):
examples/anyio_runtime_example.py: AnyIO-backed runtime (optional). Skips ifanyionot installed.- Python (module):
python -m examples.anyio_runtime_example - uv:
uv run python -m examples.anyio_runtime_example - Alt:
PYTHONPATH=. uv run python examples/anyio_runtime_example.py
- Python (module):
examples/exporters_demo.py: Export spans/metrics over OTLP HTTP (requiresaiohttp, otherwise no-ops).- Python (module):
python -m examples.exporters_demo - uv:
uv run python -m examples.exporters_demo - Alt:
PYTHONPATH=. uv run python examples/exporters_demo.py
- Python (module):
Running Tests
- Python:
python -m unittest discover -s tests -p 'test_*.py' -v - uv:
uv run python -m unittest discover -s tests -p 'test_*.py' -v
Optional deps for specific examples:
- AnyIO example: install
anyio(e.g.,uv pip install anyio) or use a venv. - Exporters demo: install
aiohttp(e.g.,uv pip install aiohttp).
Makefile Shortcuts (uv-based)
make test: runs the unit test suite withuv run.make examples-list: lists example targets.make example-basic: runsexamples/basic_effects.py.make example-layers: runsexamples/layers_resource_safety.py.make example-provide: runsexamples/provide_layer_example.py.make example-fibers: runsexamples/fibers_concurrency.py.make example-pipelines: runsexamples/pipelines_parallel.py.make example-anyio: runsexamples/anyio_runtime_example.py(requiresanyio).make example-exporters: runsexamples/exporters_demo.py(requiresaiohttp).make install-anyio: installs AnyIO viauv.make install-aiohttp: installs aiohttp viauv.
Running Examples Without Installing
You don’t need to install the package to run examples when working in this repo. Use module form so Python resolves the local effectpy package on sys.path:
- Python:
python -m examples.basic_effects - uv:
uv run python -m examples.basic_effects
For scripts by file path, add the repo root to PYTHONPATH:
PYTHONPATH=. uv run python examples/basic_effects.py
Or install in editable mode for a longer dev session:
uv pip install -e .
Publishing to PyPI
-
Set version in
pyproject.toml(under[project] version). -
Build artifacts (wheel + sdist):
uv build
- (Optional) Verify the distribution:
uvx twine check dist/*
4a) Upload to TestPyPI first:
uvx twine upload --repository testpypi dist/*
4b) Upload to PyPI:
uvx twine upload dist/*
Notes:
- You’ll need PyPI credentials configured (via
~/.pypircor environment variables).uvxruns tools in ephemeral environments. - If your
uvversion supportsuv publish, you can useuv publishinstead of the twine steps above.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file effectpy-0.1.0.tar.gz.
File metadata
- Download URL: effectpy-0.1.0.tar.gz
- Upload date:
- Size: 12.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6d2535bbd6b2a0de51ff276b11a72442343300dba737097cf35ae73a8ed4027
|
|
| MD5 |
578721aa581200d7e50ee366f2f6b739
|
|
| BLAKE2b-256 |
45e4a5da8fa910a9df4eec7ffd736b755454abf5224d7657a635f63a3115e44b
|
File details
Details for the file effectpy-0.1.0-py3-none-any.whl.
File metadata
- Download URL: effectpy-0.1.0-py3-none-any.whl
- Upload date:
- Size: 14.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
38bec6c618ca155a746e0b12c8b1d99d582145455d802ed11fa295ce13e4fcac
|
|
| MD5 |
a1547896d6ce799d822fae433e7fb0ab
|
|
| BLAKE2b-256 |
c7e99191401eddb01b162ccf5b4c90887d428ce56cf5a4b1df316a6cf9d6aa00
|