Plugins¶
Heaven provides a simple yet powerful plugin system that allows you to extend the framework's functionality, integrate with databases, or add custom behavior.
The Plugin Protocol¶
A plugin is simply a class (or object) that implements an install method. This method receives the application instance (Router) as its only argument, allowing you to register hooks, inject context, or modify configuration.
class MyPlugin:
def install(self, app):
# Register hooks
app.ON('startup', self.connect)
# Inject into context
app.keep('my_plugin', self)
async def connect(self, app):
print("Plugin connected!")
Using Plugins¶
Registering a plugin is done via app.plugin():
app = Router()
app.plugin(MyPlugin())
Plugin Marketplace¶
Looking for ready-made plugins? Check out the Plugin Marketplace for community plugins and integrations.
Advanced Example: pg_heaven¶
Here is a complete example of a PostgreSQL plugin that integrates seamless msgspec destructuring for high-performance data retrieval.
msgspec here is the plugin's own dependency
Heaven itself validates with pytastic, not msgspec. This plugin simply chooses msgspec for fast row-to-object conversion — a plugin is free to bring whatever libraries it likes.
The Plugin¶
import msgspec
import asyncpg
from typing import Type, TypeVar, List, Any
T = TypeVar("T")
class PostgresPlugin:
def __init__(self, dsn: str, name: str = "pg"):
self.dsn = dsn
self.name = name
self.pool = None
def install(self, app):
"""The distinct integration point."""
# 1. Register Lifecycle Hooks
# Heaven automatically manages the pool connection/disconnection
app.ON("startup", self.startup)
app.ON("shutdown", self.shutdown)
# 2. Inject into Context
# Using a BEFORE hook ensures 'ctx.pg' is available on every request
# with zero overhead for the end-user
async def inject_db(req, res, ctx):
# This makes 'ctx.db' or 'ctx.pg' available
ctx.keep(self.name, self)
app.BEFORE("/*", inject_db) # see the caveat below
async def startup(self, app):
self.pool = await asyncpg.create_pool(self.dsn)
print(f"🔌 {self.name} connected")
async def shutdown(self, app):
await self.pool.close()
print(f"🔌 {self.name} disconnected")
# The Magic Method: Msgspec Integration
async def fetch(self, query: str, *args, bind: Type[T] = None) -> List[T] | List[Any]:
"""
Executes query and seamlessly destructures into a msgspec Struct
if 'bind' is provided.
"""
rows = await self.pool.fetch(query, *args)
if bind:
# fast-path: asyncpg Record -> dict -> msgspec Struct
# msgspec.convert is extremely fast at this
return msgspec.convert(rows, List[bind])
return rows
Usage in Application¶
This setup allows for implicit context availability and explicit, typed destructuring.
from heaven import Router
import msgspec
# 1. Define your data structure
class User(msgspec.Struct):
id: int
email: str
is_active: bool
app = Router()
# 2. Register (once)
app.plugin(PostgresPlugin(dsn="postgres://user:pass@localhost/db", name="db"))
# 3. Use in handler
async def get_users(req, res, ctx):
# 'ctx.db' is auto-injected
# 'bind=User' automatically converts the SQL result to List[User]
users = await ctx.db.fetch("SELECT * FROM users", bind=User)
# 'users' is now a list of Structs, ready for validation or automatic encoding
res.body = users
app.GET("/users", get_users)
A /* hook runs before route-specific ones
BEFORE hooks run from the broadest pattern inward, so a plugin injecting on BEFORE('/*') has already run by the time a route-specific BEFORE('/users') hook executes and ctx.db is populated.
If you would rather not depend on hook ordering at all, read the resource from app scope, which is populated at startup and always available:
def install(self, app):
app.ON('startup', self.startup)
app.keep(self.name, self) # available everywhere, no hook ordering involved
Handlers then use req.app.peek('db'). See hook execution order.