Skip to main content

lauren

lauren framework: a metadata-first Python web framework. NestJS-style modules, Axum-style extractors, FastAPI-class ergonomics — resolved into an immutable execution graph at startup.

Test Lint CodeQL Coverage Package version Supported Python versions Downloads License Ruff Checked with mypy prek Discussions GitHub Stars


Documentation: https://lauren-py.dev

Docs MCP server (for AI coding agents): https://lauren-py.dev/api/mcp — setup guide

Source Code: https://github.com/lauren-framework/lauren-framework


For AI Agents & Coding Assistants

Install all skills in one command

# Claude Code, Cursor, Copilot, Continue, Codex CLI — auto-detected
npx skills add lauren-framework/lauren-framework

This copies all 60+ SKILL.md context packs into your agent's global skills directory (~/.claude/skills/, ~/.cursor/skills/, etc.). The next time your agent opens a Lauren project it has pre-loaded expertise on auth, database, API patterns, observability, security, and more.

Resource What it contains
lauren/llms.txt Framework overview — start here
lauren/llms-full.txt Complete reference — all APIs, patterns, common errors
AGENTS.md Agent rules, by-task lookup, file ownership, common errors, definition of done
CLAUDE.md Conventions, commands, golden rules, pattern selection guide
skills/ 60+ copy-paste skill guides covering every common task

Connect your agent to the live docs (MCP)

The docs site hosts a read-only MCP server so agents can search the current documentation instead of guessing from training data:

claude mcp add --transport http lauren-docs https://lauren-py.dev/api/mcp

Configurations for Cursor, VS Code, Windsurf, Gemini CLI and stdio-only clients: lauren-py.dev/mcp.

Full install guide and skills index: docs/guides/agent-skills.md


lauren is a modern, high-performance Python web framework for building APIs that need to fail at startup, not in production. It is built on these core ideas:

  • Metadata-first. Routes, dependency-injection bindings, module boundaries, lifecycle hooks, middleware, and guards are declared with decorators that attach metadata. They never rewrite your functions.
  • Startup-validated, runtime-pure. Every misuse — circular DI, missing module export, malformed extractor, conflicting routes — is rejected inside LaurenFactory.create(...), not on the first request.
  • No reflection on the request path. The whole graph is compiled into immutable structures at startup; serving a request is pure traversal.
  • AI-ready by default. Public surface is mirrored in llms.txt / llms-full.txt and exported in __all__ — a CI hook keeps the two in lock-step.

