Module Overview - InjectQ Documentation
Author: InjectQ Contributors
Description: Modern Python dependency injection library

Skip to content

# Modules & Providers

Organize your dependency injection configuration into reusable, composable units.

## What are Modules?

Modules are containers for related bindings that group services together.

    from injectq import InjectQ
    from injectq.modules import Module

    class DatabaseModule(Module):
        def configure(self, binder):
            binder.bind(Database, PostgresDatabase)
            binder.bind(UserRepository, UserRepositoryImpl)

    container = InjectQ.get_instance()
    container.install_module(DatabaseModule())

    # Services are now available
    repo = container[UserRepository]

## Module Types

### 1. Simple Module

Basic module for straightforward bindings:

    from injectq.modules import SimpleModule

    module = SimpleModule() \
        .bind(Database, PostgresDatabase) \
        .bind(Cache, RedisCache) \
        .bind_instance("api_key", "secret-123")

    container.install_module(module)

### 2. Configuration Module

Bind configuration values:

    from injectq.modules import ConfigurationModule

    config = {
        "db_host": "localhost",
        "db_port": 5432,
        "api_key": "secret-key"
    }

    container.install_module(ConfigurationModule(config))

    # Access configuration
    host = container["db_host"]
    port = container["db_port"]

### 3. Provider Module

Most powerful - use `@provider` methods for complex initialization:

    from injectq.modules import ProviderModule, provider

    class AppModule(ProviderModule):
        @provider
        def provide_database_config(
            self, db_host: str, db_port: int, db_name: str
        ) -> DatabaseConfig:
            """Provider parameters are auto-injected"""
            return DatabaseConfig(
                host=db_host,
                port=db_port,
                database=db_name
            )

        @provider
        def provide_database(self, config: DatabaseConfig) -> Database:
            """Compose dependencies with initialization logic"""
            db = Database(config)
            db.connect()  # Custom initialization
            return db

    # Bind config values
    container.bind_instance("db_host", "localhost")
    container.bind_instance("db_port", 5432)
    container.bind_instance("db_name", "myapp")

    # Install module
    container.install_module(AppModule())

    # Get fully initialized database
    db = container[Database]

## Provider Pattern (Recommended)

The @provider pattern is the most powerful way to create complex dependencies.

### Basic Provider

    class ServiceModule(ProviderModule):
        @provider
        def provide_logger(self, app_name: str, log_level: str) -> Logger:
            """Return type annotation determines what this provides"""
            return Logger(name=app_name, level=log_level)

    # Bind parameters
    container.bind_instance("app_name", "MyApp")
    container.bind_instance("log_level", "INFO")

    # Install and use
    container.install_module(ServiceModule())
    logger = container[Logger]

### Provider with Dependencies

    class UserModule(ProviderModule):
        @provider
        def provide_user_service(
            self, database: Database, cache: Cache, logger: Logger
        ) -> UserService:
            """Dependencies are auto-injected"""
            return UserService(db=database, cache=cache, logger=logger)

### Provider with Initialization

    class DatabaseModule(ProviderModule):
        @provider
        def provide_database(self, config: DatabaseConfig) -> Database:
            """Perform complex initialization"""
            db = Database(config)
            db.connect()
            db.setup_tables()
            db.run_migrations()
            return db

### Environment-Specific Providers

    class EnvironmentModule(ProviderModule):
        def __init__(self, environment: str):
            self.environment = environment

        @provider
        def provide_database_config(self) -> DatabaseConfig:
            """Use module state for environment-specific logic"""
            if self.environment == "production":
                return DatabaseConfig(
                    host="prod-db.example.com",
                    port=5432,
                    ssl=True
                )
            else:  # development
                return DatabaseConfig(
                    host="localhost",
                    port=5432,
                    ssl=False
                )

    # Usage
    container.install_module(EnvironmentModule("production"))

