Added per-instance state field documentation to ModuleContext reference.

2026-02-22 15:03:14 -05:00
parent 7882423a22
commit ec05d61acb
+44
@@ -27,12 +27,56 @@ async def setup(ctx: ModuleContext) -> None:
| `routes` | `ModuleRoutes` | HTTP route registration and URL building ([details](Modules-Routes)). |
| `http` | `HttpClient` | Shared HTTP client for external requests ([details](Modules-HTTP-Client)). |
| `admin_client` | `OwncastAdminClient \| None` | Owncast Admin API client, or `None` if admin is not enabled in config ([details](Modules-Owncast-API)). |
| `state` | `dict[str, Any]` | Per-instance state storage for runtime objects. See [Per-Instance State](#per-instance-state) below. |
| `logger` | `logging.Logger` | Logger named `owlbot.modules.<module_name>`. Auto-derived from `module_name`. |
`ModuleContext` is a dataclass. All fields except `logger` are set during construction. `logger` is derived from `module_name` in `__post_init__`.
The service wrapper types (`ModuleCommands`, `ModuleEvents`, `ModuleRoutes`) are importable from `owlbot.api` for use in type annotations on helper functions:
### Per-Instance State
Modules that need to hold in-memory runtime objects (managers, schedulers, caches) across handler invocations should use the `state` dict instead of module-level globals. This ensures multiple bot instances running in the same Python process maintain fully independent state.
Store objects during setup using string keys, and retrieve them in handlers via `ctx.module.state`:
```python
from owlbot.api import ModuleContext, on_setup, on_teardown
class MyManager:
"""Example stateful manager."""
def __init__(self, greeting: str) -> None:
self.greeting = greeting
@on_setup
async def setup(ctx: ModuleContext) -> None:
ctx.state["manager"] = MyManager(greeting="hello")
@on_teardown
async def teardown(ctx: ModuleContext) -> None:
ctx.state.pop("manager", None)
```
For type safety, wrap access in a typed helper function. The `isinstance` check narrows the return type so callers get full type information:
```python
from owlbot.api import CommandContext, ModuleContext, on_command
def get_manager(ctx: ModuleContext) -> MyManager:
"""Return the MyManager for this module instance."""
manager = ctx.state.get("manager")
if not isinstance(manager, MyManager):
raise RuntimeError("MyManager is not initialized.")
return manager
@on_command("greet")
async def greet(ctx: CommandContext) -> None:
manager = get_manager(ctx.module) # Fully typed as MyManager
await ctx.owncast_client.send_message(manager.greeting)
```
## EventContext\[E\]
Event handlers receive an `EventContext` parameterized by the event type. It wraps the event data and provides access to all services from `ModuleContext`: