Singleton Scope¶
One instance shared across the entire application. Created once, reused everywhere.
What is Singleton?¶
Singleton creates one instance for the entire application lifetime. Same instance is returned for all requests.
from injectq import InjectQ, singleton
container = InjectQ.get_instance()
@singleton
class Database:
def __init__(self):
print("Database created")
# First access creates instance
db1 = container[Database] # "Database created"
# Subsequent accesses return same instance
db2 = container[Database] # No output
print(db1 is db2) # True
When to Use¶
✅ Use for: - Database connections - Configuration objects - Caches - Loggers - Expensive resources
❌ Avoid for: - Request-specific data - User sessions - Temporary state
Examples¶
Good Use Cases¶
@singleton
class DatabaseConnection:
"""Shared connection pool"""
def __init__(self):
self.pool = create_connection_pool()
@singleton
class AppConfig:
"""Application settings"""
def __init__(self):
self.database_url = os.getenv("DATABASE_URL")
@singleton
class RedisCache:
"""Shared cache"""
def __init__(self):
self.client = redis.Redis()
Bad Use Cases¶
@singleton
class UserSession:
"""❌ Bad - user data gets mixed up"""
def __init__(self):
self.user_id = None
@singleton
class RequestContext:
"""❌ Bad - request data gets overwritten"""
def __init__(self):
self.request_id = None
Usage¶
Using the Decorator¶
from injectq import singleton
@singleton
class Database:
pass
# Automatically registered
db = container[Database]
With Dependencies¶
@singleton
class Database:
pass
@singleton
class UserRepository:
def __init__(self, db: Database):
self.db = db
@singleton
class UserService:
def __init__(self, repo: UserRepository):
self.repo = repo
# All dependencies are singletons
service = container[UserService]
Thread Safety¶
Singletons must be thread-safe for concurrent access:
@singleton
class ThreadSafeCache:
def __init__(self):
self._data = {}
self._lock = threading.Lock()
def get(self, key: str):
with self._lock:
return self._data.get(key)
def set(self, key: str, value):
with self._lock:
self._data[key] = value
Common Mistakes¶
❌ Storing Request Data¶
@singleton
class UserContext:
def __init__(self):
self.user_id = None # Shared across requests!
def set_user(self, user_id):
self.user_id = user_id # Overwrites for all users
✅ Use Scoped Instead¶
@scoped("request")
class UserContext:
def __init__(self):
self.user_id = None # Isolated per request
❌ Not Thread-Safe¶
@singleton
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1 # Race condition!
✅ Use Locking¶
@singleton
class Counter:
def __init__(self):
self.count = 0
self._lock = threading.Lock()
def increment(self):
with self._lock:
self.count += 1
Summary¶
- One instance per application
- Created once, reused everywhere
- Lazy initialization - created when first requested
- Thread safety required for concurrent access
Use for: Databases, config, caches, expensive resources
Avoid for: Request data, user sessions, temporary state
Next: Transient Scope | Scoped Services