Source code for wtforglib.singles
# mypy: disable-error-code="explicit-any"
"""This module implements a simple-to-use decorator to implement the singleton pattern.
Exports:
- r_singleton: resettable singleton wrapper
- singleton: ignores the arguments to the constructor on subsequent calls
- singleton_argenforce: raises a ValueError if the arguments on subsequent
calls vary from the initial ones.
"""
import functools
import threading
from typing import Any, Dict, Generic, Tuple, Type, TypeVar
T = TypeVar("T") # noqa: WPS111
class _ResettableWrapper(Generic[T]):
def __init__(self, cls: Type[T]): # noqa: WPS117
self._cls = cls
self._instance: T | None = None
self._lock = threading.Lock()
functools.update_wrapper(self, cls)
def __call__(self, *args: Tuple[Any, ...], **kwargs: Dict[str, Any]) -> T:
# Double-checked locking pattern
if self._instance is None:
with self._lock:
if self._instance is None:
self._instance = self._cls(*args, **kwargs)
return self._instance
def reset(self) -> None:
with self._lock:
self._instance = None
class _SingletonWrapper(Generic[T]):
_instance: T | None
_args: Tuple[Any, ...]
_kwargs: Dict[str, Any]
def __init__(self, cls: Type[T]) -> None: # noqa: WPS117
self.__wrapped__: Type[T]
functools.update_wrapper(self, cls)
self._instance = None
self._args = ()
self._kwargs = {}
def __call__(self, *args: Tuple[Any, ...], **kwargs: Dict[str, Any]) -> T:
if self._instance is None: # Create the instance
self._args = args
self._kwargs = kwargs
self._instance = self.__wrapped__(*args, **kwargs)
return self._instance
class _ArgEnforceSingletonWrapper(_SingletonWrapper[T]):
def __init__(self, cls: Type[T]) -> None: # noqa: WPS117
super().__init__(cls)
self._instances: Dict[Tuple[T], T] = {}
def __call__(self, *args: Tuple[Any, ...], **kwargs: Dict[str, Any]) -> T:
if self._instance is not None and (
args != self._args or kwargs != self._kwargs
):
raise ValueError("Singleton called with different arguments.")
return super().__call__(*args, **kwargs)
[docs]
def r_singleton(cls: Type[T]) -> _ResettableWrapper[T]:
"""Use this decorator to wrap a class to make it a resettable singleton.
The first time the wrapped class is instantiated, it will create an object
with those arguments and store it. Every subsequent instantiation will return
the stored object, regardless of the provided parameters. The stored object
can be reset to None by calling the `reset` method.
Args:
cls (Type[T]): The class to make a singleton
Returns:
_ResettableWrapper[T]: The singleton-wrapped class
"""
return _ResettableWrapper(cls)
[docs]
def singleton(cls: Type[T]) -> _SingletonWrapper[T]:
"""Use this decorator to wrap a class to make it a singleton.
The first time the wrapped class is instantiated, it will create an object
with those arguments and store it. Every subsequent instantiation will return
the stored object, regardless of the provided parameters.
Args:
cls (Type[T]): The class to make a singleton
Returns:
_SingletonWrapper[T]: The singleton-wrapped class
"""
return _SingletonWrapper(cls)
[docs]
def singleton_argenforce(cls: Type[T]) -> _ArgEnforceSingletonWrapper[T]:
"""Use this decorator to wrap a class to make it an arg-enforce singleton.
The first time the wrapped class is instantiated, it will create an object
with those arguments and store it. Every subsequent instantiation will return
the stored object, unless the arguments are different than the first call,
in which case a ValueError will be thrown.
Args:
cls (Type[T]): The class to make a singleton
Returns:
_ArgEnforceSingletonWrapper[T]: The singleton-wrapped class
"""
return _ArgEnforceSingletonWrapper(cls)