Skip to content

Async Functions

saferaise works with both sync and async functions. The watched set is tracked via contextvars, so concurrent async tasks are fully isolated.

Basic Async Usage

import asyncio
import saferaise

saferaise.register("myapp")

import myapp

async def main():
    with saferaise.enable():
        await myapp.run()

asyncio.run(main())
# myapp/client.py
from saferaise import raises

@raises(ConnectionError, TimeoutError)
async def fetch(url: str) -> bytes:
    """Fetch data from a URL."""
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.read()

Concurrent Tasks

Each async task inherits its own copy of the context, so concurrent tasks don't interfere with each other:

# myapp/service.py
from saferaise import raises

@raises(ConnectionError)
async def fetch_user(user_id: int) -> dict:
    ...

@raises(ValueError)
async def parse_config(path: str) -> dict:
    ...

async def main():
    try:
        # These run concurrently - each task has its own watched set
        user, config = await asyncio.gather(
            fetch_user(42),
            parse_config("config.yaml"),
        )
    except ConnectionError:
        print("Failed to fetch user")
    except ValueError:
        print("Invalid config")

Threading

For threads, enable() must be called within each thread. A parent thread's watched set is not inherited:

import threading
import saferaise

saferaise.register("myapp")
import myapp

def worker():
    with saferaise.enable():  # each thread needs its own enable()
        try:
            myapp.do_work()
        except myapp.WorkError:
            print("Work failed")

threads = [threading.Thread(target=worker) for _ in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()