The OpenViper Application

The openviper.app module contains the central OpenViper class - the entry point for all request handling. It ties together routing, middleware, dependency injection, exception handling, and OpenAPI schema generation into a single ASGI application.

Quick Start

from openviper import OpenViper

app = OpenViper(title="My API", version="1.0.0")

@app.get("/")
async def index(request):
    return {"message": "Hello, World!"}

The app instance is an ASGI callable, so it can be passed directly to an ASGI server such as uvicorn:

uvicorn myproject.app:app

OpenViper Class

class openviper.app.OpenViper(debug=None, middleware=None, title=None, version=None, description=None, openapi_url=None, docs_url=None, redoc_url=None)

The central ASGI application class. Acts as both an ASGI callable and a router decorator, so routes are registered directly on the app instance.

Parameters:
  • debug – Enable debug mode (overrides settings.DEBUG when set).

  • middleware – Extra middleware entries to prepend to the stack. Each entry is either a middleware class or a (cls, kwargs_dict) tuple.

  • title – OpenAPI document title. Falls back to settings.OPENAPI["title"].

  • version – API version string. Falls back to settings.OPENAPI["version"].

  • description – OpenAPI description. Falls back to settings.OPENAPI["description"].

  • openapi_url – URL path for the OpenAPI JSON schema.

  • docs_url – URL path for the Swagger UI.

  • redoc_url – URL path for the ReDoc UI.

get(path, **kwargs)
post(path, **kwargs)
put(path, **kwargs)
patch(path, **kwargs)
delete(path, **kwargs)
options(path, **kwargs)

Decorator shortcuts that delegate to the internal Router. Register a handler for the corresponding HTTP method on path.

route(path, methods, **kwargs)

Register a handler for path matching the given methods list.

include_router(router, prefix='')

Mount a sub-Router. When prefix is given, it is prepended to all routes in the sub-router.

on_startup(func)

Register a startup lifecycle handler. func may be a plain function or an async coroutine. Called during the ASGI lifespan.startup event.

on_shutdown(func)

Register a shutdown lifecycle handler. Called during the ASGI lifespan.shutdown event, in registration order.

exception_handler(exc_class)

Decorator that registers a custom exception handler for exc_class. When an exception of that type (or a subclass) escapes a handler, the registered callback is invoked with (request, exc) and must return a Response.

get_openapi_schema() dict

Return the generated OpenAPI schema dict. The result is cached after the first call; use invalidate_openapi_schema() to force regeneration.

invalidate_openapi_schema()

Clear the cached OpenAPI schema so it is regenerated on the next request.

invalidate_middleware_cache()

Clear the cached middleware stack. Useful when routes or middleware are added dynamically after initial setup.

coerce_response(result) Response

Convert a handler’s return value into a proper Response. Supports dicts, lists, strings, bytes, None, Pydantic models, and objects with a model_dump() method.

call_handler(handler, request) Response

Invoke handler with the appropriate parameters extracted from request. Performs automatic response coercion via coerce_response().

resolve_middleware(raw_middleware) list

Resolve a list of middleware entries (strings or classes) into their corresponding classes. When a CORSMiddleware entry is found, its keyword arguments are wired from settings automatically.

cors_kwargs() dict

Build the keyword arguments for CORSMiddleware from the current settings.

run(host='127.0.0.1', port=8000, reload=True, log_level='info', workers=1)

Start a uvicorn development server. Prefer viperctl start-server for production deployments.

test_client(**kwargs) httpx.AsyncClient

Return an httpx.AsyncClient configured to send requests directly to this app. The returned client must be used as an async context manager.

Module-Level Helpers

openviper.app.get_handler_signature(handler)

Return (signature, type_hints) for handler, cached by identity. Bounded by an LRU cache of 128 entries.

openviper.app.resolve_middleware_entry(mw)

Import and return a middleware class from a dotted string, or pass through a non-string mw as-is. Raises ImportError if mw is a string that cannot be imported.

Lifecycle & App Discovery

When the ASGI lifespan starts, the OpenViper application performs the following steps in order:

  1. Build the middleware stack (cached after first build).

  2. Generate the OpenAPI schema (if enabled).

  3. Call ready() on every installed app that exposes one.

  4. Call startup() from installed app lifecycle.py modules.

  5. Run registered on_startup handlers.

On shutdown the process runs in reverse:

  1. Call shutdown() for started lifecycle apps in reverse order.

  2. Run registered on_shutdown handlers.

Route Auto-Discovery

If OPENVIPER_SETTINGS_MODULE is set (e.g. "myproject.settings"), the application automatically imports myproject.routes and registers any route_paths list found there. Each entry in route_paths must be a (prefix, Router) tuple.

# myproject/routes.py
from openviper.routing.router import Router
from myapp.views import user_router, order_router

route_paths = [
    ("/users", user_router),
    ("/orders", order_router),
]

Installed App Hooks

Each entry in settings.INSTALLED_APPS may expose a ready() callable in one of three locations (checked in order):

  1. <app>.ready - a top-level attribute on the app package.

  2. <app>.apps.ready - inside an apps sub-module.

  3. <app>.lifecycle.ready - inside a lifecycle sub-module.

The callable may be either a plain function or an async coroutine.

Middleware Stack

The middleware stack is built from settings.MIDDLEWARE plus any extra middleware passed to the OpenViper constructor. String entries are resolved via resolve_middleware_entry(). The stack is assembled in this order (outermost first):

  1. ServerErrorMiddleware - catches unhandled exceptions.

  2. DefaultLandingMiddleware - serves the landing page when no custom root route exists.

  3. User-configured middleware from settings.MIDDLEWARE.

  4. RateLimitMiddleware - prepended when RATE_LIMIT_REQUESTS > 0.

  5. Static and media file serving (debug mode only).

When CORSMiddleware is present in the middleware list, its keyword arguments are automatically wired from the CORS_* settings.

Response Coercion

Handlers do not need to return a Response explicitly. The OpenViper.coerce_response() method converts common return types automatically:

Return Type

Response

dict or list

JSONResponse

str or bytes

PlainTextResponse

None

Response(status_code=204)

Pydantic BaseModel

JSONResponse via model_dump()

Object with model_dump()

JSONResponse via model_dump()

Response

Passed through unchanged

Exception Handling

Unhandled exceptions are dispatched to the most specific registered handler by walking the exception’s MRO. Built-in handling is provided for:

  • HTTPException - returns the status code and detail from the exception.

  • TableNotFound - returns 503; hides the table name in production.

  • FieldError / QueryError - returns 400; hides field names in production.

  • All other exceptions - returns 500 with a debug traceback in debug mode, or a generic "Internal Server Error" in production.

Custom exception handlers are registered with the exception_handler() decorator.

Append-Slash Redirects

In production mode (DEBUG=False), if a request path without a trailing slash does not match any route but the slash-appended path does, the application returns a 301 redirect. The redirect target is validated to prevent open-redirect attacks: directory traversal sequences (..) and non-relative paths are rejected.

API Reference

openviper.app.HAS_PYDANTIC

True when the pydantic package is importable; False otherwise. Used by coerce_response() to detect Pydantic model instances.