Dict-like Interface¶
InjectQ's dict-like interface provides the simplest way to get started with dependency injection. It treats the container like a dictionary where you can store and retrieve services using keys.
๐ฏ Basic Usage¶
The dict-like interface is perfect for simple applications and getting started quickly:
from injectq import InjectQ
# Create container
container = InjectQ.get_instance()
# Store services like a dictionary
container[str] = "Hello, InjectQ!"
container[int] = 42
container["database_url"] = "postgresql://localhost/db"
# Retrieve services
message = container[str] # "Hello, InjectQ!"
number = container[int] # 42
db_url = container["database_url"] # "postgresql://localhost/db"
๐๏ธ Class Registration¶
Register classes for automatic instantiation:
class DatabaseConfig:
def __init__(self, host: str = "localhost", port: int = 5432):
self.host = host
self.port = port
self.url = f"postgresql://{host}:{port}/mydb"
class Database:
def __init__(self, config: DatabaseConfig):
self.config = config
print(f"Connected to: {config.url}")
class UserRepository:
def __init__(self, db: Database):
self.db = db
# Register classes
container[DatabaseConfig] = DatabaseConfig()
container[Database] = Database
container[UserRepository] = UserRepository
# Automatic dependency resolution
repo = container[UserRepository] # Creates DatabaseConfig, Database, then UserRepository
๐ Key Operations¶
Setting Values¶
# Simple values
container[str] = "configuration"
container[int] = 12345
container[bool] = True
# Complex objects
container["config"] = AppConfig(host="prod", debug=False)
# Classes (for automatic instantiation)
container[Database] = Database
container[UserService] = UserService
# Instances (pre-created objects)
container["cache"] = RedisCache(host="localhost")
Getting Values¶
# Simple retrieval
config = container[str]
number = container[int]
# With type hints (better IDE support)
config: str = container[str]
service: UserService = container[UserService]
Checking Existence¶
# Check if a service is registered
if str in container:
config = container[str]
if "database" in container:
db = container["database"]
Removing Services¶
# Remove a service
del container[str]
del container[Database]
# Check removal
assert str not in container
assert Database not in container
๐จ Advanced Patterns¶
Factory Functions¶
Use lambda functions for dynamic creation:
import uuid
from datetime import datetime
# Factory for unique IDs
container["request_id"] = lambda: str(uuid.uuid4())
# Factory for timestamps
container["timestamp"] = lambda: datetime.now().isoformat()
# Each access creates a new value
id1 = container["request_id"]
id2 = container["request_id"]
print(f"IDs are different: {id1 != id2}") # True
Conditional Registration¶
Register services based on environment:
if environment == "production":
container[Database] = PostgreSQLDatabase
container["cache"] = RedisCache(host="prod-redis")
elif environment == "testing":
container[Database] = SQLiteDatabase
container["cache"] = MemoryCache()
else:
container[Database] = InMemoryDatabase
container["cache"] = MemoryCache()
Named Services¶
Use strings as keys for multiple implementations:
# Multiple cache implementations
container["redis_cache"] = RedisCache(host="localhost")
container["memory_cache"] = MemoryCache()
container["file_cache"] = FileCache(path="/tmp/cache")
# Usage
cache = container["redis_cache"]
backup_cache = container["memory_cache"]
๐ง Integration with Decorators¶
The dict-like interface works seamlessly with InjectQ's decorators:
from injectq import inject, singleton
# Register services
container[Database] = Database
container["config"] = AppConfig()
# Use with @inject
@inject
def process_data(db: Database, config: dict) -> None:
# db and config automatically injected
print(f"Processing with config: {config}")
# Call without parameters
process_data()
๐งช Testing with Dict Interface¶
The dict-like interface makes testing easy:
from injectq.testing import test_container
def test_user_service():
with test_container() as container:
# Set up test dependencies
container[Database] = MockDatabase()
container["config"] = {"test": True}
# Get service under test
service = container[UserService]
# Test the service
result = service.get_user(1)
assert result is not None
๐ Real-World Example¶
Here's a complete example using the dict-like interface:
from injectq import InjectQ
from typing import List, Optional
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
email: str
class UserRepository:
def __init__(self, db_url: str):
self.db_url = db_url
self.users = {} # In-memory storage
def save(self, user: User) -> User:
self.users[user.id] = user
return user
def find_by_id(self, user_id: int) -> Optional[User]:
return self.users.get(user_id)
def find_all(self) -> List[User]:
return list(self.users.values())
class UserService:
def __init__(self, repo: UserRepository, cache_timeout: int):
self.repo = repo
self.cache_timeout = cache_timeout
def create_user(self, name: str, email: str) -> User:
user_id = len(self.repo.users) + 1
user = User(id=user_id, name=name, email=email)
return self.repo.save(user)
def get_user(self, user_id: int) -> Optional[User]:
return self.repo.find_by_id(user_id)
# Application setup
container = InjectQ.get_instance()
# Configuration
container[str] = "postgresql://localhost:5432/myapp" # Database URL
container[int] = 300 # Cache timeout in seconds
# Services
container[UserRepository] = UserRepository
container[UserService] = UserService
# Usage
service = container[UserService]
# Create users
user1 = service.create_user("John Doe", "john@example.com")
user2 = service.create_user("Jane Smith", "jane@example.com")
# Retrieve users
found_user = service.get_user(1)
print(f"Found user: {found_user}")
# List all users
all_users = container[UserRepository].find_all()
print(f"All users: {all_users}")
โ๏ธ When to Use Dict Interface¶
โ Good For¶
- Simple applications - Quick setup without complex configuration
- Configuration values - Storing strings, numbers, settings
- Prototyping - Fast iteration and testing
- Small projects - When you don't need advanced features
- Learning DI - Easiest way to understand the concepts
โ Not Ideal For¶
- Large applications - Can become messy with many services
- Complex dependencies - Hard to manage intricate dependency graphs
- Type safety - Less type-safe than other approaches
- Advanced scoping - Limited lifetime management
- Team development - Less explicit about dependencies
๐ Migration Path¶
You can start with the dict interface and migrate to more advanced patterns:
# Phase 1: Simple dict interface
container = InjectQ()
container[Database] = Database
container[UserService] = UserService
# Phase 2: Add modules for organization
class DatabaseModule(Module):
def configure(self, binder):
binder.bind(Database, Database)
container = InjectQ([DatabaseModule()])
# Phase 3: Add type safety with protocols
class IDatabase(Protocol):
def connect(self) -> None: ...
container.bind(IDatabase, PostgreSQLDatabase)
๐ Best Practices¶
1. Use Descriptive Keys¶
# โ
Good - descriptive keys
container["database_url"] = "postgresql://..."
container["redis_host"] = "localhost"
container["api_timeout"] = 30
# โ Avoid - unclear keys
container["url"] = "postgresql://..."
container["host"] = "localhost"
container["num"] = 30
2. Group Related Configuration¶
# โ
Good - grouped configuration
container["database"] = {
"host": "localhost",
"port": 5432,
"name": "myapp"
}
container["cache"] = {
"host": "redis",
"ttl": 3600
}
# โ Avoid - scattered configuration
container["db_host"] = "localhost"
container["db_port"] = 5432
container["cache_host"] = "redis"
3. Use Factories for Dynamic Values¶
# โ
Good - factories for dynamic values
container["request_id"] = lambda: str(uuid.uuid4())
container["timestamp"] = lambda: datetime.now()
# โ Avoid - static values that should be dynamic
container["request_id"] = "static-id" # Same for all requests
4. Document Your Services¶
# โ
Good - documented services
container["database"] = PostgreSQLDatabase() # Main application database
container["cache"] = RedisCache() # Redis cache for performance
container["logger"] = StructuredLogger() # JSON structured logging
๐ฏ Summary¶
The dict-like interface is:
- Simple - Easy to understand and use
- Flexible - Store any type of value or service
- Fast - Quick setup for small projects
- Intuitive - Familiar dictionary-like API
Key features: - Store simple values, objects, classes, or factories - Automatic dependency resolution for registered classes - Easy testing with dependency overrides - Seamless integration with other InjectQ features
When to use: - Learning dependency injection - Small to medium applications - Prototyping and experimentation - Simple configuration management
Ready to explore the @inject decorator?