Features

  • Fast — Zero per-request reflection: routes, DI graph, extractors, and middleware are fully compiled at startup.
  • Implicit extractors — Path params, query strings, and JSON bodies are auto-detected from type annotations. Write id: int, q: str, body: MyModel without boilerplate unless you need the explicit form. Query[T] and Json[T] support Pydantic models, msgspec.Struct, Python dataclass, and TypedDict types.
  • Pydantic-free discriminated unions — Discriminated[Cat | Dog, "kind"] routes tagged-union JSON bodies to the correct variant class using only stdlib. Works with @dataclass, TypedDict, msgspec.Struct, and pydantic.BaseModel. OpenAPI emits oneOf + discriminator.mapping automatically.
  • Three-scope DI — SINGLETON, REQUEST, and TRANSIENT scopes with Protocol bindings, multi-bindings (list[T]), and NestJS-style custom providers (use_value / use_class / use_factory / use_existing).
  • Pipes — Post-extraction value transforms: validate, coerce, or enrich parameters before they reach the handler. Function-based, class-based, chainable, and DI-aware.
  • Guards, Middleware & Interceptors — All three attachment points. Guards run first (allow/deny); middleware wraps raw request/response bytes; interceptors wrap handler execution for timing, caching, and response transforms. @use_guards and @use_interceptors work on both HTTP @controller and WebSocket @ws_controller classes natively.
  • @propagate_metadata — Copy @use_guards, @use_interceptors, @use_middlewares, @use_encoder, and @set_metadata annotations from a source class or function to a target — the functools.wraps equivalent for Lauren's decorator metadata.
  • lauren.reflect — Full metadata introspection API: static readers (reflect_controller, reflect_routes, reflect_guards, …), app-level readers (get_all_routes, get_all_ws_gateways, get_route_metadata), and rich result types (ReflectedRoute, ReflectedController, …). All readers honour the own-class rule — no metadata inheritance.
  • WebSockets — First-class @ws_controller gateways with typed validated frames, discriminated-union dispatch (Discriminated[A | B, "key"]), BroadcastGroup rooms, and native guard/interceptor enforcement via WsConnectionContext.
  • Server-Sent Events — EventStream with keep-alive heartbeats and Last-Event-ID resumability for AI token-streaming patterns.
  • Typed streaming — StreamingResponse[T] auto-negotiates between SSE, NDJSON, and JSON Lines based on the client's Accept header.
  • Custom responses & file delivery — Return your own Response subclasses, stream downloads with await Response.file(...), or emit XML with Response.xml(...) while keeping the dispatch pipeline untouched.
  • Background tasks — BackgroundTasks extractor fires work after the response is sent. TaskHandle exposes status, done, cancel() (before the task starts) and await result() (from another coroutine). Signals notify on start, complete, and failure.
  • Sessions — first-class signed-cookie sessions via LaurenFactory.create(..., sessions=SessionConfig(...)). A mutable session: Session is injected into handlers at zero per-request cost; the pluggable async SessionStore ships with an in-memory backend and a stateless SignedCookieSessionStore. HMAC-signed, HttpOnly/Secure/ SameSite=Lax by default, with regenerate_id() and invalidate(). Unsafe configuration is rejected at startup. Pair with content=ContentConfig(strict_content_type=True) so cross-site text/plain posts to JSON routes are rejected (415).
  • Static files — StaticFilesModule.for_root("/assets", directory="./public") with ETag caching, Cache-Control, path-traversal protection, and dotfiles (.env, .git/) hidden by default (/.well-known/ still served).
  • Socket.IO — Engine.IO v4 / Socket.IO v5 adapter via @socketio_controller.
  • OpenAPI 3.1 — Automatic schema generation from Pydantic models, dataclasses, TypedDict, and Discriminated unions. Swagger UI and ReDoc served out of the box.
  • Lifecycle signals — SignalBus with StartupBegin, StartupComplete, ShutdownBegin, RequestReceived, RequestComplete, and more.
  • Standards-based — Built on ASGI and anyio. Pydantic is optional (pip install "lauren[pydantic]").

Requirements

Python 3.11, 3.12, 3.13, and 3.14 are supported. Hard dependencies:

  • anyio — async backend and thread-pool offload for sync handlers.

Optional extras:

  • pip install "lauren[pydantic]" — adds pydantic>=2.0 for Pydantic-backed validation and PydanticEncoder.
  • pip install "lauren[msgspec]" — adds msgspec>=0.18 for struct-based serialisation.
  • pip install "lauren[full]" — installs both.

Installation

pip install lauren
# with an ASGI server:
pip install "uvicorn[standard]"
# or granian (faster on CPython):
pip install granian

Quick start

from pydantic import BaseModel
from lauren import LaurenFactory, controller, get, post, module, use_guards
from lauren.types import ExecutionContext


class CreateUser(BaseModel):
    name: str
    age: int


class AdminGuard:
    async def can_activate(self, ctx: ExecutionContext) -> bool:
        return ctx.request.headers.get("x-role") == "admin"


@controller("/users", tags=["users"])
class UserController:
    @get("/{id}")
    async def get_user(self, id: int) -> dict:
        return {"id": id, "found": True}

    @post("/")
    async def create(self, body: CreateUser):
        return body.model_dump(), 201

    @get("/admin")
    @use_guards(AdminGuard)
    async def admin_only(self) -> dict:
        return {"access": "granted"}


@module(controllers=[UserController])
class AppModule:
    pass


app = LaurenFactory.create(AppModule, docs_url="/docs")
uvicorn main:app --reload
# → http://127.0.0.1:8000/docs  (Swagger UI)

