Skip to content

Migration from python-injector

python-injector is a mature dependency injection framework. This guide helps you migrate from python-injector to InjectQ while maintaining your existing patterns.

๐Ÿ”„ Core Differences

Container and Injector

# โŒ python-injector
from injector import Injector, inject, singleton

injector = Injector()

# โœ… InjectQ
from injectq import InjectQ, inject

container = InjectQ()

Service Binding

# โŒ python-injector
from injector import Injector, inject, singleton, Module

class MyModule(Module):
    def configure(self, binder):
        binder.bind(DatabaseService, to=SqlDatabaseService, scope=singleton)
        binder.bind(str, to="Hello World", annotation="greeting")

injector = Injector([MyModule])

# โœ… InjectQ
from injectq import InjectQ, Module

class MyModule(Module):
    def configure(self):
        self.bind(DatabaseService, SqlDatabaseService).singleton()
        self.bind(str, "Hello World", name="greeting")

container = InjectQ()
container.install(MyModule())

Dependency Injection

# โŒ python-injector
from injector import inject

class UserService:
    @inject
    def __init__(self, db: DatabaseService):
        self.db = db

# Same in InjectQ
# โœ… InjectQ
from injectq import inject

class UserService:
    @inject
    def __init__(self, db: DatabaseService):
        self.db = db

๐Ÿ“‹ Migration Checklist

Step 1: Replace Imports

# Before: python-injector imports
from injector import Injector, inject, singleton, Module, provider

# After: InjectQ imports
from injectq import InjectQ, inject, Module
from injectq.decorators import singleton  # If needed

Step 2: Convert Injector to Container

# Before: python-injector
injector = Injector([MyModule])

# After: InjectQ
container = InjectQ()
container.install(MyModule())

Step 3: Update Module Configuration

# Before: python-injector module
from injector import Module, singleton, provider

class AppModule(Module):
    def configure(self, binder):
        binder.bind(DatabaseService, to=SqlDatabaseService, scope=singleton)
        binder.bind(ApiClient, to=self.create_api_client, scope=singleton)

    @provider
    @singleton
    def create_api_client(self) -> ApiClient:
        return ApiClient("production")

# After: InjectQ module
from injectq import Module

class AppModule(Module):
    def configure(self):
        self.bind(DatabaseService, SqlDatabaseService).singleton()
        self.bind(ApiClient, self.create_api_client).singleton()

    def create_api_client(self) -> ApiClient:
        return ApiClient("production")

Step 4: Convert Providers

# Before: python-injector provider
from injector import provider, singleton

class DatabaseModule(Module):
    @provider
    @singleton
    def provide_database(self, config: DatabaseConfig) -> DatabaseConnection:
        return DatabaseConnection(
            host=config.host,
            port=config.port,
            database=config.database
        )

# After: InjectQ provider
from injectq import Module

class DatabaseModule(Module):
    def configure(self):
        self.bind(DatabaseConnection, self.provide_database).singleton()

    def provide_database(self) -> DatabaseConnection:
        config = self.container.get(DatabaseConfig)
        return DatabaseConnection(
            host=config.host,
            port=config.port,
            database=config.database
        )

๐Ÿ”ง Migration Examples

Complete python-injector Application

# โŒ Original python-injector Application
from injector import Injector, inject, singleton, Module, provider
from abc import ABC, abstractmethod

# Interfaces
class IUserRepository(ABC):
    @abstractmethod
    def find_user(self, user_id: str):
        pass

class IEmailService(ABC):
    @abstractmethod
    def send_email(self, to: str, subject: str, body: str):
        pass

# Implementations
class SqlUserRepository(IUserRepository):
    @inject
    def __init__(self, db_connection: str):
        self.db_connection = db_connection

    def find_user(self, user_id: str):
        return {"id": user_id, "name": "John Doe"}

class SmtpEmailService(IEmailService):
    @inject
    def __init__(self, smtp_config: dict):
        self.smtp_config = smtp_config

    def send_email(self, to: str, subject: str, body: str):
        print(f"Sending email to {to}: {subject}")

