A testable singleton decorator that allows easily creating singleton objects by adding a decorator to class definitions while maintaining testability.
npx @tessl/cli install tessl/pypi-singleton-decorator@1.0.0A testable singleton decorator that allows easily creating singleton objects by adding a decorator to class definitions while maintaining testability. Unlike traditional singleton implementations that make direct class access impossible in unit tests, this decorator uses a wrapper object that stores the original class in a __wrapped__ attribute, allowing developers to access the decorated class directly for isolated testing.
pip install singleton-decoratorfrom singleton_decorator import singletonVersion information:
from singleton_decorator import __version__from singleton_decorator import singleton
@singleton
class DatabaseConnection:
def __init__(self, host, port):
self.host = host
self.port = port
print(f"Connecting to {host}:{port}")
def query(self, sql):
return f"Executing: {sql}"
# First call creates the instance
db1 = DatabaseConnection("localhost", 5432)
# Subsequent calls return the same instance (constructor args ignored)
db2 = DatabaseConnection("ignored", 9999)
db3 = DatabaseConnection("also_ignored", 1111)
# All variables reference the same instance
assert db1 is db2 is db3
print(db1.host) # "localhost"
print(db2.host) # "localhost" (not "ignored")Creates a singleton wrapper for any class, ensuring only one instance exists while maintaining testability through the __wrapped__ attribute.
def singleton(cls):
"""
A singleton decorator that returns a wrapper object. A call on that object
returns a single instance object of decorated class. Use the __wrapped__
attribute to access decorated class directly in unit tests.
Args:
cls: The class to decorate as a singleton
Returns:
_SingletonWrapper: Wrapper object that manages singleton instance
"""Access the original class for isolated unit testing without singleton behavior.
# Access pattern for testing
decorated_class.__wrapped__ # Original class for direct method callsUsage in Tests:
from unittest import TestCase, mock
from singleton_decorator import singleton
@singleton
class MyService:
def __init__(self, config):
self.config = config
def process_data(self, data):
return f"Processing {data} with {self.config}"
class TestMyService(TestCase):
def test_process_data(self):
# Use __wrapped__ to test method in isolation
mock_self = mock.MagicMock()
mock_self.config = "test_config"
result = MyService.__wrapped__.process_data(mock_self, "test_data")
assert result == "Processing test_data with test_config"class _SingletonWrapper:
"""
Internal wrapper class created for each decorated class.
Manages singleton instance lifecycle.
"""
def __init__(self, cls):
"""
Initialize wrapper with the decorated class.
Args:
cls: The class to wrap
"""
def __call__(self, *args, **kwargs):
"""
Returns singleton instance of wrapped class.
Creates instance on first call, returns existing instance thereafter.
Args:
*args: Constructor arguments (only used on first call)
**kwargs: Constructor keyword arguments (only used on first call)
Returns:
object: Single instance of the wrapped class
"""
__wrapped__: type # Original decorated class for testing access
_instance: object # Singleton instance (None until first call)
__version__: str # Package version constant ("1.0.0")__init__ method is called only once with arguments from the first instantiationClassName.__wrapped__ to access the original class in unit testsself to test methods in complete isolation__wrapped__ attribute provides direct access to all class methods and attributesThis implementation does not provide thread-safety guarantees. In multi-threaded environments, consider additional synchronization mechanisms if concurrent access to the singleton creation is possible.