Migration from Kink¶
Kink is a dependency injection framework for Python. This guide helps you migrate from Kink to InjectQ while preserving your existing architecture.
๐ Core Differences¶
Container Creation¶
# โ Kink
import kink
# Kink uses a global container by default
container = kink.Container()
# โ
InjectQ
from injectq import InjectQ
# InjectQ encourages explicit container management
container = InjectQ()
Service Registration¶
# โ Kink
from kink import Container, inject
container = Container()
# Service registration in Kink
@container.register
class DatabaseService:
pass
# Factory registration
@container.register
def create_api_client() -> ApiClient:
return ApiClient("production")
# โ
InjectQ
from injectq import InjectQ
container = InjectQ()
# Service registration in InjectQ
container.bind(DatabaseService, DatabaseService)
# Factory registration
container.bind(ApiClient, lambda: ApiClient("production"))
Dependency Injection¶
# โ Kink
from kink import inject
class UserService:
@inject
def __init__(self, db: DatabaseService, api: ApiClient):
self.db = db
self.api = api
# โ
InjectQ
from injectq import inject
class UserService:
@inject
def __init__(self, db: DatabaseService, api: ApiClient):
self.db = db
self.api = api
# Alternative: Manual resolution
class UserService:
def __init__(self, db: DatabaseService, api: ApiClient):
self.db = db
self.api = api
# Register with dependencies
container.bind(UserService, UserService)
๐ Migration Checklist¶
Step 1: Replace Container Import¶
# Before: Kink imports
from kink import Container, inject
# After: InjectQ imports
from injectq import InjectQ, inject
Step 2: Convert Service Registration¶
# Before: Kink service registration
@container.register
class MyService:
def __init__(self, dependency: SomeDependency):
self.dependency = dependency
# After: InjectQ service registration
class MyService:
def __init__(self, dependency: SomeDependency):
self.dependency = dependency
container.bind(MyService, MyService)
# Or with explicit dependencies
container.bind(MyService, lambda dep=container.get(SomeDependency): MyService(dep))
Step 3: Update Factory Methods¶
# Before: Kink factory
@container.register
def create_database_connection() -> DatabaseConnection:
return DatabaseConnection(host="localhost", port=5432)
# After: InjectQ factory
def create_database_connection() -> DatabaseConnection:
return DatabaseConnection(host="localhost", port=5432)
container.bind(DatabaseConnection, create_database_connection)
Step 4: Convert Conditional Registration¶
# Before: Kink conditional registration
if environment == "production":
@container.register
def get_api_client() -> ApiClient:
return ProductionApiClient()
else:
@container.register
def get_api_client() -> ApiClient:
return TestApiClient()
# After: InjectQ conditional registration
if environment == "production":
container.bind(ApiClient, ProductionApiClient)
else:
container.bind(ApiClient, TestApiClient)
๐ง Migration Examples¶
Complete Kink Application¶
# โ Original Kink Application
from kink import Container, inject
import kink
# Setup container
container = Container()
# Register services
@container.register
class DatabaseConnection:
def __init__(self):
self.connection = "database://localhost:5432"
@container.register
class UserRepository:
@inject
def __init__(self, db: DatabaseConnection):
self.db = db
def find_user(self, user_id: str):
return {"id": user_id, "name": "John Doe"}
@container.register
class UserService:
@inject
def __init__(self, repo: UserRepository):
self.repo = repo
def get_user(self, user_id: str):
return self.repo.find_user(user_id)
# Usage
kink.set_default_container(container)
user_service = container[UserService]
user = user_service.get_user("123")
Migrated InjectQ Application¶
# โ
Migrated InjectQ Application
from injectq import InjectQ, inject
# Setup container
container = InjectQ()
# Register services
class DatabaseConnection:
def __init__(self):
self.connection = "database://localhost:5432"
class UserRepository:
@inject
def __init__(self, db: DatabaseConnection):
self.db = db
def find_user(self, user_id: str):
return {"id": user_id, "name": "John Doe"}
class UserService:
@inject
def __init__(self, repo: UserRepository):
self.repo = repo
def get_user(self, user_id: str):
return self.repo.find_user(user_id)
# Bind services
container.bind(DatabaseConnection, DatabaseConnection)
container.bind(UserRepository, UserRepository)
container.bind(UserService, UserService)
# Usage
user_service = container.get(UserService)
user = user_service.get_user("123")
๐ฏ Advanced Migration Patterns¶
Scope Mapping¶
# โ Kink singletons
@container.register
class SingletonService:
pass
# Kink doesn't have explicit scope management
# Services are singletons by default
# โ
InjectQ explicit scopes
class SingletonService:
pass
# Explicit singleton scope
container.bind(SingletonService, SingletonService).singleton()
# Or scoped/transient as needed
container.bind(TransientService, TransientService).transient()
container.bind(ScopedService, ScopedService).scoped()
Factory Pattern Migration¶
# โ Kink factory pattern
@container.register
def database_factory() -> DatabaseConnection:
config = container[DatabaseConfig]
return DatabaseConnection(config.connection_string)
# โ
InjectQ factory pattern
def database_factory() -> DatabaseConnection:
config = container.get(DatabaseConfig)
return DatabaseConnection(config.connection_string)
container.bind(DatabaseConnection, database_factory).singleton()
# Or with lambda
container.bind(
DatabaseConnection,
lambda: DatabaseConnection(container.get(DatabaseConfig).connection_string)
).singleton()
Interface Implementation¶
# โ Kink interface implementation
from abc import ABC, abstractmethod
class IUserRepository(ABC):
@abstractmethod
def find_user(self, user_id: str):
pass
@container.register
class SqlUserRepository(IUserRepository):
def find_user(self, user_id: str):
return {"id": user_id, "source": "sql"}
# Kink doesn't have explicit interface binding
# โ
InjectQ interface implementation
from abc import ABC, abstractmethod
class IUserRepository(ABC):
@abstractmethod
def find_user(self, user_id: str):
pass
class SqlUserRepository(IUserRepository):
def find_user(self, user_id: str):
return {"id": user_id, "source": "sql"}
# Explicit interface binding
container.bind(IUserRepository, SqlUserRepository)
# Or multiple implementations
container.bind(IUserRepository, SqlUserRepository, name="sql")
container.bind(IUserRepository, MongoUserRepository, name="mongo")
๐งช Testing Migration¶
Kink Testing¶
# โ Kink testing approach
import unittest
from kink import Container
class TestUserService(unittest.TestCase):
def setUp(self):
self.container = Container()
# Mock dependencies
@self.container.register
class MockDatabase:
def get_connection(self):
return "mock_connection"
@self.container.register
class UserService:
@inject
def __init__(self, db: MockDatabase):
self.db = db
def test_user_service(self):
service = self.container[UserService]
# Test service...
InjectQ Testing¶
# โ
InjectQ testing approach
import unittest
from injectq import InjectQ, inject
class TestUserService(unittest.TestCase):
def setUp(self):
self.container = InjectQ()
# Mock dependencies
class MockDatabase:
def get_connection(self):
return "mock_connection"
class UserService:
@inject
def __init__(self, db: MockDatabase):
self.db = db
# Bind mocks
self.container.bind(MockDatabase, MockDatabase)
self.container.bind(UserService, UserService)
def test_user_service(self):
service = self.container.get(UserService)
# Test service...
def test_with_overrides(self):
# InjectQ supports test overrides
with self.container.override(MockDatabase, SpecialMockDatabase()):
service = self.container.get(UserService)
# Test with special mock...
๐ Configuration Migration¶
Kink Configuration¶
# โ Kink configuration pattern
from kink import Container
def setup_production_container():
container = Container()
@container.register
def get_database_config() -> DatabaseConfig:
return DatabaseConfig(
host="prod-db.example.com",
port=5432,
database="production"
)
@container.register
def get_api_config() -> ApiConfig:
return ApiConfig(
endpoint="https://api.example.com",
timeout=30
)
return container
def setup_test_container():
container = Container()
@container.register
def get_database_config() -> DatabaseConfig:
return DatabaseConfig(
host="localhost",
port=5433,
database="test"
)
@container.register
def get_api_config() -> ApiConfig:
return ApiConfig(
endpoint="http://localhost:8080",
timeout=5
)
return container
InjectQ Configuration¶
# โ
InjectQ configuration pattern
from injectq import InjectQ
def setup_production_container():
container = InjectQ()
# Configuration objects
db_config = DatabaseConfig(
host="prod-db.example.com",
port=5432,
database="production"
)
api_config = ApiConfig(
endpoint="https://api.example.com",
timeout=30
)
# Bind configurations
container.bind(DatabaseConfig, db_config)
container.bind(ApiConfig, api_config)
# Bind services
container.bind(DatabaseConnection, DatabaseConnection)
container.bind(ApiClient, ApiClient)
return container
def setup_test_container():
container = InjectQ()
# Test configurations
db_config = DatabaseConfig(
host="localhost",
port=5433,
database="test"
)
api_config = ApiConfig(
endpoint="http://localhost:8080",
timeout=5
)
# Bind configurations
container.bind(DatabaseConfig, db_config)
container.bind(ApiConfig, api_config)
# Bind services
container.bind(DatabaseConnection, DatabaseConnection)
container.bind(ApiClient, ApiClient)
return container
# Or use modules for better organization
from injectq import Module
class ProductionModule(Module):
def configure(self):
self.bind(DatabaseConfig, DatabaseConfig(
host="prod-db.example.com",
port=5432,
database="production"
))
self.bind(ApiConfig, ApiConfig(
endpoint="https://api.example.com",
timeout=30
))
class TestModule(Module):
def configure(self):
self.bind(DatabaseConfig, DatabaseConfig(
host="localhost",
port=5433,
database="test"
))
self.bind(ApiConfig, ApiConfig(
endpoint="http://localhost:8080",
timeout=5
))
# Usage
container = InjectQ()
if environment == "production":
container.install(ProductionModule())
else:
container.install(TestModule())
โก Performance Considerations¶
Memory Usage¶
# Kink keeps global references
# InjectQ allows explicit container management
# Better memory management with InjectQ
class ApplicationContainer:
def __init__(self, environment: str):
self.container = InjectQ()
self.environment = environment
self._setup_container()
def _setup_container(self):
if self.environment == "production":
self._setup_production_services()
else:
self._setup_test_services()
def _setup_production_services(self):
# Setup production services
self.container.bind(DatabaseConnection, DatabaseConnection).singleton()
self.container.bind(CacheClient, RedisClient).singleton()
def _setup_test_services(self):
# Setup test services
self.container.bind(DatabaseConnection, MockDatabase).singleton()
self.container.bind(CacheClient, InMemoryCache).singleton()
def get_service(self, service_type):
return self.container.get(service_type)
def cleanup(self):
# Explicit cleanup
self.container.dispose()
# Usage
app_container = ApplicationContainer("production")
try:
user_service = app_container.get_service(UserService)
# Use service...
finally:
app_container.cleanup()
๐ฏ Migration Summary¶
Key Changes¶
- Container Management: Explicit container creation instead of global container
- Service Registration: Use
container.bind()instead of@container.register - Dependency Resolution: Same
@injectdecorator or explicit resolution - Scope Management: Explicit scope configuration (singleton, scoped, transient)
- Testing: Enhanced testing utilities and override capabilities
- Configuration: Better organization with modules and providers
Benefits of Migration¶
- Explicit Dependencies: Better control over container lifecycle
- Enhanced Testing: Comprehensive testing utilities and mocking support
- Performance: Better memory management and optimization features
- Flexibility: Multiple container support and advanced features
- Type Safety: Better type annotation support
- Documentation: Comprehensive documentation and examples
Migration Tips¶
- Gradual Migration: Migrate one module at a time
- Testing First: Start with test code migration
- Container Isolation: Use separate containers for different parts of your application
- Use Modules: Organize bindings with InjectQ modules
- Leverage Scopes: Use appropriate scopes for better performance
- Monitor Performance: Use InjectQ's profiling tools to optimize
Ready to explore migration from python-injector?