Coverage for src/configuraptor/singleton.py: 100%

15 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2026-04-28 15:40 +0200

1""" 

2Singleton mixin/metaclass. 

3""" 

4 

5import typing 

6 

7T = typing.TypeVar("T") 

8 

9 

10class SingletonMeta(type): 

11 """ 

12 Every instance of a singleton shares the same object underneath. 

13 

14 Can be used as a metaclass: 

15 Example: 

16 class AbstractConfig(metaclass=Singleton): 

17 

18 Source: https://stackoverflow.com/questions/6760685/creating-a-singleton-in-python 

19 """ 

20 

21 _instances: typing.ClassVar[dict[type[typing.Any], typing.Any]] = {} 

22 

23 def __call__(self: type[T], *args: typing.Any, **kwargs: typing.Any) -> T: 

24 """ 

25 When a class is instantiated (e.g. `AbstractConfig()`), __call__ is called. This overrides the default behavior. 

26 """ 

27 if self not in SingletonMeta._instances: 

28 SingletonMeta._instances[self] = type.__call__(self, *args, **kwargs) 

29 

30 return typing.cast(T, SingletonMeta._instances[self]) 

31 

32 def clear(cls, instance: "Singleton | type[Singleton] | None" = None) -> None: 

33 """ 

34 Use to remove old instances. 

35 

36 (otherwise e.g. pytest will crash) 

37 """ 

38 if instance: 

39 # Singleton.clear(SomeSingleton) or Singleton.clear(some_instance) 

40 SingletonMeta._instances.pop(instance if isinstance(instance, SingletonMeta) else instance.__class__, None) 

41 elif isinstance(cls, SingletonMeta) and issubclass(cls, Singleton) and cls is not Singleton: 

42 # SomeSingleton.clear() 

43 SingletonMeta._instances.pop(cls, None) 

44 else: 

45 # Singleton.clear() 

46 SingletonMeta._instances.clear() 

47 

48 

49class Singleton(metaclass=SingletonMeta): 

50 """ 

51 Mixin to make a class a singleton. 

52 """