Dependency injection

from lauren import injectable, post_construct, pre_destruct, Scope

@injectable(scope=Scope.SINGLETON)
class UserRepository:
    @post_construct
    async def warm(self) -> None:
        self.cache: dict[int, str] = {}

    @pre_destruct
    async def flush(self) -> None:
        self.cache.clear()

    async def find(self, user_id: int) -> str | None:
        return self.cache.get(user_id)

    async def upsert(self, user_id: int, name: str) -> None:
        self.cache[user_id] = name


@controller("/users")
class UserController:
    def __init__(self, repo: UserRepository) -> None:
        self.repo = repo

    @get("/{id}")
    async def get_user(self, id: int) -> dict:
        name = await self.repo.find(id)
        if name is None:
            return {"id": id, "found": False}, 404
        return {"id": id, "name": name}


@module(providers=[UserRepository], controllers=[UserController])
class AppModule:
    pass

Modules

Modules are the unit of feature composition. Each module declares what it provides, what it exports, and what it imports from other modules — similar to NestJS:

@module(
    imports=[DatabaseModule, AuthModule],
    controllers=[UserController, ProfileController],
    providers=[UserService, EmailService],
    exports=[UserService],
)
class UsersModule:
    pass

Circular dependency detection, missing export errors, and scope violations are all caught at startup — before your first request.

SSE / streaming

from lauren import EventStream, ServerSentEvent, get

@get("/events")
async def stream(self) -> EventStream:
    async def generate():
        for i in range(10):
            yield ServerSentEvent(data=f"tick {i}", event="tick")
            await asyncio.sleep(1)
        yield ServerSentEvent(data="done", event="close")
    return EventStream(generate(), keep_alive=15.0)

For typed, content-negotiated streams (SSE / NDJSON / JSON Lines):

from pydantic import BaseModel
from lauren import StreamingResponse

class Tick(BaseModel):
    seq: int

@get("/ticks")
async def ticks(self) -> StreamingResponse[Tick]:
    async def gen():
        for i in range(100):
            yield Tick(seq=i)
            await asyncio.sleep(0.05)
    return StreamingResponse(gen())

Static files

from lauren.static_files import StaticFilesModule

@module(imports=[StaticFilesModule.for_root("/assets", directory="./public")])
class AppModule:
    pass

Or mount any ASGI sub-app:

app = LaurenFactory.create(AppModule)
app.mount("/static", StaticFiles(directory="static"))

Sessions

from lauren import LaurenFactory, SessionConfig, Session, controller, get, post, module

@controller("/account")
class AccountController:
    @get("/visits")
    async def visits(self, session: Session) -> dict:
        session["visits"] = session.get("visits", 0) + 1
        return {"visits": session["visits"]}

    @post("/login")
    async def login(self, session: Session) -> dict:
        session.regenerate_id()        # session-fixation defence
        session["user_id"] = "u-42"
        return {"ok": True}

    @post("/logout")
    async def logout(self, session: Session) -> dict:
        session.invalidate()
        return {"ok": True}

@module(controllers=[AccountController])
class AppModule:
    pass

app = LaurenFactory.create(
    AppModule,
    sessions=SessionConfig(secret="a-long-random-secret"),
)

The cookie is HMAC-signed and secure by default (HttpOnly, Secure, SameSite=Lax). Swap InMemorySessionStore for the stateless SignedCookieSessionStore or a Redis-backed SessionStore in production. Unsafe configuration fails inside LaurenFactory.create, not at runtime.

Performance

Runtime is pure traversal of pre-compiled structures — no inspect.signature(...), no get_type_hints(...), no isinstance(...) walking on the hot path. The DI graph, route table, extractor bindings, and middleware pipeline are all resolved once at startup. Each request pays only the cost of dispatching through the already-compiled graph.

Benchmarks

