Mastering the Context Object Pattern in Python
Overview
Passing five or six arguments through every function layer creates a maintenance nightmare. This "parameter bloat" often signals that your functions lack a cohesive way to handle shared environmental data. The
Prerequisites
To follow this guide, you should understand:
- Python Type Hinting: Familiarity with
typingand class-based annotations. - Data Classes: Knowledge of the
@dataclassdecorator for creating concise data containers. - Basic SQL Alchemy: Understanding how database sessions and queries function.

Key Libraries & Tools
- Dataclasses: A standard Python library for generating boilerplate code in classes.
- Protocols: Part of the
typingmodule used for structural subtyping (duck typing). - SQLAlchemy: An ORM used here to manage database sessions.
- Logging: Python's built-in module for tracking runtime events.
Code Walkthrough
Defining the Context Object
First, we group our common dependencies into a single
from dataclasses import dataclass
from typing import Any
import logging
@dataclass
class AppContext:
user_id: int
db: Any
logger: logging.Logger
config: dict[str, Any]
Refactoring High-Level Functions
Instead of passing individual variables, we pass the AppContext. This drastically reduces the noise in high-level business logic.
def publish_article(article_id: int, context: AppContext):
# Accessing shared dependencies via the context
context.logger.info(f"Processing article {article_id}")
article = retrieve_article(article_id, context.db, context.logger)
if article:
html = render_article(article, context.logger)
send_to_api(html, context.config["api_key"])
Syntax Notes
- Dot Notation: Accessing
context.loggerinstead of a localloggervariable makes the source of the dependency explicit. - Protocols for Decoupling: Instead of depending on concrete types like
sqlalchemy.Session, usetyping.Protocolto define what the context needs. This allows you to swap real dependencies for mocks during testing.
Practical Examples
- Web Frameworks: Djangouses a context dictionary to pass data from views to templates.
- Request Handling: Backend systems often use a
Requestobject that carries headers, user authentication, and payload data through various middleware layers.
Tips & Gotchas
- Avoid the God Object: Never throw every possible variable into your context. If a low-level utility function only needs an integer, pass the integer, not the whole context.
- High-Level vs. Low-Level: Reserve context objects for high-level orchestrators. Low-level functions should remain "pure" and focused to prevent tight coupling.
- Testing: Use the context object to inject mocks. By passing a mock context to your function, you can simulate database failures or log captures easily.