Lifecycle Events¶
Lilya provides a powerful and explicit lifecycle system to help you run logic at specific points in your application's lifetime, such as connecting to databases, initializing caches, or performing cleanup before shutdown.
Lifecycle hooks exist at three levels:
- Global hooks – Registered once for all apps.
- App hooks – Specific to a
Lilya
orChildLilya
instance. - Custom lifespan managers – Using
@asynccontextmanager
.
Global Lifecycle Hooks¶
Global hooks are declared once and automatically injected into every Lilya
or ChildLilya
instance.
They are defined using decorators from lilya.lifecycle
:
from lilya.lifecycle import on_startup, on_shutdown
@on_startup
async def connect_global_services():
print("Connecting to global services...")
@on_shutdown
async def disconnect_global_services():
print("Shutting down global services...")
These hooks are executed before any app starts up, and after every app shuts down.
Order of execution:
Phase | Order |
---|---|
Startup | Global -> App |
Shutdown | App -> Global |
App-Level Hooks (on_startup
/ on_shutdown
)¶
Each Lilya
(or ChildLilya
) app can define its own lifecycle hooks, which run only for that app instance.
These are passed directly to the app constructor:
from lilya.apps import Lilya
from lilya.responses import PlainText
from lilya.routing import Path
async def connect_db():
print("Connecting to database...")
async def disconnect_db():
print("Closing database connection...")
async def home():
return PlainText("Hello, world!")
app = Lilya(
routes=[Path("/", home)],
on_startup=[connect_db],
on_shutdown=[disconnect_db],
)
When Lilya starts, it automatically runs:
global_startup -> connect_db
and on shutdown:
disconnect_db -> global_shutdown
ChildLilya Applications¶
You can structure large applications using modular ChildLilya instances.
Each ChildLilya
has its own independent lifecycle hooks.
When included in a parent app, its lifecycle does not automatically trigger.
This isolation prevents unintended side effects when composing apps.
from lilya.apps import Lilya, ChildLilya
from lilya.routing import Path, Include
from lilya.responses import PlainText
async def child_startup():
print("Child app started")
async def child_shutdown():
print("Child app stopped")
child = ChildLilya(
routes=[Path("/", lambda: PlainText("Hello from child!"))],
on_startup=[child_startup],
on_shutdown=[child_shutdown],
)
app = Lilya(
routes=[Include("/child", app=child)]
)
In this example:
- Starting the parent app will not trigger the child's lifecycle.
- If the
ChildLilya
runs independently, its hooks will execute normally.
This design gives you predictable and modular control over your app lifecycles.
Using a Custom Lifespan Context¶
Instead of using startup/shutdown hooks separately, you can define a single lifespan context manager that handles both.
from contextlib import asynccontextmanager
from lilya.apps import Lilya
@asynccontextmanager
async def lifespan(app):
print("Startup logic")
yield
print("Shutdown logic")
app = Lilya(lifespan=lifespan)
This is a clean, async-native way to manage setup and teardown in a single, elegant block.
When to Use What¶
Use Case | Recommended Mechanism |
---|---|
Global service initialization (e.g., telemetry, tracing) | @on_startup / @on_shutdown decorators |
Per-app setup (e.g., DB, cache, per-instance configuration) | on_startup / on_shutdown in Lilya() |
Highly customized setup or teardown flow | lifespan context manager |
Modular, reusable sub-apps | ChildLilya with isolated hooks |
Full Example¶
from contextlib import asynccontextmanager
from lilya.apps import Lilya, ChildLilya
from lilya.routing import Path, Include
from lilya.responses import PlainText
from lilya.lifecycle import on_startup, on_shutdown
@on_startup
def init_telemetry():
print("Telemetry started")
@on_shutdown
def stop_telemetry():
print("Telemetry stopped")
async def main_startup():
print("Main DB connected")
async def main_shutdown():
print("Main DB disconnected")
async def child_startup():
print("Child cache ready")
async def child_shutdown():
print("Child cache flushed")
child_app = ChildLilya(
routes=[Path("/", lambda: PlainText("Hello from child!"))],
on_startup=[child_startup],
on_shutdown=[child_shutdown],
)
app = Lilya(
routes=[Include("/child", app=child_app)],
on_startup=[main_startup],
on_shutdown=[main_shutdown],
)
# Expected output when running app
"""
Telemetry started
Main DB connected
<app serving>
Main DB disconnected
Telemetry stopped
"""
Summary¶
- Global hooks – Registered with decorators, run for every app.
- App hooks – Defined per
Lilya
orChildLilya
instance. - Independent lifecycles – No cross-triggering between parent and child apps.
- Optional
lifespan
– For advanced setup/teardown logic.