Skip to content

Litestar example

A minimal Litestar app that schedules an add_numbers task per request and exposes a second route to poll for the result, using the default PickleSerializer.

Run it

cd examples/litestar_app
uv run --with litestar,uvicorn litestar run

Then, in another terminal:

curl -X POST localhost:8000/add -H 'Content-Type: application/json' -d '{"a": 2, "b": 3}'
# {"task_id": 1}

curl localhost:8000/result/1
# {"status": "done", "result": 5}

The worker that executes the task runs in a background thread started from Litestar's on_startup hook, purely to keep this example to a single command. See the plain_python example for the more common pattern of running alchemical_worker as a separate process.

app.py

"""Litestar example: schedule a task from one route, poll for its result
from another.

For this mini example the worker runs in a background thread started on
Litestar's `on_startup` hook, so `litestar run` alone is enough to see it
work end to end. In a real deployment you would instead run
`alchemical_worker` as its own process (see the plain_python example) so it
can be scaled independently of the web app.
"""

import os
import threading
from datetime import timedelta
from typing import Union

from litestar import Litestar, get, post
from litestar.params import FromPath
from pydantic import BaseModel
from sqlalchemy import create_engine

from alchemical_queues import AlchemicalQueues
from alchemical_queues.tasks import TaskInfo, Worker, task


class AddRequest(BaseModel):
    a: int
    b: int


@task
def add_numbers(taskinfo: TaskInfo, a: int, b: int) -> int:
    return a + b


def create_app(database_url: Union[str, None] = None) -> Litestar:
    database_url = database_url or os.environ.get(
        "DATABASE_URL", "sqlite:///litestar_example.db"
    )
    engine = create_engine(database_url)
    queues = AlchemicalQueues(engine)
    queues.create_all()
    task_queue = queues.get_task_queue("add-queue")

    def start_worker() -> None:
        worker = Worker(task_queue, poll_every=timedelta(milliseconds=100))
        threading.Thread(target=worker.work, daemon=True).start()

    @post("/add")
    async def schedule_add(data: AddRequest) -> dict:
        entry = add_numbers(data.a, data.b).schedule(task_queue)
        return {"task_id": entry.entry_id}

    @get("/result/{task_id:int}")
    async def get_result(task_id: FromPath[int]) -> dict:
        queued = add_numbers.retrieve(task_queue, task_id)
        if not queued.done:
            return {"status": "pending"}
        return {"status": "done", "result": queued.result}

    return Litestar(
        route_handlers=[schedule_add, get_result], on_startup=[start_worker]
    )


app = create_app()