Modules
In Oridecon, a Module is a high-level organizational unit that groups related providers, services, and configuration. It serves as an encapsulation boundary, defining a strict “public API” for other parts of the application.
1. The @module Decorator
Section titled “1. The @module Decorator”Use the @module decorator to define a module. This tells Oridecon how to treat the package during auto-discovery and DI resolution.
from oridecon.di.module import module
@module( providers=[ChatProvider], exports=[ChatServiceProtocol, MessageStoreProtocol],)class ChatModule: """A module providing AI chat functionality."""Module Class Variables
Section titled “Module Class Variables”@module()class AuthModule: name: str | None = None # Module name (auto-derived if None) providers: list[type] = [] # Provider classes imports: list[type] = [] # Required module imports exports: list[type] = [] # Public API types controllers: list[type] = [] # Web controllers is_global: bool = False # Exports visible to all modules scan: list[str] = [] # Package paths to scan2. Encapsulation & Exports
Section titled “2. Encapsulation & Exports”The exports list (or the exports argument in dynamic modules) is the most critical part of a module. It defines which services are visible to other modules in the application.
- Internal Services: Any service registered within a module but NOT exported is considered “private” and cannot be injected into components outside of that module.
- Protocol Exports: It is best practice to export Protocols rather than implementation classes to maintain decoupling.
graph LR
subgraph AuthModule
AuthServiceImpl["AuthService (impl)"]
AuthProtocol["AuthServiceProtocol (exported)"]
end
subgraph BillingModule
BillingService["BillingService"]
end
BillingService --> AuthProtocol
Global Modules
Section titled “Global Modules”A @global_module’s exports are visible to all modules without explicit import:
from oridecon.di.module import global_module, Module
@global_moduleclass LoggingModule(Module): providers = [LoggingProvider] exports = [LoggerProtocol]@global_module is @module(is_global=True). You do not also set is_global on the class.
3. Dynamic Modules
Section titled “3. Dynamic Modules”Sometimes you need to configure a module dynamically before adding it to the application. Use the Module.configure() pattern for this.
from oridecon.di.module import module, Module, DynamicModule
@module()class DatabaseModule(Module):
@classmethod def configure(cls, url: str) -> DynamicModule: return DynamicModule( module=cls, providers=[DatabaseProvider(url=url)], exports=[DatabaseSession, TransactionManager], is_global=True, # Visible to all modules )Usage in Application
Section titled “Usage in Application”from oridecon import Application
app = Application(name="my-app")app.add_module(DatabaseModule.configure("postgresql://localhost/mydb"))4. Module Factory Methods
Section titled “4. Module Factory Methods”The Module base class provides three factory methods:
| Method | Purpose |
|---|---|
configure(*args, **kwargs) | Global configuration — called once at the app root |
scope(*providers, **kwargs) | Register additional providers in a per-feature scope |
stub(config=None) | Return a test-mode module with in-memory/noop backends |
Scope Method
Section titled “Scope Method”Module.scope() is already implemented on the base class. Use it to register extra providers without a second configure() (which would duplicate the module):
app.add_module(DatabaseModule.configure("postgresql://localhost/mydb"))app.add_module(DatabaseModule.scope(UserRepository, OrderRepository))Stub Method
Section titled “Stub Method”@module()class CacheModule(Module): @classmethod def stub(cls, config: Any = None) -> DynamicModule: return DynamicModule( module=cls, providers=[FakeCacheProvider()], exports=[CacheBackend], )5. Module Lifecycle Hooks
Section titled “5. Module Lifecycle Hooks”@module()class DataModule(Module):
@classmethod async def on_module_booted(cls) -> None: """Called after all providers in this module have been booted.
Override in subclasses to run post-boot initialization logic. The container is fully available at this point. """ pass
@classmethod async def on_module_shutdown(cls) -> None: """Called before this module's providers are shut down.
Override in subclasses to run pre-shutdown cleanup logic. """ pass6. Module Registry
Section titled “6. Module Registry”The application maintains a global ModuleRegistry which tracks all loaded modules and their inter-dependencies. This ensures that:
- Circular dependencies are caught early
- Modules are initialized in the correct order
- Module visibility rules are enforced
Visibility Enforcement
Section titled “Visibility Enforcement”If a module tries to inject a service that is not in its imports’ exports (and is not global), Oridecon raises ModuleVisibilityError:
'TokenService' is not visible in 'BillingModule'
Requested by: BillingProvider Exported by: AuthModule BillingModule currently imports: [] Exported services available there: [AuthServiceProtocol]
To fix: Fix one of: 1. Add the exporting module to BillingModule.imports 2. Make the exporting module global with @module(is_global=True) 3. Depend on a service that is already exported7. Complete Example
Section titled “7. Complete Example”from oridecon.di.module import module, Module
from my_app.modules.auth.provider import AuthProviderfrom my_app.modules.auth.protocols import AuthServiceProtocol
@module( providers=[AuthProvider], exports=[AuthServiceProtocol], # Only the protocol is public)class AuthModule(Module): """Authentication — only AuthServiceProtocol is visible to importers."""oridecon new module auth writes AuthModule. List it in create_app() next to WebModule — see Project Structure.
from oridecon import Application, OrideconConfigfrom oridecon.web import WebModule
from my_app.modules.auth import AuthModule
def create_app(config: OrideconConfig | None = None) -> Application: application = Application(name="my-app", config=config) application.add_modules( [ AuthModule, WebModule.configure(discover=["my_app.controllers", "my_app.modules"]), ] ) return application
app = create_app()Start unscoped. Add a module when a feature needs a private interior and a public protocol — that is growth, not a different project type.
oridecon new module authoridecon gen controller users --module authNext Steps
Section titled “Next Steps”- Project Structure — where module files land
- Providers — the provider inside the module
- Core Concepts — modules vs unscoped app root
- Examples —
auth-rbacfor a living boundary