Skip to content

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