Day 19: Dependency Injection in Nexios
Dependency injection is a design pattern that helps manage dependencies between components in your application. Nexios provides a straightforward dependency injection system that makes it easy to write clean, maintainable code.
Core Concepts
Basic Dependencies
The most fundamental form of dependency injection in Nexios uses the Depend()
function to mark parameters that should be injected:
python
from nexios import NexiosApp, Depend
app = NexiosApp()
def get_settings():
return {"debug": True, "version": "1.0.0"}
@app.get("/config")
async def show_config(request, response, settings: dict = Depend(get_settings)):
return settings
Resource Management with Yield
For dependencies that need cleanup (like database connections), use yield
:
python
async def get_db_session():
session = Session()
try:
yield session
finally:
await session.close()
@app.post("/items")
async def create_item(request, response, session = Depend(get_db_session)):
await session.add(Item(...))
return {"status": "created"}
Sub-Dependencies
Dependencies can depend on other dependencies:
python
async def get_db_config():
return {"host": "localhost", "port": 5432}
async def get_db_connection(config: dict = Depend(get_db_config)):
return Database(**config)
@app.get("/users")
async def list_users(request, response, db = Depend(get_db_connection)):
return await db.query("SELECT * FROM users")
Class-Based Dependencies
You can use classes as dependencies through their __call__
method:
python
def get_token():
...
class AuthService:
def __init__(self, secret_key: str):
self.secret_key = secret_key
async def __call__(self, token: str = Depend(get_token)):
return await self.verify_token(token)
auth = AuthService(secret_key="my-secret")
@app.get("/protected")
async def protected_route(request, response, user = Depend(auth)):
return {"message": f"Welcome {user.name}"}
Context-Aware Dependencies
Dependencies can access request context:
python
async def get_user_agent(request, response):
return request.headers.get("User-Agent")
@app.get("/ua")
async def show_ua(request, response, ua: str = Depend(get_user_agent)):
return {"user_agent": ua}
Practice Exercise: Building an Authentication System
Let's build a simple authentication system using dependency injection:
python
from nexios import NexiosApp, Depend
from nexios.exceptions import HTTPException
app = NexiosApp()
# Simulated user database
users = {
"token123": {"id": 1, "name": "John Doe"}
}
async def get_current_user(request, response):
token = request.headers.get("Authorization")
if not token:
raise HTTPException(401, "Not authenticated")
user = users.get(token)
if not user:
raise HTTPException(401, "Invalid token")
return user
@app.get("/me")
async def get_profile(request, response, user = Depend(get_current_user)):
return {"user": user}
@app.get("/admin")
async def admin_only(request, response, user = Depend(get_current_user)):
if user["id"] != 1: # Simple admin check
raise HTTPException(403, "Not authorized")
return {"message": "Welcome admin!"}
Best Practices
- Resource Management: Always use
yield
for dependencies that need cleanup - Error Handling: Raise appropriate exceptions in dependencies
- Keep Dependencies Focused: Each dependency should have a single responsibility
- Use Type Hints: They improve code readability and IDE support
- Document Dependencies: Especially when they're shared across multiple handlers
Summary
- Nexios's dependency injection system is simple but powerful
- Use
Depend()
to mark parameters for injection - Dependencies can be functions or classes with
__call__
- Use
yield
for proper resource management - Dependencies can access request context and other dependencies
- Dependencies are great for code reuse and separation of concerns
Exercise
Create a logging system using dependency injection that:
- Logs request details
- Tracks request timing
- Handles different log levels based on configuration
python
from nexios import NexiosApp, Depend
import time
import logging
app = NexiosApp()
class LoggerService:
def __init__(self):
self.logger = logging.getLogger("nexios")
self.logger.setLevel(logging.INFO)
async def __call__(self, request, response):
start_time = time.time()
log_data = {
"method": request.method,
"path": request.url.path,
"start_time": start_time
}
return self.logger, log_data
logger_service = LoggerService()
@app.get("/hello")
async def hello(
request,
response,
log_info = Depend(logger_service)
):
logger, log_data = log_info
logger.info(f"Processing request to {log_data['path']}")
# Simulate some work
time.sleep(0.1)
# Log completion
duration = time.time() - log_data["start_time"]
logger.info(f"Request completed in {duration:.2f}s")
return {"message": "Hello World!"}
This exercise demonstrates:
- Class-based dependencies
- Request context access
- Resource tracking
- Practical logging implementation