## Complete Example

    from injectq import InjectQ
    from injectq.modules import ProviderModule, provider

    # Domain models
    class DatabaseConfig:
        def __init__(self, host: str, port: int):
            self.host = host
            self.port = port

    class Database:
        def __init__(self, config: DatabaseConfig):
            self.config = config
            self.connected = False

        def connect(self):
            self.connected = True
            print(f"Connected to {self.config.host}")

    class UserService:
        def __init__(self, db: Database, logger: Logger):
            self.db = db
            self.logger = logger

    # Provider module
    class ApplicationModule(ProviderModule):
        @provider
        def provide_database_config(
            self, db_host: str, db_port: int
        ) -> DatabaseConfig:
            return DatabaseConfig(host=db_host, port=db_port)

        @provider
        def provide_database(self, config: DatabaseConfig) -> Database:
            db = Database(config)
            db.connect()  # Initialize
            return db

        @provider
        def provide_logger(self, log_level: str) -> Logger:
            return Logger(level=log_level)

        @provider
        def provide_user_service(
            self, db: Database, logger: Logger
        ) -> UserService:
            return UserService(db=db, logger=logger)

    # Setup
    container = InjectQ.get_instance()

    # Bind configuration
    container.bind_instance("db_host", "localhost")
    container.bind_instance("db_port", 5432)
    container.bind_instance("log_level", "INFO")

    # Install module
    container.install_module(ApplicationModule())

    # Use services
    user_service = container[UserService]
    # Database is connected, logger is configured

## Module Patterns

### Domain Modules

    class UserModule(ProviderModule):
        @provider
        def provide_user_repository(self, db: Database) -> UserRepository:
            return UserRepository(db)

        @provider
        def provide_user_service(self, repo: UserRepository) -> UserService:
            return UserService(repo)

    class OrderModule(ProviderModule):
        @provider
        def provide_order_repository(self, db: Database) -> OrderRepository:
            return OrderRepository(db)

        @provider
        def provide_order_service(self, repo: OrderRepository) -> OrderService:
            return OrderService(repo)

### Infrastructure Module

    class InfrastructureModule(ProviderModule):
        @provider
        def provide_database(self, db_url: str) -> Database:
            return Database(db_url)

        @provider
        def provide_cache(self, redis_url: str) -> Cache:
            return RedisCache(redis_url)

        @provider
        def provide_queue(self, queue_url: str) -> Queue:
            return MessageQueue(queue_url)

### Testing Modules

    class TestModule(ProviderModule):
        @provider
        def provide_database(self) -> Database:
            return InMemoryDatabase()

        @provider
        def provide_cache(self) -> Cache:
            return MockCache()

    # Test setup
    def test_user_service():
        container = InjectQ.get_instance()
        container.install_module(TestModule())

        service = container[UserService]
        # Uses mocked dependencies

## Best Practices

### ✅ Use Providers for Complex Setup

    @provider
    def provide_database(self, config: DatabaseConfig) -> Database:
        db = Database(config)
        db.connect()
        db.run_migrations()
        return db

### ✅ One Module Per Domain

    class UserModule(ProviderModule): pass
    class OrderModule(ProviderModule): pass
    class PaymentModule(ProviderModule): pass

### ✅ Environment-Specific Modules

    class ProductionModule(ProviderModule):
        def __init__(self):
            self.environment = "production"

        @provider
        def provide_config(self) -> Config:
            return Config(ssl=True, debug=False)

### ✅ Type Annotations Required

    @provider
    def provide_database(self, config: DatabaseConfig) -> Database:
        """Return type annotation is required"""
        return Database(config)

### ❌ Avoid God Modules

    # ❌ Bad - everything in one module
    class EverythingModule(ProviderModule):
        # 50+ providers...

    # ✅ Good - focused modules
    class DatabaseModule(ProviderModule): pass
    class CacheModule(ProviderModule): pass
    class EmailModule(ProviderModule): pass

## Summary

- Simple Module: Fluent API for basic bindings
- Configuration Module: Bind config dictionaries
- Provider Module: Most powerful - complex initialization

Provider Pattern Benefits: - Auto-inject dependencies into provider methods - Custom initialization logic - Environment-specific configuration - Clean, organized code

Key Points: - Use `@provider` decorator on methods - Return type annotation determines what's provided - Parameters are auto-injected from container - Great for multi-step initialization

Next: FastAPI Integration | Testing