class UserService:
    @inject
    def __init__(self, user_repo: IUserRepository, email_service: IEmailService):
        self.user_repo = user_repo
        self.email_service = email_service

    def register_user(self, user_data: dict):
        user = self.user_repo.find_user(user_data["id"])
        self.email_service.send_email(
            user_data["email"],
            "Welcome!",
            "Welcome to our service"
        )
        return user

# Module configuration
class AppModule(Module):
    def configure(self, binder):
        binder.bind(IUserRepository, to=SqlUserRepository, scope=singleton)
        binder.bind(IEmailService, to=SmtpEmailService, scope=singleton)
        binder.bind(UserService, scope=singleton)

    @provider
    @singleton
    def provide_db_connection(self) -> str:
        return "postgresql://localhost:5432/mydb"

    @provider
    @singleton
    def provide_smtp_config(self) -> dict:
        return {
            "host": "smtp.example.com",
            "port": 587,
            "username": "user@example.com",
            "password": "password"
        }

# Setup and usage
injector = Injector([AppModule])
user_service = injector.get(UserService)
result = user_service.register_user({
    "id": "123",
    "email": "user@example.com"
})

Migrated InjectQ Application

# โœ… Migrated InjectQ Application
from injectq import InjectQ, inject, Module
from abc import ABC, abstractmethod

# Interfaces (same)
class IUserRepository(ABC):
    @abstractmethod
    def find_user(self, user_id: str):
        pass

class IEmailService(ABC):
    @abstractmethod
    def send_email(self, to: str, subject: str, body: str):
        pass

# Implementations (same)
class SqlUserRepository(IUserRepository):
    @inject
    def __init__(self, db_connection: str):
        self.db_connection = db_connection

    def find_user(self, user_id: str):
        return {"id": user_id, "name": "John Doe"}

class SmtpEmailService(IEmailService):
    @inject
    def __init__(self, smtp_config: dict):
        self.smtp_config = smtp_config

    def send_email(self, to: str, subject: str, body: str):
        print(f"Sending email to {to}: {subject}")

class UserService:
    @inject
    def __init__(self, user_repo: IUserRepository, email_service: IEmailService):
        self.user_repo = user_repo
        self.email_service = email_service

    def register_user(self, user_data: dict):
        user = self.user_repo.find_user(user_data["id"])
        self.email_service.send_email(
            user_data["email"],
            "Welcome!",
            "Welcome to our service"
        )
        return user

# Module configuration (updated)
class AppModule(Module):
    def configure(self):
        self.bind(IUserRepository, SqlUserRepository).singleton()
        self.bind(IEmailService, SmtpEmailService).singleton()
        self.bind(UserService, UserService).singleton()

        # Provider bindings
        self.bind(str, self.provide_db_connection, name="db_connection").singleton()
        self.bind(dict, self.provide_smtp_config, name="smtp_config").singleton()

    def provide_db_connection(self) -> str:
        return "postgresql://localhost:5432/mydb"

    def provide_smtp_config(self) -> dict:
        return {
            "host": "smtp.example.com",
            "port": 587,
            "username": "user@example.com",
            "password": "password"
        }

# Setup and usage (updated)
container = InjectQ()
container.install(AppModule())
user_service = container.get(UserService)
result = user_service.register_user({
    "id": "123",
    "email": "user@example.com"
})

๐ŸŽฏ Advanced Migration Patterns

Scope Migration

# โŒ python-injector scopes
from injector import singleton, threadlocal

class DatabaseModule(Module):
    def configure(self, binder):
        binder.bind(DatabaseConnection, scope=singleton)
        binder.bind(RequestContext, scope=threadlocal)

# โœ… InjectQ scopes
class DatabaseModule(Module):
    def configure(self):
        self.bind(DatabaseConnection, DatabaseConnection).singleton()
        self.bind(RequestContext, RequestContext).scoped()  # or thread_local()

Provider Method Migration

# โŒ python-injector provider methods
from injector import provider, singleton

class ServiceModule(Module):
    @provider
    @singleton
    def provide_complex_service(self, dep1: Service1, dep2: Service2) -> ComplexService:
        return ComplexService(dep1, dep2, configuration="production")

