Parameterized Factories¶
InjectQ supports two types of factory functions:
- Dependency Injection (DI) Factories - Factories whose parameters are automatically resolved from the container
- Parameterized Factories - Factories that accept custom arguments passed by the caller
This flexibility allows you to choose the right pattern for your use case.
📋 Table of Contents¶
- DI Factories (Auto-Resolution)
- Parameterized Factories (Manual Arguments)
- API Methods
- Use Cases
- Best Practices
DI Factories (Auto-Resolution)¶
These are the traditional factory functions where InjectQ automatically resolves all parameters from the container.
Basic Example¶
from injectq import InjectQ
from datetime import datetime
container = InjectQ()
# Factory with no parameters
container.bind_factory("timestamp", lambda: datetime.now().isoformat())
# Get the value - factory is automatically invoked
timestamp = container.get("timestamp")
print(timestamp) # "2024-01-01T12:00:00.000000"
With Dependencies¶
class Database:
def query(self, sql: str) -> list:
return [{"id": 1, "name": "Alice"}]
class Cache:
def get(self, key: str) -> Any:
return None
# Factory that depends on Database and Cache
def create_user_service(db: Database, cache: Cache) -> UserService:
"""Factory with DI - parameters resolved automatically."""
return UserService(db, cache)
container.bind(Database, Database).singleton()
container.bind(Cache, Cache).singleton()
container.bind_factory(UserService, create_user_service)
# Dependencies are automatically injected
service = container.get(UserService)
Parameterized Factories (Manual Arguments)¶
When you need to pass custom arguments to a factory function at call time, use parameterized factories.
Basic Example¶
from injectq import InjectQ
container = InjectQ()
# Data store
data = {
"user:1": {"name": "Alice", "age": 30},
"user:2": {"name": "Bob", "age": 25},
"user:3": {"name": "Charlie", "age": 35},
}
# Bind a parameterized factory
container.bind_factory("data_store", lambda key: data.get(key))
# Method 1: Get factory then call
factory = container.get_factory("data_store")
user1 = factory("user:1")
print(user1) # {"name": "Alice", "age": 30}
# Method 2: Use call_factory shorthand
user2 = container.call_factory("data_store", "user:2")
print(user2) # {"name": "Bob", "age": 25}
# Method 3: Chain the calls
user3 = container.get_factory("data_store")("user:3")
print(user3) # {"name": "Charlie", "age": 35}
Multiple Parameters¶
def calculator(operation: str, a: float, b: float) -> float:
"""Factory that performs calculations."""
operations = {
"add": a + b,
"subtract": a - b,
"multiply": a * b,
"divide": a / b if b != 0 else 0,
}
return operations.get(operation, 0)
container.bind_factory("calculator", calculator)
# Use with different operations
result_add = container.call_factory("calculator", "add", 10, 5)
result_multiply = container.call_factory("calculator", "multiply", 10, 5)
print(f"10 + 5 = {result_add}") # 15
print(f"10 * 5 = {result_multiply}") # 50
Keyword Arguments¶
def create_config(env: str = "dev", debug: bool = False, port: int = 8000) -> dict:
"""Factory that creates configuration objects."""
return {
"environment": env,
"debug": debug,
"port": port,
"url": f"http://localhost:{port}",
}
container.bind_factory("config", create_config)
# Call with different configurations
dev_config = container.call_factory("config", env="dev", debug=True)
prod_config = container.call_factory("config", env="prod", debug=False, port=80)
print(dev_config) # {"environment": "dev", "debug": True, ...}
print(prod_config) # {"environment": "prod", "debug": False, ...}
API Methods¶
get_factory(service_type)¶
Returns the raw factory function without invoking it.
Signature:
def get_factory(self, service_type: ServiceKey) -> ServiceFactory:
"""Get the raw factory function without invoking it."""
Parameters:
- service_type: The service type or key for the factory
Returns: - The factory function
Raises:
- DependencyNotFoundError: If no factory is registered
Example:
container.bind_factory("data_store", lambda key: data[key])
# Get the factory function
factory = container.get_factory("data_store")
# Call it with your own arguments
result = factory("key1")
call_factory(service_type, *args, **kwargs)¶
Convenience method that gets and calls a factory in one step.
Signature:
def call_factory(self, service_type: ServiceKey, *args: Any, **kwargs: Any) -> Any:
"""Get and call a factory function with custom arguments."""
Parameters:
- service_type: The service type or key for the factory
- *args: Positional arguments to pass to the factory
- **kwargs: Keyword arguments to pass to the factory
Returns: - The result of calling the factory function
Raises:
- DependencyNotFoundError: If no factory is registered
Example:
container.bind_factory("calculator", lambda op, a, b: operations[op](a, b))
# Call with arguments directly
result = container.call_factory("calculator", "add", 10, 5)
Use Cases¶
1. Data Access Patterns¶
# Repository pattern with dynamic queries
class UserRepository:
def __init__(self, db: Database):
self.db = db
def find_by_id(self, user_id: int):
return self.db.query(f"SELECT * FROM users WHERE id = {user_id}")
container.bind(Database, Database).singleton()
# Bind as parameterized factory
container.bind_factory(
"user_repo_query",
lambda user_id: container.get(UserRepository).find_by_id(user_id)
)
# Use it
user = container.call_factory("user_repo_query", 123)
2. Configuration Management¶
# Environment-specific configurations
def create_db_config(env: str, pool_size: int = 10):
configs = {
"dev": {"host": "localhost", "port": 5432, "db": "dev_db"},
"prod": {"host": "prod-db.example.com", "port": 5432, "db": "prod_db"},
}
config = configs.get(env, configs["dev"])
config["pool_size"] = pool_size
return config
container.bind_factory("db_config", create_db_config)
# Get different configs
dev_config = container.call_factory("db_config", "dev")
prod_config = container.call_factory("db_config", "prod", pool_size=50)
3. Connection Pooling¶
class ConnectionPool:
def __init__(self, db_name: str, max_connections: int = 10):
self.db_name = db_name
self.max_connections = max_connections
container.bind_factory(
"db_pool",
lambda db_name, max_conn=10: ConnectionPool(db_name, max_conn)
)
# Create different pools for different databases
users_pool = container.call_factory("db_pool", "users_db", max_conn=20)
orders_pool = container.call_factory("db_pool", "orders_db", max_conn=15)
4. Dynamic Service Creation¶
# Service factory that creates services based on type
def create_notification_service(channel: str):
services = {
"email": EmailNotificationService(),
"sms": SMSNotificationService(),
"push": PushNotificationService(),
}
return services.get(channel)
container.bind_factory("notification", create_notification_service)
# Get different notification channels
email_service = container.call_factory("notification", "email")
sms_service = container.call_factory("notification", "sms")
Best Practices¶
✅ Do's¶
-
Use DI factories for services with dependencies
-
Use parameterized factories for dynamic data access
-
Combine both patterns when needed
-
Use descriptive factory names
❌ Don'ts¶
-
Don't use
get()for parameterized factories -
Don't mix DI and manual parameters in the same factory
# Confusing - some params from DI, others from caller def mixed_factory(db: Database, user_id: int): return db.get_user(user_id) # Better - separate concerns def di_factory(db: Database) -> UserRepository: return UserRepository(db) container.bind_factory("user_repo", di_factory) repo = container.get("user_repo") user = repo.get(123) # Separate the parameter passing -
Don't overuse parameterized factories
Comparison Table¶
| Feature | DI Factories | Parameterized Factories |
|---|---|---|
| Binding | bind_factory("service", factory) |
bind_factory("service", factory) |
| Resolution | get("service") |
call_factory("service", args) |
| Parameters | Auto-resolved from container | Passed by caller |
| Use Case | Services with dependencies | Dynamic data access |
| Caching | Respects scope (singleton, transient) | Always transient |
| Example | User service with DB/cache deps | Get user by ID |
Migration Guide¶
If you have existing code that tries to use parameterized factories with get():
Before:
# This doesn't work
container.bind_factory("data", lambda key: data[key])
value = container.get("data") # ERROR
After:
# Use get_factory or call_factory
container.bind_factory("data", lambda key: data[key])
# Option 1: Get factory then call
factory = container.get_factory("data")
value = factory("key1")
# Option 2: Use shorthand
value = container.call_factory("data", "key1")