Skip to content

Classes Module

The classes module provides design pattern implementations to simplify common object-oriented programming patterns in Python.

Singleton Pattern

The Singleton metaclass ensures that a class has only one instance throughout the application lifecycle, providing a global point of access to that instance.

When to Use Singletons

Singletons are useful when you need exactly one instance of a class to coordinate actions across your system:

  • Configuration managers
  • Database connection pools
  • Logging services
  • Cache managers
  • Application state managers

Basic Usage

from pyutilkit.classes import Singleton


class AppConfig(metaclass=Singleton):
    def __init__(self):
        self.settings = {}
        self._load_config()

    def _load_config(self):
        self.settings['debug'] = False
        self.settings['database_url'] = 'postgresql://localhost/mydb'

    def get(self, key):
        return self.settings.get(key)


# Both calls return the same instance
config1 = AppConfig()
config2 = AppConfig()

assert config1 is config2  # True - same instance

Thread Safety

The Singleton implementation is thread-safe using double-checked locking:

import threading
from pyutilkit.classes import Singleton


class ThreadSafeCounter(metaclass=Singleton):
    def __init__(self):
        self.count = 0
        self.lock = threading.Lock()

    def increment(self):
        with self.lock:
            self.count += 1
            return self.count


# Safe to use from multiple threads
counter = ThreadSafeCounter()

Real-World Examples

Configuration Manager

from pyutilkit.classes import Singleton
import os


class EnvironmentConfig(metaclass=Singleton):
    """Centralized environment configuration."""

    def __init__(self):
        self.debug = os.getenv('DEBUG', 'false').lower() == 'true'
        self.database_url = os.getenv('DATABASE_URL')
        self.api_key = os.getenv('API_KEY')
        self.max_retries = int(os.getenv('MAX_RETRIES', '3'))

    def is_production(self):
        return not self.debug


# Access from anywhere in your application
config = EnvironmentConfig()
if config.is_production():
    print("Running in production mode")

Database Connection Pool

from pyutilkit.classes import Singleton


class DatabasePool(metaclass=Singleton):
    """Simple database connection pool."""

    def __init__(self):
        self._connections = []
        self._initialize_pool()

    def _initialize_pool(self, size=5):
        """Create initial pool of connections."""
        for i in range(size):
            conn = self._create_connection()
            self._connections.append(conn)

    def _create_connection(self):
        """Simulate creating a database connection."""
        return {"id": id(self), "status": "connected"}

    def get_connection(self):
        """Get a connection from the pool."""
        if self._connections:
            return self._connections.pop()
        raise RuntimeError("No available connections")

    def release_connection(self, conn):
        """Return a connection to the pool."""
        self._connections.append(conn)


# Use the same pool everywhere
pool = DatabasePool()
conn = pool.get_connection()
# ... use connection ...
pool.release_connection(conn)

Common Pitfalls

Testing Challenges

Singletons can make testing difficult because state persists between tests. Consider resetting the singleton instance between tests:

# In your test teardown
def tearDown(self):
    MySingleton.instance = None

Hidden Dependencies

Singletons create implicit dependencies that can make code harder to understand and maintain. Use dependency injection when possible for better testability.

When NOT to Use Singletons

  • When you might need multiple instances in the future
  • For simple utility functions (use modules instead)
  • When state management becomes complex (consider a proper state management solution)

API Reference

::: pyutilkit.classes.Singleton handler: python options: show_root_heading: true show_source: false members: - init - call