Skip to main content

Effect/ZIO-inspired structured async for Python

Project description

EffectPy Architecture

effectpy

Docs

Effect-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 Effect you can compose, transform, retry, race, zip, and run in parallel.
  • Automatic resource safety

    • Layer + Scope ensure resources are acquired and released correctly, in order.
  • Structured errors, not random exceptions

    • Failures are rich Cause trees: fail, die, interrupt, both, then — with annotations and stack traces.
  • Deterministic concurrency

    • Built-ins for race, zip_par, for_each_par, cancellation masks, FiberRef locals.
    • Works with both asyncio and anyio (Trio).
  • Observability baked in

    • instrument("name", tags={...}) logs, records metrics, and traces.
    • Export spans/metrics to OTLP/Prometheus/OpenTelemetry.
  • Composable streaming and pipelines

    • StreamE with error channel.
    • Channels + Pipelines with backpressure.
  • Test-friendly clocks and supervision

    • TestClock for 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
  • 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
  • examples/provide_layer_example.py: Use Effect.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
  • examples/fibers_concurrency.py: Run effects concurrently with Runtime.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
  • 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
  • examples/anyio_runtime_example.py: AnyIO-backed runtime (optional). Skips if anyio not 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
  • examples/exporters_demo.py: Export spans/metrics over OTLP HTTP (requires aiohttp, 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

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 with uv run.
  • make examples-list: lists example targets.
  • make example-basic: runs examples/basic_effects.py.
  • make example-layers: runs examples/layers_resource_safety.py.
  • make example-provide: runs examples/provide_layer_example.py.
  • make example-fibers: runs examples/fibers_concurrency.py.
  • make example-pipelines: runs examples/pipelines_parallel.py.
  • make example-anyio: runs examples/anyio_runtime_example.py (requires anyio).
  • make example-exporters: runs examples/exporters_demo.py (requires aiohttp).
  • make install-anyio: installs AnyIO via uv.
  • make install-aiohttp: installs aiohttp via uv.

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

  1. Set version in pyproject.toml (under [project] version).

  2. Build artifacts (wheel + sdist):

  • uv build
  1. (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 ~/.pypirc or environment variables). uvx runs tools in ephemeral environments.
  • If your uv version supports uv publish, you can use uv publish instead of the twine steps above.

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

effectpy-0.2.0.tar.gz (26.0 kB view details)

Uploaded Source

Built Distribution

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

effectpy-0.2.0-py3-none-any.whl (33.4 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for effectpy-0.2.0.tar.gz
Algorithm Hash digest
SHA256 463ac24af96938ae8f08787a76e51d4d5fe92bddb2f0ebd17e1bf396567f9c0e
MD5 689fdf6e181a9f35ff39ede4aafa133e
BLAKE2b-256 8aa1899876d421932b74276e41a7050452e3b2084e28253069aa03e5b196536d

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for effectpy-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 58be31f07084af0c9befa48a653a46573f7949d12ee5648c4a5e650e02ae950b
MD5 50ef04a3ae876b32eed627de027c8401
BLAKE2b-256 20ff4eec896f6ce667c8a86eeecf97ead1f4dba05fc53c643230817148ee045c

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