Singleton Pattern in FastAPI Dependency Injection
The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. In web development, this is essential for managing resources like database pools, configuration objects, logging handlers, or complex machine learning models that are costly to initialize.
In FastAPI, while dependencies are generally executed once per request and the result is cached, this does not guarantee that the underlying class is instantiated only once across the entire application's lifecycle. To enforce a true global Singleton, we must use Python's built-in methods, typically overriding __new__.
1. Defining the Singleton Classโ
We implement the Singleton logic within the class itself by overriding the __new__ method. This ensures that the initialization code runs only once when the application starts.
import time
from fastapi import FastAPI, Depends
from typing import Annotated
# --- 1. The Singleton Implementation ---
class GlobalConfigService:
"""
A service that guarantees only one instance exists across the application.
The initialization logic runs only once.
"""
_instance = None
def __new__(cls):
# Check if the instance already exists
if cls._instance is None:
print("[INFO] Singleton initializing... (Expensive initialization)")
# --- Simulation of one-time setup ---
cls._start_time = time.time()
cls._version = "v2.1"
# ------------------------------------
# Create the instance only if it doesn't exist
cls._instance = super(GlobalConfigService, cls).__new__(cls)
# Always return the single existing instance
return cls._instance
def get_uptime(self):
return time.time() - self._start_time
def get_version(self):
return self._version
def __call__(self):
"""
The __call__ method allows the class to be used directly in Depends().
It returns the instance itself.
"""
return self._instance
# --- 2. FastAPI Application Setup ---
app = FastAPI()
# Define the dependency type hint
Singleton = Annotated[GlobalConfigService, Depends(GlobalConfigService)]
Annotation: By overriding __new__, we control the instantiation process. The heavy initialization logic runs only on the first call to GlobalConfigService(), ensuring that subsequent calls immediately return the single, cached _instance.
2. Injecting the Singleton into Routesโ
We inject the class directly into the route using Depends(GlobalConfigService).
A. Simple Injectionโ
The first request to any of these endpoints will trigger the __new__ method, printing "Singleton initializing..." only once in the lifetime of the FastAPI process.
@app.get("/version")
def get_version_info(config: Singleton):
"""
Injects the single instance of GlobalConfigService.
"""
return {
"api_version": config.get_version(),
"instance_id": id(config) # Used to verify the same instance is returned
}
@app.get("/uptime")
def get_uptime_info(config: Singleton):
"""
Subsequent requests reuse the same instance. The initialization is not rerun.
"""
return {
"version": config.get_version(),
"uptime_seconds": config.get_uptime()
}
# Annotation: When FastAPI calls GlobalConfigService() via Depends(), Python's
# internal __new__ method intercepts the creation, guaranteeing that both
# '/version' and '/uptime' always receive the exact same object in memory.
3. Why this Method Works Bestโ
| Method | Instantiation Frequency | Use Case |
|---|---|---|
Simple Function Dependency (def get_db(): return DB()) | Once Per Request | Database sessions, request-scoped data. |
yield Dependency (def get_db(): yield DB()) | Once Per Request | Database sessions with guaranteed teardown. |
Class with __new__ (Singleton) | Once Per Application Lifecycle | Global configuration, costly service initialization (e.g., ML models, logging). |
By using the __new__ method, you ensure that the creation overhead is incurred only once when the server starts, making the service globally efficient and safe for managing shared state.
