Added per-instance state field documentation to ModuleContext reference.
@@ -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`:
|
||||
|
||||
Reference in New Issue
Block a user