module-development
SKILL.md
Module Development in myfy
Modules are the building blocks of myfy applications. Each module follows a protocol with lifecycle hooks.
Available Modules
myfy provides these built-in modules:
| Module | Package | Purpose |
|---|---|---|
| WebModule | myfy.web | HTTP routing, ASGI, FastAPI-like decorators |
| DataModule | myfy.data | SQLAlchemy async, migrations, sessions |
| FrontendModule | myfy.frontend | Jinja2, Tailwind 4, DaisyUI 5, Vite |
| TasksModule | myfy.tasks | Background jobs, SQL-based task queue |
| UserModule | myfy.user | Auth, OAuth, user management |
| CliModule | myfy.commands | Custom CLI commands |
| AuthModule | myfy.web.auth | Type-based authentication, protected routes |
| RateLimitModule | myfy.web.ratelimit | Rate limiting per IP or user |
Module Protocol
from myfy.core import BaseModule, Container, SINGLETON
class MyModule(BaseModule):
"""Custom module following the Module protocol."""
def __init__(self):
super().__init__(name="my-module")
@property
def requires(self) -> list[type]:
"""Module types this module depends on."""
return [] # e.g., [WebModule, DataModule]
@property
def provides(self) -> list[type]:
"""Extension protocols this module implements."""
return [] # e.g., [IWebExtension]
def configure(self, container: Container) -> None:
"""Phase 1: Register providers in DI container."""
container.register(
type_=MyService,
factory=lambda: MyService(),
scope=SINGLETON,
)
def extend(self, container: Container) -> None:
"""Phase 2: Modify other modules' registrations (optional)."""
pass
def finalize(self, container: Container) -> None:
"""Phase 3: Configure singletons after compilation (optional)."""
# Safe to get singletons here - container is compiled
service = container.get(MyService)
service.setup()
async def start(self) -> None:
"""Phase 4: Start runtime services (connect to DB, etc.)."""
pass
async def stop(self) -> None:
"""Phase 5: Cleanup resources gracefully."""
pass
Lifecycle Phases
- Discovery - Application discovers all modules
- Dependency Validation - Validates module dependency graph
- Configure - Each module registers services in DI
- Extend - Modules can modify other modules' registrations
- Compile - DI container builds injection plans
- Finalize - Modules configure singleton services
- Start - Runtime services start (in dependency order)
- Stop - Graceful shutdown (reverse order)
Module Dependencies
Declare dependencies via the requires property:
from myfy.web import WebModule
from myfy.data import DataModule
class MyModule(BaseModule):
@property
def requires(self) -> list[type]:
# This module needs WebModule and DataModule to be registered
return [WebModule, DataModule]
The application validates all required modules are present and initializes them in topological order.
Registering Settings
Modules typically register their own settings:
from myfy.core.config import load_settings
from myfy.core.di.types import ProviderKey
class MyModule(BaseModule):
def __init__(self, settings: MySettings | None = None):
super().__init__("my-module")
self._settings = settings
def configure(self, container: Container) -> None:
# Check if settings already registered (avoid double registration)
key = ProviderKey(MySettings)
if key not in container._providers:
if self._settings is None:
self._settings = load_settings(MySettings)
container.register(
type_=MySettings,
factory=lambda: self._settings,
scope=SINGLETON,
)
Extension Protocols
Define extension interfaces using provides:
from myfy.web.extensions import IWebExtension
class MyModule(BaseModule):
@property
def provides(self) -> list[type]:
return [IWebExtension]
def finalize(self, container: Container) -> None:
# Access ASGIApp after compilation
asgi_app = container.get(ASGIApp)
asgi_app.app.mount("/my-path", MyASGIApp())
Complete Module Example
"""
MyFeature module for myfy.
Provides feature X with Y capabilities.
"""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
from myfy.core import BaseModule, SINGLETON
from myfy.core.config import load_settings
from myfy.core.di.types import ProviderKey
from .config import MyFeatureSettings
from .service import MyFeatureService
if TYPE_CHECKING:
from myfy.core.di import Container
logger = logging.getLogger(__name__)
class MyFeatureModule(BaseModule):
"""
MyFeature module.
Features:
- Feature capability 1
- Feature capability 2
"""
def __init__(self, settings: MyFeatureSettings | None = None):
super().__init__("my-feature")
self._settings = settings
@property
def requires(self) -> list[type]:
return []
@property
def provides(self) -> list[type]:
return []
def configure(self, container: Container) -> None:
logger.debug("Configuring MyFeatureModule...")
# Register settings
key = ProviderKey(MyFeatureSettings)
if key not in container._providers:
if self._settings is None:
self._settings = load_settings(MyFeatureSettings)
container.register(
type_=MyFeatureSettings,
factory=lambda: self._settings,
scope=SINGLETON,
)
# Register services
container.register(
type_=MyFeatureService,
factory=lambda settings=self._settings: MyFeatureService(settings),
scope=SINGLETON,
)
logger.debug("MyFeatureModule configured")
async def start(self) -> None:
logger.info("MyFeature module started")
async def stop(self) -> None:
logger.info("MyFeature module stopped")
# Module instance for entry point discovery
my_feature_module = MyFeatureModule()
Best Practices
- Always call super().init(name) - Required by BaseModule
- Use logging - Add debug/info logs for troubleshooting
- Allow settings injection - Accept optional settings in init for testing
- Check for existing registrations - Avoid double registration errors
- Keep configure() pure - Only register providers, no side effects
- Use finalize() for singletons - Safe to resolve singletons here
- Make start/stop idempotent - Safe to call multiple times
- Include docstrings - Document what the module provides
Weekly Installs
2
Repository
psincraian/myfyGitHub Stars
86
First Seen
14 days ago
Security Audits
Installed on
gemini-cli2
opencode2
codebuddy2
github-copilot2
codex2
kimi-cli2