Coverage for functions \ flipdare \ core \ singleton.py: 83%
30 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
1#!/usr/bin/env python
2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved.
3#
4# This file is part of Flipdare's proprietary software and contains
5# confidential and copyrighted material. Unauthorised copying,
6# modification, distribution, or use of this file is strictly
7# prohibited without prior written permission from Flipdare Pty Ltd.
8#
9# This software includes third-party components licensed under MIT,
10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details.
11#
13from __future__ import annotations
14from typing import Any, ClassVar, Self, TypeVar, cast
16T = TypeVar("T", bound="Singleton")
19class Singleton:
20 """
21 Base class for singleton pattern.
23 Usage:
24 class MyService(Singleton):
25 def __init__(self, config: str = "default"):
26 super().__init__()
27 self.config = config
29 # First call with arguments
30 service = MyService.instance(config="production")
32 # Subsequent calls ignore arguments and return existing instance
33 same_service = MyService.instance(config="ignored")
35 # For resetting in dev/test environments:
36 MyService.reset_instance()
37 """
39 # Note: Each subclass needs its own _instance, so we use a dict keyed by class
40 _instances: ClassVar[dict[type, Singleton]] = {}
42 def __new__(cls, *args: Any, **kwargs: Any) -> Self: # noqa: ARG004
43 if cls not in cls._instances:
44 instance = super().__new__(cls)
45 cls._instances[cls] = instance
47 return cast("Self", cls._instances[cls])
49 def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ARG002
50 """
51 Override this in subclasses to accept initialization arguments.
52 Always call super().__init__() first.
53 Use the _initialized guard to prevent re-initialization.
54 """
55 if hasattr(self, "_initialized"):
56 return
58 self._initialized = True
60 @classmethod
61 def instance(cls, *args: Any, **kwargs: Any) -> Self:
62 """
63 Get or create the singleton instance.
65 Arguments are only used on first instantiation.
66 Subsequent calls return the existing instance regardless of arguments.
67 """
68 if cls not in cls._instances:
69 cls._instances[cls] = cls(*args, **kwargs)
71 return cls._instances[cls] # type: ignore[return-value]
73 @classmethod
74 def reset_instance(cls) -> None:
75 """Reset the singleton instance. Only allowed in dev/test environments."""
76 from flipdare.app_env import get_app_environment
78 if get_app_environment().in_cloud:
79 from flipdare.services import get_app_logger
80 from flipdare.generated.shared.app_error_code import AppErrorCode
82 # we dont want to throw a runtime error, because that may affect
83 # users instead log a message and return ..
84 msg = f"Attempt made to reset {cls.__name__} in production"
85 get_app_logger().system_error(
86 error_code=AppErrorCode.SERVER,
87 message=msg,
88 notify_admin=True,
89 )
90 return
92 if cls in cls._instances:
93 # LOG().info(f"Resetting singleton instance for {cls.__name__}")
94 del cls._instances[cls]