# โœ… InjectQ provider methods
class ServiceModule(Module):
    def configure(self):
        self.bind(ComplexService, self.provide_complex_service).singleton()

    def provide_complex_service(self) -> ComplexService:
        dep1 = self.container.get(Service1)
        dep2 = self.container.get(Service2)
        return ComplexService(dep1, dep2, configuration="production")

# Alternative: lambda provider
class ServiceModule(Module):
    def configure(self):
        self.bind(ComplexService, lambda: ComplexService(
            self.container.get(Service1),
            self.container.get(Service2),
            configuration="production"
        )).singleton()

Multi-binding Migration

# โŒ python-injector multi-binding
from injector import multiprovider

class PluginModule(Module):
    @multiprovider
    @singleton
    def provide_plugins(self) -> List[Plugin]:
        return [
            DatabasePlugin(),
            CachePlugin(),
            LoggingPlugin()
        ]

# โœ… InjectQ multi-binding
from typing import List

class PluginModule(Module):
    def configure(self):
        # Individual plugin bindings
        self.bind(Plugin, DatabasePlugin(), name="database")
        self.bind(Plugin, CachePlugin(), name="cache")
        self.bind(Plugin, LoggingPlugin(), name="logging")

        # Aggregate binding
        self.bind(List[Plugin], self.provide_plugins).singleton()

    def provide_plugins(self) -> List[Plugin]:
        return [
            self.container.get(Plugin, name="database"),
            self.container.get(Plugin, name="cache"),
            self.container.get(Plugin, name="logging")
        ]

# Or use collection binding
class PluginModule(Module):
    def configure(self):
        plugins = [DatabasePlugin(), CachePlugin(), LoggingPlugin()]
        self.bind(List[Plugin], plugins).singleton()

Interface Implementation Migration

# โŒ python-injector abstract binding
from injector import InstanceProvider

class ServiceModule(Module):
    def configure(self, binder):
        # Abstract binding
        binder.bind(IEmailService, to=SmtpEmailService)

        # Instance binding
        binder.bind(str, to=InstanceProvider("production"), annotation="environment")

# โœ… InjectQ interface binding
class ServiceModule(Module):
    def configure(self):
        # Interface binding
        self.bind(IEmailService, SmtpEmailService)

        # Instance binding
        self.bind(str, "production", name="environment")

๐Ÿงช Testing Migration

python-injector Testing

# โŒ python-injector testing
import unittest
from injector import Injector, Module

class TestUserService(unittest.TestCase):
    def setUp(self):
        class TestModule(Module):
            def configure(self, binder):
                binder.bind(IUserRepository, to=MockUserRepository)
                binder.bind(IEmailService, to=MockEmailService)

        self.injector = Injector([TestModule])

    def test_register_user(self):
        user_service = self.injector.get(UserService)
        result = user_service.register_user({"id": "123", "email": "test@example.com"})
        self.assertIsNotNone(result)

InjectQ Testing

# โœ… InjectQ testing
import unittest
from injectq import InjectQ, Module

class TestUserService(unittest.TestCase):
    def setUp(self):
        class TestModule(Module):
            def configure(self):
                self.bind(IUserRepository, MockUserRepository)
                self.bind(IEmailService, MockEmailService)

        self.container = InjectQ()
        self.container.install(TestModule())

    def test_register_user(self):
        user_service = self.container.get(UserService)
        result = user_service.register_user({"id": "123", "email": "test@example.com"})
        self.assertIsNotNone(result)

    def test_with_override(self):
        # InjectQ supports runtime overrides
        special_mock = SpecialMockUserRepository()
        with self.container.override(IUserRepository, special_mock):
            user_service = self.container.get(UserService)
            # Test with special mock...

๐Ÿ”— Async Support Migration

python-injector Async

# โŒ python-injector (limited async support)
class AsyncService:
    @inject
    def __init__(self, db: DatabaseConnection):
        self.db = db

    async def process_data(self):
        # Async operations
        return await self.db.fetch_data()

# Manual async injection
async def main():
    injector = Injector([AppModule])
    service = injector.get(AsyncService)
    result = await service.process_data()

InjectQ Async

# โœ… InjectQ (comprehensive async support)
class AsyncService:
    @inject
    def __init__(self, db: DatabaseConnection):
        self.db = db

    async def process_data(self):
        return await self.db.fetch_data()

