App Lifecycle
The openviper.apps package provides hook discovery and execution for
application lifecycle events. Apps declare optional ready(), startup(),
and shutdown() hooks in a lifecycle.py module; the framework discovers
and runs them automatically based on INSTALLED_APPS ordering.
Overview
Each installed app may contain a lifecycle.py module that defines zero or
more of the following hooks:
ready() - synchronous, called once during bootstrapping before the event loop starts. Use for one-time setup such as registering signals or warming caches.
startup() - async, called when the ASGI server starts accepting requests. Use for opening connections, starting background listeners, or any async initialisation that must complete before traffic arrives.
shutdown() - async, called in reverse order on graceful shutdown. Use for closing connections, flushing buffers, or releasing resources.
If an app does not define lifecycle.py, it is silently skipped.
Hook Discovery
AppLifecycleManager iterates over
INSTALLED_APPS and attempts to import <app_name>.lifecycle for each
entry. Discovered hooks are validated at import time:
readymust be a plain synchronous function (not a coroutine, not a class method).startupandshutdownmust be async coroutine functions.
Invalid hooks raise AppLifecycleConfigError.
Execution Order
Hooks execute in INSTALLED_APPS order, with one critical exception:
shutdown() runs in reverse order so that dependencies are torn down
after their dependants.
If a startup() hook fails, the framework immediately runs shutdown()
for all apps that already started successfully (in reverse order), then raises
AppStartupError with any
shutdown_errors collected during cleanup.
Key Classes
openviper.apps.lifecycle
- class AppLifecycle(app_name, ready=None, startup=None, shutdown=None)
Data container for a single app’s lifecycle hooks.
- Parameters:
app_name – dotted app module path.
ready – synchronous
ready()callable, orNone.startup – async
startup()callable, orNone.shutdown – async
shutdown()callable, orNone.
- class AppLifecycleManager
Discovers, validates, and executes lifecycle hooks across all installed apps.
- discover(app_names) list[AppLifecycle]
Import
<app_name>.lifecyclefor each name in app_names, validate hooks, and return the resultingAppLifecyclelist.
- run_ready() None
Call every discovered
ready()hook synchronously. RaisesAppReadyErroron the first failure.
- async run_startup() -> None
Await every discovered
startup()hook. On failure, runsshutdown()for already-started apps and raisesAppStartupError.
- async run_shutdown() -> None
Await every
shutdown()hook in reverse startup order. RaisesAppShutdownErrorif any hook fails.
openviper.apps.exceptions
- exception AppLifecycleError
Base exception for all app lifecycle errors.
- exception AppLifecycleConfigError(AppLifecycleError)
Raised when a
lifecycle.pymodule defines an invalid hook (wrong callable type, async/sync mismatch).
- exception AppLifecycleImportError(AppLifecycleError)
Raised when
lifecycle.pyexists but cannot be imported.
- exception AppReadyError(AppLifecycleError)
Raised when a
ready()hook raises an exception. Carriesapp_nameandoriginal_exceptionattributes.
- exception AppStartupError(AppLifecycleError)
Raised when a
startup()hook raises an exception. Carriesapp_name,original_exception, andshutdown_errors(a list ofAppShutdownErrorfrom cleanup).
- exception AppShutdownError(AppLifecycleError)
Raised when one or more
shutdown()hooks fail. Carrieserrors, a list of(app_name, exception)pairs.
Example
A typical lifecycle.py inside an app package:
# myapp/lifecycle.py
async def startup():
await db_pool.connect()
async def shutdown():
await db_pool.disconnect()
The app is registered in settings:
# settings.py
INSTALLED_APPS = [
"myapp",
]