Cross-framework numbers are produced by a separate harness: benches/bench.py drives every participant through the same workloads, one fresh process per measurement, and writes benches/results/frameworks-inprocess.json. The figures below are drawn straight from that committed artifact — median throughput per workload, then the p50/p95/p99 round trip per participant — with error bars spanning the min–max range across repeats, so the spread sits next to the median instead of behind it. The latency error bars are capped at 2.7 ms so one 10.5 ms range cannot flatten the other workloads: that single range stops at the cap and its true maximum is written above a break marker. The client calls each application through httpx.ASGITransport: no socket, no server process, no TLS. Read them as the cost of the framework layer itself, not as a deployed-configuration number. The full tables, the method and the pinned dependency set are on the benchmark page.

Median throughput by workload, one bar per participant, with error bars spanning the min–max range across the repeats p50, p95 and p99 round trip per participant, one panel per workload, with error bars spanning the min–max range across the repeats; colour marks the participant, and the one range past the 2.7 ms cap is cut off with its true maximum written above the break

Optional dependencies

Package Purpose
uvicorn / hypercorn / granian ASGI server (none bundled — pick one)
pydantic / msgspec Model validation and OpenAPI for those model kinds (lauren[pydantic], lauren[msgspec], lauren[full])
orjson Faster JSON via OrjsonEncoder — opt in with json_encoder=OrjsonEncoder() or json_encoder=auto_encoder()
msgspec Alternative fast JSON encoder via MsgspecEncoder
httpx Only if you prefer httpx.ASGITransport in tests — lauren.testing.TestClient needs nothing extra

Form bodies and multipart uploads are parsed in-tree; no python-multipart needed.

Companion packages

Package Purpose
lauren-middlewares CORS, rate-limit, GZip, security headers, request-id, trusted hosts, HTTPS redirect, body-size limit, timeout
lauren-logging Structured logging module with processor pipeline, contextvars binding, pluggable backends (stdlib, structlog, file, fan-out)
lauren-guards Auth guards: JWT bearer, API key, basic auth, OAuth2 introspection, session cookie, RBAC/ABAC, CSRF, IP allowlist

Deployment

lauren is a standard ASGI application — deploy exactly like FastAPI or Starlette:

# Uvicorn
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4

# Granian (Rust-based, faster on CPython)
granian --interface asgi main:app --host 0.0.0.0 --port 8000

# Hypercorn (HTTP/2 + HTTP/3)
hypercorn main:app --bind 0.0.0.0:8000

Contributing

We welcome contributions of every size, from typo fixes to whole subsystems. Read first:

  1. CONTRIBUTING.md — setup, branch & commit conventions, and the quality bar.
  2. AGENTS.md — the design invariants every PR must respect, whether the author is human or an AI agent.
uv tool install prek      # one-time
prek install              # wires up the git hook
nox                       # lint + tests + typecheck

License

MIT — see LICENSE.

Metadata

Release files for lauren 1.10.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lauren 1.10.0
File Size Uploaded Provenance Actions
lauren-1.10.0.tar.gz 2.6 MB With provenance Details for lauren-1.10.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for lauren 1.10.0
File Interpreter ABI Platform Actions
lauren-1.10.0-py3-none-any.whl Python 3 none any Details for lauren-1.10.0-py3-none-any.whl

Total release size: 2.9 MB

Release files / lauren-1.10.0.tar.gz

Download URL lauren-1.10.0.tar.gz
Size 2.6 MB
Tags Source
SHA-256 checksum
How to use checksums
a3587209ec52c5e59c919b4440325ba95d09029376464431dac892b82ce6fca6
BLAKE2b-256 checksum
How to use checksums
c80b6ea0f370b8ab740cacf234eee6d96b5a72a70a1ad9e7ad9e3ac7449f12f5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Release files / lauren-1.10.0-py3-none-any.whl

Download URL lauren-1.10.0-py3-none-any.whl
Size 336.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2d34ebcc1054f3a0ef4137fa84220edc1a67b260f86da43c232fcb5cc2dc5ece
BLAKE2b-256 checksum
How to use checksums
4e6f7fee4836c0067d4b4a216f600b8cdd474021ac17906effb0d2de014eb6bc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

Security for lauren 1.10.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for lauren-1.10.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page