# Async container support
async def main():
    container = InjectQ()
    container.install(AppModule())

    # Async resolution
    service = await container.aget(AsyncService)
    result = await service.process_data()

    # Async context managers
    async with container.async_scope():
        scoped_service = container.get(ScopedAsyncService)
        await scoped_service.process()

๐Ÿ”ง Configuration Migration

Environment-based Configuration

# โŒ python-injector configuration
import os
from injector import Module, provider, singleton

class ConfigModule(Module):
    @provider
    @singleton
    def provide_database_config(self) -> DatabaseConfig:
        env = os.getenv("ENVIRONMENT", "development")
        if env == "production":
            return DatabaseConfig(
                host="prod-db.example.com",
                port=5432,
                database="production"
            )
        else:
            return DatabaseConfig(
                host="localhost",
                port=5433,
                database="test"
            )

# โœ… InjectQ configuration
import os
from injectq import Module

class ConfigModule(Module):
    def configure(self):
        env = os.getenv("ENVIRONMENT", "development")

        if env == "production":
            config = DatabaseConfig(
                host="prod-db.example.com",
                port=5432,
                database="production"
            )
        else:
            config = DatabaseConfig(
                host="localhost",
                port=5433,
                database="test"
            )

        self.bind(DatabaseConfig, config).singleton()

# Or use conditional modules
class ProductionModule(Module):
    def configure(self):
        self.bind(DatabaseConfig, DatabaseConfig(
            host="prod-db.example.com",
            port=5432,
            database="production"
        )).singleton()

class DevelopmentModule(Module):
    def configure(self):
        self.bind(DatabaseConfig, DatabaseConfig(
            host="localhost",
            port=5433,
            database="test"
        )).singleton()

# Usage
container = InjectQ()
env = os.getenv("ENVIRONMENT", "development")
if env == "production":
    container.install(ProductionModule())
else:
    container.install(DevelopmentModule())

โšก Performance Comparison

Memory Usage

# python-injector uses more reflection
# InjectQ optimizes for performance

# Performance monitoring with InjectQ
from injectq.profiling import PerformanceMonitor

monitor = PerformanceMonitor(container)

# Profile resolution performance
with monitor.profile_resolution(UserService) as profile:
    service = container.get(UserService)

results = profile.get_results()
print(f"Resolution time: {results.total_time}ms")
print(f"Memory usage: {results.memory_usage} bytes")

Startup Time

# Measure container setup time
import time

# InjectQ container setup
start_time = time.time()
container = InjectQ()
container.install(AppModule())
setup_time = (time.time() - start_time) * 1000

print(f"Container setup time: {setup_time}ms")

# Pre-compile for faster resolution
container.compile()  # Optional optimization

๐ŸŽฏ Migration Summary

Key Changes

  1. Injector โ†’ Container: Replace Injector with InjectQ
  2. Module.configure(): Remove binder parameter, use self.bind()
  3. Provider Methods: Access container via self.container instead of injection
  4. Scopes: Use method chaining (.singleton(), .scoped())
  5. Multi-binding: Use named bindings or collection bindings
  6. Testing: Enhanced override capabilities and test utilities

Benefits of Migration

  • Better Performance: Optimized resolution and memory usage
  • Async Support: Comprehensive async/await support
  • Type Safety: Better type checking and annotation support
  • Testing Tools: Enhanced testing utilities and mocking
  • Profiling: Built-in performance monitoring and profiling
  • Flexibility: More binding options and configuration patterns

Migration Tips

  1. Start Simple: Begin with basic service bindings
  2. Test Early: Migrate tests alongside production code
  3. Use Modules: Organize bindings with modules for better structure
  4. Profile Performance: Use InjectQ's profiling tools to optimize
  5. Leverage Async: Take advantage of async support where beneficial
  6. Monitor Memory: Use InjectQ's memory monitoring for optimization

Common Patterns

  • Replace @provider with method bindings in modules
  • Use named bindings instead of annotations
  • Leverage InjectQ's scope chaining syntax
  • Take advantage of enhanced testing utilities
  • Use async features for async applications

Ready to explore migration from other DI frameworks?