App & Factory
The top-level entry points for creating and running a Lauren application.
LaurenFactory
class LaurenFactoryProduces •LaurenApp instances via the 7-phase pipeline.
LaurenFactory.create
def create(root_module: type, strict_lifecycle: bool = True, global_middlewares: Iterable[type] | None = None, global_guards: Iterable[type] | None = None, global_interceptors: Iterable[type] | None = None, global_exception_handlers: Iterable[Any] | None = None, global_providers: Iterable[Any] | None = None, max_body_size: int = 1048576, app_state: AppState | None = None, logger: Logger | None = None, openapi_info: dict[str, Any] | None = None, openapi_servers: list[dict[str, Any]] | None = None, openapi_security_schemes: dict[str, Any] | None = None, openapi_url: str | None = None, docs_url: str | None = None, redoc_url: str | None = None, arena: RequestArena | None = None, arena_capacity: int | None = None, json_encoder: JSONEncoder | None = None, signals: SignalBus | None = None, error_format: str = 'default', root_path: str = '', mounts: dict[str, Any] | None = None) -> LaurenAppBuild a •LaurenApp from a root @module class.
The seven-phase pipeline is logged through logger (defaults to a
no-op •~lauren.logging.NullLogger when not provided, so
existing test suites remain silent). Pass
logger=lauren.logging.default_logger() for a production-ready
configuration that auto-detects TTY vs JSON output.
Passing any of openapi_url, docs_url, or redoc_url
exposes the corresponding documentation endpoint. The common
pattern is openapi_url='/openapi.json', docs_url='/docs',
redoc_url='/redoc' — matching FastAPI conventions.
global_guards and global_exception_handlers mirror NestJS's
app-level guards and exception handlers: every request runs through
them after the route is resolved (guards) or whenever the handler
raises (handlers). Per-route @use_guards and
@use_exception_handlers declarations compose with these globals —
route handlers are tried first, then globals.
Lauren
class Lauren(title: str = 'lauren application', version: str = '1.0.0', description: str | None = None, openapi_url: str | None = '/openapi.json', docs_url: str | None = '/docs', redoc_url: str | None = '/redoc', servers: list[dict[str, Any]] | None = None, security_schemes: dict[str, Any] | None = None, debug: bool = False, max_body_size: int = 1048576, strict_lifecycle: bool = True, app_state: AppState | None = None, logger: Any | None = None, global_middlewares: list[type] | None = None, global_guards: list[type] | None = None, global_interceptors: list[type] | None = None, global_exception_handlers: list[Any] | None = None, global_providers: list[Any] | None = None)A FastAPI-inspired ASGI application over lauren's module pipeline.
All constructor arguments have defaults; the canonical call is simply
Lauren(). Routes are added with the verb decorators
(get / post / put / delete / patch / head /
options), modules are merged with •include_module, and
middleware is appended with •add_middleware.
Lauren.get
def get(self, path: str = '/', kw: Any = {}) -> Callable[[Callable[..., Any]], Callable[..., Any]]Register a GET route. Mirrors FastAPI's @app.get.
Lauren.post
def post(self, path: str = '/', kw: Any = {}) -> Callable[[Callable[..., Any]], Callable[..., Any]]Lauren.put
def put(self, path: str = '/', kw: Any = {}) -> Callable[[Callable[..., Any]], Callable[..., Any]]Lauren.patch
def patch(self, path: str = '/', kw: Any = {}) -> Callable[[Callable[..., Any]], Callable[..., Any]]Lauren.delete
def delete(self, path: str = '/', kw: Any = {}) -> Callable[[Callable[..., Any]], Callable[..., Any]]Lauren.head
def head(self, path: str = '/', kw: Any = {}) -> Callable[[Callable[..., Any]], Callable[..., Any]]Lauren.options
def options(self, path: str = '/', kw: Any = {}) -> Callable[[Callable[..., Any]], Callable[..., Any]]Lauren.include_module
def include_module(self, module_cls: type) -> NoneMerge a NestJS-style @module class into this application.
Every provider declared inside the included module (and anything it
re-exports) becomes visible to the synthetic app-level module. Call
as many times as needed before •startup.
Lauren.include_router
def include_router(self, other: 'Lauren', prefix: str = '') -> NoneMerge another •Lauren instance's routes and modules.
Every buffered route on other is copied into self with
prefix prepended, and other's included modules are pulled
in too. Neither instance compiles until •startup runs on
self. other must itself be uncompiled.
Lauren.add_middleware
def add_middleware(self, cls: type) -> NoneRegister a middleware class globally.
The class must be decorated with @middleware() (defining a
dispatch(request, call_next) coroutine). Middleware runs in
insertion order around every request.
Lauren.add_guard
def add_guard(self, cls: type) -> NoneRegister a guard class globally.
The class must define can_activate(context). Global guards
run before any per-route @use_guards chain on every request.
Equivalent to passing global_guards=[...] to
•LaurenFactory.create.
Lauren.add_interceptor
def add_interceptor(self, cls: type) -> NoneRegister an interceptor class globally.
The class must be decorated with @interceptor. Global interceptors
run after guards and before route handlers on every request. Equivalent
to passing global_interceptors=[...] to •LaurenFactory.create.
Lauren.add_exception_handler
def add_exception_handler(self, handler: Any) -> NoneRegister an exception handler globally.
handler must already be decorated with @exception_handler.
It runs whenever a request handler raises an exception that
matches the handler's declared exception tuple, after any
per-route or controller-level handlers have been tried.
Lauren.add_provider
def add_provider(self, provider: Any) -> NoneRegister a provider globally (visible to every module).
Accepts the same shapes as the module providers= list: an
@injectable class, a function provider, or the result of
use_value / use_class / use_factory / use_existing.
Must be called before the application compiles.
Lauren.on_startup
def on_startup(self, fn: Callable[[], Any]) -> Callable[[], Any]Register a callable to run during application startup.
Accepts both sync and async callables. Hooks run after the DI
graph is built and after all @post_construct hooks have fired,
but before any request is dispatched.
Lauren.on_shutdown
def on_shutdown(self, fn: Callable[[], Any]) -> Callable[[], Any]Register a callable to run during graceful shutdown.
Executes after in-flight requests drain and before
@pre_destruct hooks on singleton providers.
Lauren.startup
def startup(self) -> AnyCompile the application and run all startup hooks.
Safe to call multiple times — subsequent invocations are
no-ops that return the already-compiled •LaurenApp.
Returns the underlying •LaurenApp so tests can introspect it.
Lauren.shutdown
def shutdown(self, drain_timeout: float = 10.0) -> NoneGracefully stop the underlying •LaurenApp.
A no-op if the app never compiled (nothing to shut down).
Lauren.openapi
def openapi(self) -> dict[str, Any]Return the OpenAPI 3.1 document.
Requires the app to have been compiled (either via •startup
or an ASGI request). Raises •LifecycleViolationError if
called before compilation.
Lauren.routes
def routes(self) -> list[Any]Return the compiled route list.
Before compilation this reflects buffered routes as a list of
(method, path, handler) tuples; afterwards it returns the
underlying •LaurenApp's •RouteEntry objects for
parity with the classic API.
LaurenApp
class LaurenApp(router: Router, container: DIContainer, module_graph: ModuleGraph, lifecycle: LifecycleScheduler, compiled_handlers: dict[tuple[str, str], CompiledHandler], global_middlewares: list[type], app_state: AppState, strict_lifecycle: bool = True, max_body_size: int = 1048576, logger: Logger | None = None, ws_router: Router | None = None, ws_gateways: dict[str, Any] | None = None, arena: RequestArena | None = None, signals: SignalBus | None = None, error_format: str = 'default', global_guards: list[type] | None = None, global_exception_handlers: list[Any] | None = None, global_interceptors: list[type] | None = None, global_providers: list[Any] | None = None)A compiled, ready-to-serve ASGI application.
Instances of this class are produced exclusively by •LaurenFactory.create.
LaurenApp.routes
def routes(self) -> list[RouteEntry]LaurenApp.openapi
def openapi(self) -> dict[str, Any]LaurenApp.on_shutdown
def on_shutdown(self, callback: Callable[[], Any]) -> Callable[[], Any]Register a callback to run during •shutdown.
Complements @pre_destruct by letting callers attach arbitrary
cleanup coroutines (or plain callables) without needing to express
them as DI-scoped providers. Usable as a decorator::
@app.on_shutdown
async def flush_buffers() -> None:
...Callbacks run in reverse registration order (LIFO) after in-flight
requests have drained and before @pre_destruct hooks, so
they can use the DI graph if needed.
LaurenApp.mount
def mount(self, path: str, app: Any) -> NoneMount an ASGI sub-application at path.
All requests whose path starts with path (or equals it exactly) are
forwarded to app after stripping the prefix. The stripped portion
is appended to scope["root_path"] so the sub-application can
reconstruct absolute URLs correctly.
Mounts are checked in descending prefix-length order so a more-specific
prefix (/api/v2) always wins over a shorter one (/api).
Example::
app = LaurenFactory.create(AppModule)
app.mount("/legacy", legacy_asgi_app)
app.mount("/files", static_files_app)The same result can be achieved at build time via
LaurenFactory.create(..., mounts={"/legacy": legacy_asgi_app}).
LaurenApp.startup
def startup(self) -> NoneLaurenApp.shutdown
def shutdown(self, drain_timeout: float = 10.0) -> NoneGracefully stop the application.
Steps (each logged as an event):
- Mark the app not-running — no new request scheduling.
- Drain in-flight requests (up to
drain_timeoutseconds). - Invoke user-registered
on_shutdowncallbacks in reverse order. - Run
@pre_destructhooks in reverse topological order.
Idempotent: concurrent or repeated calls return as soon as the first shutdown has completed.
LaurenApp.handle
def handle(self, request: Request) -> ResponseDispatch a •Request through middleware, guards and handler.
Global middlewares run before routing so they can intercept every request — including OPTIONS preflight — regardless of whether a matching route exists. Per-route and controller middlewares run after routing (they need access to the compiled handler).
Every dispatch acquires a •RequestAllocation bundle from
the app's •RequestArena. The bundle's request_cache
holds request-scoped DI instances; framework_values is
re-populated per call with the live Request so the DI
container can short-circuit Request lookups; kwargs
holds handler arguments assembled from the extractor plan.
Every container returned from the lease is cleared on exit, so pooled allocations never leak user data across requests.