Module API Reference¶
This reference covers the complete API for creating and managing StarStreamer modules, including the base classes, registry system, and lifecycle management.
BaseModule Class¶
BaseModule
¶
Bases: ABC
Base class for all StarStreamer modules
Source code in src/modules/base.py
on_disable
async
¶
Called when module is disabled
Override this to perform cleanup tasks when the module is disabled.
on_enable
async
¶
Called when module is enabled
Override this to perform setup tasks when the module is first enabled or re-enabled.
on_load
async
¶
Called when module is first loaded
Override this for one-time initialization tasks.
on_unload
async
¶
Called when module is unloaded
Override this for cleanup when module is unloaded.
register_actions
abstractmethod
async
¶
Import and register all module actions
This method should import all action files to trigger their @on_event decorators to register with the event bus.
All StarStreamer modules must inherit from BaseModule and implement the required abstract methods.
Abstract Methods¶
module_name: str (Property)¶
Required Implementation: Every module must return a unique string identifier.
Example:
register_actions() (Method)¶
@abstractmethod
async def register_actions(self) -> None:
"""Import and register all module actions"""
pass
Purpose: Import action files to trigger their @on_event decorators.
Example:
async def register_actions(self) -> None:
try:
# Import triggers decorator registration
from modules.my_module.actions import handlers # noqa: F401
self.logger.info("Actions registered successfully")
except Exception as e:
self.logger.error(f"Failed to register actions: {e}")
raise
Lifecycle Hooks¶
All lifecycle hooks are optional and have default implementations that do nothing.
on_load()¶
When Called: Once when the module is first loaded into the registry.
Use Cases: - Database schema initialization - Service setup - Resource allocation
on_unload()¶
When Called: When the module is removed from the registry.
Use Cases: - Cleanup resources - Close connections - Save state
on_enable()¶
When Called:
- During initial loading (after on_load())
- When manually enabled after being disabled
Use Cases: - Start background tasks - Enable event handlers - Resume module functionality
on_disable()¶
When Called: When module is manually disabled (but not unloaded).
Use Cases: - Pause background tasks - Temporarily disable features - Suspend event handling
Properties and Attributes¶
Instance Attributes¶
def __init__(self) -> None:
self.name: str = "" # Module display name
self.enabled: bool = True # Whether module is enabled
self._loaded: bool = False # Internal loaded state
Complete Module Example¶
import logging
from modules.base import BaseModule
class MyCustomModule(BaseModule):
"""Example custom module implementation"""
def __init__(self) -> None:
super().__init__()
self.logger = logging.getLogger(__name__)
self.background_task = None
@property
def module_name(self) -> str:
return "my_custom_module"
async def register_actions(self) -> None:
"""Register all action handlers"""
try:
from modules.my_custom_module.actions import commands # noqa: F401
from modules.my_custom_module.actions import events # noqa: F401
self.logger.info("Custom module actions registered")
except Exception as e:
self.logger.error(f"Failed to register actions: {e}")
raise
async def on_load(self) -> None:
"""One-time initialization"""
self.logger.info("Custom module loaded - initializing resources")
# Initialize databases, services, etc.
async def on_enable(self) -> None:
"""Start module functionality"""
self.logger.info("Custom module enabled")
# Start background tasks, enable features
async def on_disable(self) -> None:
"""Pause module functionality"""
self.logger.info("Custom module disabled")
# Pause tasks, disable features
async def on_unload(self) -> None:
"""Final cleanup"""
self.logger.info("Custom module unloaded")
# Cleanup resources, save state
ModuleRegistry Class¶
ModuleRegistry
¶
Registry for managing StarStreamer modules
Source code in src/modules/registry.py
disable_module
async
¶
Disable a loaded module
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Module name to disable |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if module was disabled successfully, False otherwise |
Source code in src/modules/registry.py
enable_module
async
¶
Enable a loaded module
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Module name to enable |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if module was enabled successfully, False otherwise |
Source code in src/modules/registry.py
get_enabled_modules
async
¶
Get list of enabled modules from database
Returns:
| Type | Description |
|---|---|
list[str]
|
List of enabled module names |
Source code in src/modules/registry.py
get_loaded_modules
async
¶
Get list of currently loaded modules
Returns:
| Type | Description |
|---|---|
list[str]
|
List of loaded module names |
get_module_stats
async
¶
Get module statistics
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
Dictionary with module stats |
Source code in src/modules/registry.py
is_module_enabled
async
¶
Check if a module is enabled
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Module name to check |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if module is enabled, False otherwise |
Source code in src/modules/registry.py
load_all_modules
async
¶
Load all discovered modules
Returns:
| Type | Description |
|---|---|
dict[str, bool]
|
Dict mapping module names to whether they loaded successfully |
Source code in src/modules/registry.py
load_module
async
¶
Dynamically load and register a module
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Module name to load (e.g., "chat", "rpg") |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if module was loaded successfully, False otherwise |
Source code in src/modules/registry.py
reload_module
async
¶
Hot reload a module using importlib.reload()
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Module name to reload (e.g., "chat", "alerts") |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if module was reloaded successfully, False otherwise |
Source code in src/modules/registry.py
unload_module
async
¶
Unload a module
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Module name to unload |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if module was unloaded successfully, False otherwise |
Source code in src/modules/registry.py
The ModuleRegistry manages the lifecycle of all modules in StarStreamer.
Constructor¶
Parameters:
- db: Database instance for persistence
Module Management Methods¶
load_module(name: str) -> bool¶
Process:
1. Import module class dynamically
2. Call on_load() lifecycle hook
3. Call register_actions() to register handlers
4. Call on_enable() to activate module
5. Update database state
Returns: True if successful, False otherwise
unload_module(name: str) -> bool¶
Process:
1. Call on_disable() lifecycle hook
2. Call on_unload() lifecycle hook
3. Remove from registry
4. Update database state
enable_module(name: str) -> bool¶
Use Case: Re-enable a disabled module without full reload.
disable_module(name: str) -> bool¶
Use Case: Temporarily disable module functionality.
reload_module(name: str) -> bool¶
async def reload_module(self, name: str) -> bool:
"""Hot reload a module using importlib.reload()"""
Process:
1. Unload the module if currently loaded
2. Use importlib.reload() to reload Python modules
3. Re-discover the module class (in case class changed)
4. Load the reloaded module with fresh state
Returns: True if reload successful, False otherwise
Use Cases:
- Development mode with --reload flag
- Manual module updates without restarting
- Applying code changes without losing connections
Example:
# Hot reload the chat module
success = await registry.reload_module("chat")
if success:
logger.info("Chat module reloaded successfully")
else:
logger.error("Failed to reload chat module")
load_all_modules() -> dict[str, bool]¶
Process: 1. Discover available modules in modules directory 2. Load each module that has a valid module.py file 3. Track load success/failure for each module
Returns: Dict mapping module names to load success status
Example:
Hot Reloading Methods¶
_reload_python_modules(module_name: str) -> None¶
async def _reload_python_modules(self, module_name: str) -> None:
"""Reload Python modules using importlib.reload()"""
Internal Method: Used by reload_module() to handle the low-level module reloading.
Process:
1. Find all Python modules with the module prefix (e.g., modules.chat.*)
2. Use importlib.reload() on each found module
3. Handle circular imports and dependency order
Note: This is an internal method and should not be called directly.
_discover_modules() -> None¶
Process:
1. Scan the modules directory for subdirectories
2. Look for module.py files in each subdirectory
3. Import and cache module classes for lazy loading
4. Update internal module class registry
Auto-Discovery Rules:
- Directory must contain module.py file
- Directory name cannot start with underscore (_)
- Module class must follow naming convention: {ModuleName}Module
Query Methods¶
get_enabled_modules() -> list[str]¶
Returns: List of module names that are enabled in the database.
get_loaded_modules() -> list[str]¶
Returns: List of module names currently loaded in memory.
is_module_enabled(name: str) -> bool¶
get_module_stats() -> dict[str, int]¶
Returns:
{
"total_modules": 5, # Total modules in database
"enabled_modules": 3, # Enabled modules in database
"loaded_modules": 2 # Currently loaded in memory
}
Usage Examples¶
Web Interface Management (Recommended)¶
StarStreamer v0.6.1+ includes a web interface for module management:
- Access the Modules Page:
- Start StarStreamer:
uv run python src/main.py -
Available Actions:
- Load - Load an unloaded module into memory
- Enable - Activate a loaded but disabled module
- Disable - Deactivate a module while keeping it loaded
-
Reload - Hot reload module code (useful for development)
-
Real-time Statistics:
- Total modules discovered
- Currently enabled modules
- Currently loaded modules
- Module status indicators
Programmatic Module Management¶
from modules.registry import ModuleRegistry
from starstreamer.db.database import Database
# Initialize registry
db = Database("data/starstreamer.db")
registry = ModuleRegistry(db)
# Load modules
await registry.load_module("chat")
await registry.load_module("alerts")
await registry.load_module("rpg")
# Check status
enabled = await registry.get_enabled_modules()
loaded = await registry.get_loaded_modules()
stats = await registry.get_module_stats()
print(f"Enabled: {enabled}")
print(f"Loaded: {loaded}")
print(f"Stats: {stats}")
Dynamic Module Control¶
# Temporarily disable a module
await registry.disable_module("rpg")
# Re-enable it later
await registry.enable_module("rpg")
# Completely unload a module
await registry.unload_module("chat")
# Load it back
await registry.load_module("chat")
Module State Management¶
State Persistence¶
Module states are persisted in the database:
CREATE TABLE modules (
name TEXT PRIMARY KEY,
enabled BOOLEAN DEFAULT 1,
loaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
State Transitions¶
stateDiagram-v2
[*] --> Unloaded
Unloaded --> Loading: load_module()
Loading --> Loaded: on_load() success
Loading --> Unloaded: on_load() failure
Loaded --> Registering: register_actions()
Registering --> Enabled: on_enable() success
Registering --> Loaded: register_actions() failure
Enabled --> Disabled: disable_module()
Disabled --> Enabled: enable_module()
Enabled --> Unloaded: unload_module()
Disabled --> Unloaded: unload_module()
Error Handling¶
All registry methods include comprehensive error handling:
- Load Failures: Module remains unloaded, error logged
- Registration Failures: Module cleanup performed automatically
- Lifecycle Hook Failures: Operation aborted, state rolled back
- Database Failures: In-memory state preserved, warning logged
Best Practices¶
Module Design¶
- Single Responsibility: Each module should have one clear purpose
- Fail-Safe Initialization: Handle all possible initialization failures
- Resource Cleanup: Always implement proper cleanup in lifecycle hooks
- Error Resilience: Don't let one action failure crash the entire module
Lifecycle Hook Implementation¶
async def on_load(self) -> None:
"""Safe initialization pattern"""
try:
# Initialize critical resources
await self.setup_database()
await self.load_configuration()
self.logger.info("Module loaded successfully")
except Exception as e:
self.logger.error(f"Failed to load module: {e}")
# Cleanup partial initialization
await self.cleanup()
raise
async def on_unload(self) -> None:
"""Safe cleanup pattern"""
try:
# Stop all background tasks
if hasattr(self, 'background_task') and self.background_task:
self.background_task.cancel()
# Close connections
await self.close_connections()
self.logger.info("Module unloaded successfully")
except Exception as e:
self.logger.error(f"Error during unload: {e}")
# Don't re-raise - cleanup should be best-effort
Action Registration¶
async def register_actions(self) -> None:
"""Safe action registration"""
try:
# Import modules in dependency order
from modules.my_module.actions import core_handlers # noqa: F401
from modules.my_module.actions import extended_handlers # noqa: F401
from modules.my_module.actions import admin_handlers # noqa: F401
self.logger.info("All action handlers registered")
except ImportError as e:
self.logger.error(f"Missing action module: {e}")
raise
except Exception as e:
self.logger.error(f"Unexpected error registering actions: {e}")
raise