bear-utils 0.7.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. bear_utils/__init__.py +13 -0
  2. bear_utils/ai/__init__.py +30 -0
  3. bear_utils/ai/ai_helpers/__init__.py +130 -0
  4. bear_utils/ai/ai_helpers/_common.py +19 -0
  5. bear_utils/ai/ai_helpers/_config.py +24 -0
  6. bear_utils/ai/ai_helpers/_parsers.py +188 -0
  7. bear_utils/ai/ai_helpers/_types.py +20 -0
  8. bear_utils/cache/__init__.py +119 -0
  9. bear_utils/cli/__init__.py +4 -0
  10. bear_utils/cli/commands.py +59 -0
  11. bear_utils/cli/prompt_helpers.py +166 -0
  12. bear_utils/cli/shell/__init__.py +0 -0
  13. bear_utils/cli/shell/_base_command.py +74 -0
  14. bear_utils/cli/shell/_base_shell.py +390 -0
  15. bear_utils/cli/shell/_common.py +19 -0
  16. bear_utils/config/__init__.py +11 -0
  17. bear_utils/config/config_manager.py +92 -0
  18. bear_utils/config/dir_manager.py +64 -0
  19. bear_utils/config/settings_manager.py +232 -0
  20. bear_utils/constants/__init__.py +16 -0
  21. bear_utils/constants/_exceptions.py +3 -0
  22. bear_utils/constants/_lazy_typing.py +15 -0
  23. bear_utils/constants/date_related.py +36 -0
  24. bear_utils/constants/time_related.py +22 -0
  25. bear_utils/database/__init__.py +6 -0
  26. bear_utils/database/_db_manager.py +104 -0
  27. bear_utils/events/__init__.py +16 -0
  28. bear_utils/events/events_class.py +52 -0
  29. bear_utils/events/events_module.py +65 -0
  30. bear_utils/extras/__init__.py +17 -0
  31. bear_utils/extras/_async_helpers.py +15 -0
  32. bear_utils/extras/_tools.py +178 -0
  33. bear_utils/extras/platform_utils.py +53 -0
  34. bear_utils/extras/wrappers/__init__.py +0 -0
  35. bear_utils/extras/wrappers/add_methods.py +98 -0
  36. bear_utils/files/__init__.py +4 -0
  37. bear_utils/files/file_handlers/__init__.py +3 -0
  38. bear_utils/files/file_handlers/_base_file_handler.py +93 -0
  39. bear_utils/files/file_handlers/file_handler_factory.py +278 -0
  40. bear_utils/files/file_handlers/json_file_handler.py +44 -0
  41. bear_utils/files/file_handlers/log_file_handler.py +33 -0
  42. bear_utils/files/file_handlers/txt_file_handler.py +34 -0
  43. bear_utils/files/file_handlers/yaml_file_handler.py +57 -0
  44. bear_utils/files/ignore_parser.py +298 -0
  45. bear_utils/graphics/__init__.py +4 -0
  46. bear_utils/graphics/bear_gradient.py +140 -0
  47. bear_utils/graphics/image_helpers.py +39 -0
  48. bear_utils/gui/__init__.py +3 -0
  49. bear_utils/gui/gui_tools/__init__.py +5 -0
  50. bear_utils/gui/gui_tools/_settings.py +37 -0
  51. bear_utils/gui/gui_tools/_types.py +12 -0
  52. bear_utils/gui/gui_tools/qt_app.py +145 -0
  53. bear_utils/gui/gui_tools/qt_color_picker.py +119 -0
  54. bear_utils/gui/gui_tools/qt_file_handler.py +138 -0
  55. bear_utils/gui/gui_tools/qt_input_dialog.py +306 -0
  56. bear_utils/logging/__init__.py +25 -0
  57. bear_utils/logging/logger_manager/__init__.py +0 -0
  58. bear_utils/logging/logger_manager/_common.py +47 -0
  59. bear_utils/logging/logger_manager/_console_junk.py +131 -0
  60. bear_utils/logging/logger_manager/_styles.py +91 -0
  61. bear_utils/logging/logger_manager/loggers/__init__.py +0 -0
  62. bear_utils/logging/logger_manager/loggers/_base_logger.py +238 -0
  63. bear_utils/logging/logger_manager/loggers/_base_logger.pyi +50 -0
  64. bear_utils/logging/logger_manager/loggers/_buffer_logger.py +55 -0
  65. bear_utils/logging/logger_manager/loggers/_console_logger.py +249 -0
  66. bear_utils/logging/logger_manager/loggers/_console_logger.pyi +64 -0
  67. bear_utils/logging/logger_manager/loggers/_file_logger.py +141 -0
  68. bear_utils/logging/logger_manager/loggers/_level_sin.py +58 -0
  69. bear_utils/logging/logger_manager/loggers/_logger.py +18 -0
  70. bear_utils/logging/logger_manager/loggers/_sub_logger.py +110 -0
  71. bear_utils/logging/logger_manager/loggers/_sub_logger.pyi +38 -0
  72. bear_utils/logging/loggers.py +76 -0
  73. bear_utils/monitoring/__init__.py +10 -0
  74. bear_utils/monitoring/host_monitor.py +350 -0
  75. bear_utils/time/__init__.py +16 -0
  76. bear_utils/time/_helpers.py +91 -0
  77. bear_utils/time/_time_class.py +316 -0
  78. bear_utils/time/_timer.py +80 -0
  79. bear_utils/time/_tools.py +17 -0
  80. bear_utils/time/time_manager.py +218 -0
  81. bear_utils-0.7.11.dist-info/METADATA +260 -0
  82. bear_utils-0.7.11.dist-info/RECORD +83 -0
  83. bear_utils-0.7.11.dist-info/WHEEL +4 -0
@@ -0,0 +1,92 @@
1
+ import os
2
+ from functools import lru_cache
3
+ from pathlib import Path
4
+ from typing import Any, Generic, TypeVar
5
+
6
+ from pydantic import BaseModel
7
+
8
+ ConfigType = TypeVar("ConfigType", bound=BaseModel)
9
+
10
+ # TODO: Get around to potentially using this, right now it is just masturbation
11
+
12
+
13
+ class ConfigManager(Generic[ConfigType]):
14
+ def __init__(self, config_model: type[ConfigType], config_path: Path | None = None, env: str = "dev") -> None:
15
+ self._model = config_model
16
+ self._config_path = config_path or Path("config")
17
+ self._env = env
18
+ self._config: ConfigType | None = None
19
+
20
+ def _get_env_overrides(self) -> dict[str, Any]:
21
+ """Convert environment variables to nested dictionary structure."""
22
+ env_config: dict[str, Any] = {}
23
+
24
+ for key, value in os.environ.items():
25
+ if not key.startswith("APP_"):
26
+ continue
27
+
28
+ # Convert APP_DATABASE_HOST to ['database', 'host']
29
+ parts = key.lower().replace("app_", "").split("_")
30
+
31
+ # Build nested dictionary
32
+ current = env_config
33
+ for part in parts[:-1]:
34
+ current = current.setdefault(part, {})
35
+ current[parts[-1]] = value
36
+
37
+ return env_config
38
+
39
+ @lru_cache
40
+ def load(self) -> ConfigType:
41
+ # Load order (later overrides earlier):
42
+ # 1. default.yaml
43
+ # 2. {env}.yaml
44
+ # 3. local.yaml (gitignored)
45
+ # 4. environment variables
46
+ config_data: dict[str, Any] = {}
47
+
48
+ # for config_file in [
49
+ # self._config_path / "default.yaml",
50
+ # self._config_path / f"{self._env}.yaml",
51
+ # self._config_path / "local.yaml",
52
+ # ]:
53
+ # if config_file.exists():
54
+ # with open(config_file) as f:
55
+ # config_data.update(yaml.safe_load(f))
56
+
57
+ config_data.update(self._get_env_overrides())
58
+
59
+ return self._model.model_validate(config_data)
60
+
61
+ @property
62
+ def config(self) -> ConfigType:
63
+ if self._config is None:
64
+ self._config = self.load()
65
+ return self._config
66
+
67
+
68
+ # Example usage:
69
+ # class DatabaseConfig(BaseModel):
70
+ # host: str
71
+ # port: int
72
+ # username: str
73
+ # password: str
74
+ # database: str
75
+
76
+
77
+ # class AppConfig(BaseModel):
78
+ # database: DatabaseConfig
79
+ # environment: str
80
+ # debug: bool = False
81
+ # logging_mode: str = "console"
82
+ # log_level: str = "INFO"
83
+
84
+
85
+ # config_manager = ConfigManager[AppConfig](
86
+ # config_model=AppConfig,
87
+ # config_path=Path("config"),
88
+ # env="development",
89
+ # )
90
+
91
+ # Access config
92
+ # db_config = config_manager.config.database
@@ -0,0 +1,64 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+
4
+
5
+ @dataclass
6
+ class DirectoryManager:
7
+ _base_path: Path = Path.home() / ".config" / "bear_utils"
8
+ _settings_path: Path = _base_path / "settings"
9
+ _temp_path: Path = _base_path / "temp"
10
+
11
+ def setup(self) -> None:
12
+ """Ensure the base, settings, and temp directories exist."""
13
+ self._base_path.mkdir(parents=True, exist_ok=True)
14
+ self._settings_path.mkdir(parents=True, exist_ok=True)
15
+ self._temp_path.mkdir(parents=True, exist_ok=True)
16
+
17
+ def clear_temp(self) -> None:
18
+ """Clear the temporary directory."""
19
+ if self.temp_path.exists():
20
+ for item in self.temp_path.iterdir():
21
+ if item.is_file() or item.is_symlink():
22
+ item.unlink()
23
+ elif item.is_dir():
24
+ item.rmdir()
25
+
26
+ @property
27
+ def base_path(self) -> Path:
28
+ """Get the base path for bear_utils."""
29
+ return self._base_path
30
+
31
+ @property
32
+ def settings_path(self) -> Path:
33
+ """Get the path to the settings directory."""
34
+ return self._settings_path
35
+
36
+ @property
37
+ def temp_path(self) -> Path:
38
+ """Get the path to the temporary directory."""
39
+ return self._temp_path
40
+
41
+
42
+ def get_base_path() -> Path:
43
+ """Get the base path for bear_utils."""
44
+ return DirectoryManager().base_path
45
+
46
+
47
+ def get_settings_path() -> Path:
48
+ """Get the path to the settings directory."""
49
+ return DirectoryManager().settings_path
50
+
51
+
52
+ def get_temp_path() -> Path:
53
+ """Get the path to the temporary directory."""
54
+ return DirectoryManager().temp_path
55
+
56
+
57
+ def setup_directories() -> None:
58
+ """Set up the necessary directories for bear_utils."""
59
+ DirectoryManager().setup()
60
+
61
+
62
+ def clear_temp_directory() -> None:
63
+ """Clear the temporary directory."""
64
+ DirectoryManager().clear_temp()
@@ -0,0 +1,232 @@
1
+ import atexit
2
+ from argparse import Namespace
3
+ from contextlib import contextmanager
4
+ from pathlib import Path
5
+ from shelve import Shelf
6
+ from typing import Any
7
+
8
+ from singleton_base.singleton_base_new import SingletonBase
9
+
10
+
11
+ def get_bear_config_path() -> Path:
12
+ """Get the path to the bear configuration path"""
13
+ path = Path.home() / ".config" / "bear_utils"
14
+ if not path.exists():
15
+ path.mkdir(parents=True, exist_ok=True)
16
+ return path
17
+
18
+
19
+ def get_config_file_path(filename: str) -> Path:
20
+ """Get the path to a specific configuration file in the bear configuration path"""
21
+ config_path = get_bear_config_path()
22
+ return config_path / filename
23
+
24
+
25
+ class SettingsCache(Namespace):
26
+ """A Namespace to hold settings in memory."""
27
+
28
+ def __init__(self):
29
+ super().__init__()
30
+
31
+ def clear(self):
32
+ """Clear the settings cache."""
33
+ self.__dict__.clear()
34
+
35
+ def set(self, key: str, value):
36
+ """Set a value in the settings cache."""
37
+ setattr(self, key, value)
38
+
39
+ def get(self, key: str, default=None):
40
+ """Get a value from the settings cache."""
41
+ return getattr(self, key, default)
42
+
43
+ def has(self, key: str) -> bool:
44
+ """Check if a key exists in the settings cache."""
45
+ return hasattr(self, key)
46
+
47
+
48
+ class SettingsManager:
49
+ settings_name: str
50
+ shelf: Shelf
51
+ settings_cache: SettingsCache
52
+
53
+ def __init__(self, settings_name: str):
54
+ object.__setattr__(self, "settings_name", settings_name)
55
+ object.__setattr__(self, "settings_cache", SettingsCache())
56
+ object.__setattr__(self, "shelf", self._open())
57
+ object.__setattr__(self, "_initialized", True)
58
+
59
+ atexit.register(self.close)
60
+ self._load_existing_settings()
61
+
62
+ def __getattr__(self, key: str):
63
+ """Handle dot notation access for settings."""
64
+ if key.startswith("_"):
65
+ raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{key}'")
66
+ return self.get(key)
67
+
68
+ def __setattr__(self, key: str, value):
69
+ """Handle dot notation assignment for settings."""
70
+ if not hasattr(self, "_initialized"):
71
+ object.__setattr__(self, key, value)
72
+ return
73
+
74
+ if key in ["settings_name", "settings_cache", "shelf"] or key.startswith("_"):
75
+ raise AttributeError(f"Cannot modify '{key}' after initialization")
76
+
77
+ self.set(key, value)
78
+
79
+ def get(self, key: str, default=None):
80
+ """Get a setting value by key with optional default."""
81
+ try:
82
+ if self.settings_cache.has(key):
83
+ return self.settings_cache.get(key, default)
84
+ elif self._shelf_has(key):
85
+ return self.shelf[key]
86
+ else:
87
+ return default
88
+ except Exception as e:
89
+ return default
90
+
91
+ def set(self, key: str, value):
92
+ """Set a setting value by key."""
93
+ self.shelf[key] = value
94
+ self.settings_cache.set(key, value)
95
+
96
+ def has(self, key: str) -> bool:
97
+ """Check if a setting exists by key."""
98
+ return self.settings_cache.has(key) or self._shelf_has(key)
99
+
100
+ def _shelf_has(self, key: str) -> bool:
101
+ """Check if a setting exists by key."""
102
+ return key in self.shelf
103
+
104
+ def open(self):
105
+ object.__setattr__(self, "shelf", self._open())
106
+ self._load_existing_settings()
107
+
108
+ def _open(self) -> Shelf:
109
+ """Open the settings file."""
110
+ import shelve
111
+
112
+ try:
113
+ shelf: Shelf[Any] = shelve.open(get_config_file_path(self.settings_name))
114
+ return shelf
115
+ except Exception as e:
116
+ raise RuntimeError(f"Warning: Could not open settings file '{self.settings_name}': {e}")
117
+
118
+ def _load_existing_settings(self):
119
+ """Load existing settings from shelf into namespace."""
120
+ for key in self.shelf:
121
+ if not self.settings_cache.has(key):
122
+ self.settings_cache.set(key, self.shelf[key])
123
+
124
+ def close(self):
125
+ """Close the settings file."""
126
+ if self.shelf is not None:
127
+ self.shelf.close()
128
+ if self.settings_cache is not None:
129
+ self.settings_cache.clear()
130
+
131
+ def destroy_settings(self) -> bool:
132
+ """Delete the settings file, a bit nuclear and will require calling open() again."""
133
+ file_path = get_config_file_path(self.settings_name)
134
+ if file_path.exists():
135
+ self.close()
136
+ file_path.unlink()
137
+ self.settings_cache.clear()
138
+ object.__setattr__(self, "shelf", None)
139
+ return True
140
+ return False
141
+
142
+ def __del__(self):
143
+ """Destructor to ensure the shelf is closed."""
144
+ self.close()
145
+
146
+ def __enter__(self):
147
+ """Context manager entry."""
148
+ return self
149
+
150
+ def __exit__(self, exc_type, exc_val, exc_tb):
151
+ """Context manager exit."""
152
+ self.close()
153
+
154
+ def __contains__(self, key: str) -> bool:
155
+ """Check if a setting exists."""
156
+ return self.has(key)
157
+
158
+ def keys(self) -> list[str]:
159
+ """Get all setting keys."""
160
+ return list(vars(self.settings_cache).keys())
161
+
162
+ def items(self):
163
+ """Get all setting key-value pairs."""
164
+ for key in self.keys():
165
+ yield key, self.settings_cache.get(key)
166
+
167
+ def values(self):
168
+ """Get all setting values."""
169
+ for key in self.keys():
170
+ yield self.settings_cache.get(key)
171
+
172
+ def __repr__(self):
173
+ """String representation of the SettingsManager."""
174
+ return f"<SettingsManager settings_name='{self.settings_name}'>"
175
+
176
+ def __str__(self):
177
+ """String representation of the SettingsManager."""
178
+ return f"SettingsManager for '{self.settings_name}' with {len(self.keys())} settings."
179
+
180
+ def __len__(self):
181
+ """Get the number of settings."""
182
+ return len(self.keys())
183
+
184
+
185
+ class SettingsSupervisor(SingletonBase):
186
+ def __init__(self):
187
+ self._settings_managers = {}
188
+
189
+ def _get_instance(self, settings_name: str) -> SettingsManager:
190
+ """Get or create a SettingsManager instance."""
191
+ if settings_name in self._settings_managers:
192
+ return self._settings_managers[settings_name]
193
+ return SettingsManager(settings_name)
194
+
195
+
196
+ def get_settings_manager(settings_name: str) -> SettingsManager:
197
+ """Get the SettingsManager instance for a given settings name."""
198
+ supervisor: SettingsSupervisor = SettingsSupervisor.get_instance(init=True)
199
+ return supervisor._get_instance(settings_name)
200
+
201
+
202
+ @contextmanager
203
+ def settings(settings_name: str):
204
+ """Context manager for SettingsManager."""
205
+ sm: SettingsManager = get_settings_manager(settings_name)
206
+ try:
207
+ yield sm
208
+ finally:
209
+ sm.close()
210
+
211
+
212
+ __all__: list[str] = ["SettingsManager", "settings", "get_settings_manager"]
213
+
214
+ # if __name__ == "__main__":
215
+ # with settings("example_settings") as sm:
216
+ # sm.sample_setting = "This is a sample setting"
217
+ # print(sm.sample_setting)
218
+ # print(sm.keys())
219
+ # for key, value in sm.items():
220
+ # print("This is items()")
221
+ # print(f"Key: {key}, Value: {value}")
222
+ # for value in sm.values():
223
+ # print("This is values()")
224
+ # print(value)
225
+ # print(len(sm))
226
+ # print(sm)
227
+ # if sm.destroy_settings():
228
+ # print("Settings destroyed successfully.")
229
+ # sm.open()
230
+ # print(sm.keys())
231
+ # sm.test = "Test setting"
232
+ # print(len(sm))
@@ -0,0 +1,16 @@
1
+ from os import getenv
2
+ from pathlib import Path
3
+
4
+ VIDEO_EXTS = [".mp4", ".mov", ".avi", ".mkv"]
5
+ """Extensions for video files."""
6
+ IMAGE_EXTS = [".jpg", ".jpeg", ".png", ".gif"]
7
+ """Extensions for image files."""
8
+ FILE_EXTS = IMAGE_EXTS + VIDEO_EXTS
9
+ """Extensions for both image and video files."""
10
+
11
+ PATH_TO_DOWNLOADS = Path.home() / "Downloads"
12
+ """Path to the Downloads folder."""
13
+ PATH_TO_PICTURES = Path.home() / "Pictures"
14
+ """Path to the Pictures folder."""
15
+ GLOBAL_VENV = Path.home() / ".global_venv"
16
+ """Path to the global virtual environment."""
@@ -0,0 +1,3 @@
1
+ class UserCancelled(Exception):
2
+ def __init__(self, message: str = "User cancelled the operation"):
3
+ super().__init__(message)
@@ -0,0 +1,15 @@
1
+ from typing import Literal, TypeVar
2
+
3
+ from sqlalchemy.ext.declarative import DeclarativeMeta
4
+
5
+ LitInt = Literal["int"]
6
+ LitFloat = Literal["float"]
7
+ LitStr = Literal["str"]
8
+ LitBool = Literal["bool"]
9
+
10
+ OptInt = int | None
11
+ OptFloat = float | None
12
+ OptStr = str | None
13
+ OptBool = bool | None
14
+
15
+ TableType = TypeVar("TableType", bound=DeclarativeMeta)
@@ -0,0 +1,36 @@
1
+ from typing import LiteralString
2
+
3
+ from pytz import UTC, timezone
4
+ from pytz.tzinfo import BaseTzInfo, DstTzInfo, StaticTzInfo
5
+
6
+ TimeZoneType = BaseTzInfo | DstTzInfo | StaticTzInfo
7
+
8
+ DATE_FORMAT = "%m-%d-%Y"
9
+ """Date format"""
10
+
11
+ TIME_FORMAT = "%I:%M %p"
12
+ """Time format with 12 hour format"""
13
+
14
+ TIME_FORMAT_WITH_SECONDS: LiteralString = "%I:%M:%S %p"
15
+ """Time format with 12 hour format and seconds"""
16
+
17
+ DATE_TIME_FORMAT: LiteralString = f"{DATE_FORMAT} {TIME_FORMAT}"
18
+ """Datetime format with 12 hour format"""
19
+
20
+ DT_FORMAT_WITH_SECONDS: LiteralString = f"{DATE_FORMAT} {TIME_FORMAT_WITH_SECONDS}"
21
+ """Datetime format with 12 hour format and seconds"""
22
+
23
+ DT_FORMAT_WITH_TZ: LiteralString = f"{DATE_TIME_FORMAT} %Z"
24
+ """Datetime format with 12 hour format and timezone"""
25
+
26
+ DT_FORMAT_WITH_TZ_AND_SECONDS: LiteralString = f"{DT_FORMAT_WITH_SECONDS} %Z"
27
+ """Datetime format with 12 hour format, seconds, and timezone"""
28
+
29
+ PT_TIME_ZONE: TimeZoneType = timezone("America/Los_Angeles")
30
+ """Default timezone, a Pacific Time Zone using a pytz timezone object"""
31
+
32
+ UTC_TIME_ZONE: TimeZoneType = UTC
33
+ """UTC timezone, a UTC timezone using a pytz timezone object"""
34
+
35
+ CEST_TIME_ZONE: TimeZoneType = timezone("Europe/Berlin") # Central European Summer Time, I GUESS!
36
+ """Central European Summer Time timezone, a CEST timezone using a pytz timezone object"""
@@ -0,0 +1,22 @@
1
+ from typing import Literal
2
+
3
+ MINUTES_IN_HOUR: Literal[60] = 60
4
+ """60 minutes in an hour"""
5
+
6
+ HOURS_IN_DAY: Literal[24] = 24
7
+ """24 hours in a day"""
8
+
9
+ DAYS_IN_MONTH: Literal[30] = 30
10
+ """30 days in a month, approximation for a month"""
11
+
12
+ SECONDS_IN_MINUTE: Literal[60] = 60
13
+ """60 seconds in a minute"""
14
+
15
+ SECONDS_IN_HOUR: Literal[3600] = SECONDS_IN_MINUTE * MINUTES_IN_HOUR
16
+ """60 * 60 = 3600 seconds in an hour"""
17
+
18
+ SECONDS_IN_DAY: Literal[86400] = SECONDS_IN_HOUR * HOURS_IN_DAY
19
+ """24 * 60 * 60 = 86400 seconds in a day"""
20
+
21
+ SECONDS_IN_MONTH: Literal[2592000] = SECONDS_IN_DAY * DAYS_IN_MONTH
22
+ """30 * 24 * 60 * 60 = 2592000 seconds in a month"""
@@ -0,0 +1,6 @@
1
+ from ._db_manager import DatabaseManager, SingletonDB
2
+
3
+ __all__ = [
4
+ "DatabaseManager",
5
+ "SingletonDB",
6
+ ]
@@ -0,0 +1,104 @@
1
+ import atexit
2
+ from collections.abc import Generator
3
+ from contextlib import contextmanager
4
+ from pathlib import Path
5
+ from typing import Any, ClassVar
6
+
7
+ from singleton_base import SingletonBase
8
+ from sqlalchemy import Engine, MetaData, create_engine
9
+ from sqlalchemy.ext.declarative import DeclarativeMeta
10
+ from sqlalchemy.orm import declarative_base, scoped_session, sessionmaker
11
+ from sqlalchemy.orm.session import Session
12
+
13
+ from ..constants._lazy_typing import TableType
14
+
15
+
16
+ class DatabaseManager:
17
+ _base: ClassVar[DeclarativeMeta | None] = None
18
+
19
+ @classmethod
20
+ def set_base(cls, base: DeclarativeMeta) -> None:
21
+ """Set the base class for the database manager."""
22
+ cls._base = base
23
+
24
+ @classmethod
25
+ def get_base(cls) -> DeclarativeMeta:
26
+ """Get the base class for the database manager."""
27
+ if cls._base is None:
28
+ cls.set_base(declarative_base())
29
+ if cls._base is None:
30
+ raise ValueError("Base class is not set, failed to set base.")
31
+ return cls._base
32
+
33
+ def __init__(self, db_url: str | Path | None = None):
34
+ if db_url is None or db_url == "":
35
+ raise ValueError("Database URL cannot be None or empty.")
36
+ self.db_url: str = str(db_url)
37
+ self.engine: Engine = create_engine(self.db_url, echo=False)
38
+ base: DeclarativeMeta = DatabaseManager.get_base()
39
+ self.metadata: MetaData = base.metadata
40
+ self.SessionFactory = sessionmaker(bind=self.engine)
41
+ self.session = scoped_session(self.SessionFactory)
42
+ atexit.register(self.close_all)
43
+ self.create_tables()
44
+
45
+ def get_all_records(self, table_obj: type[TableType]) -> list[TableType]:
46
+ """Get all records from a table."""
47
+ with self.open_session() as session:
48
+ records = session.query(table_obj).all()
49
+ return records
50
+
51
+ def count_records(self, table_obj: type[TableType]) -> int:
52
+ """Count the number of records in a table."""
53
+ with self.open_session() as session:
54
+ count: int = session.query(table_obj).count()
55
+ return count
56
+
57
+ def get_records_by_var(self, table_obj: type[TableType], variable: str, value) -> list[TableType]:
58
+ """Get records from a table by a specific variable."""
59
+ with self.open_session() as session:
60
+ records: list[TableType] = session.query(table_obj).filter(getattr(table_obj, variable) == value).all()
61
+ return records
62
+
63
+ def count_records_by_var(self, table_obj: type[TableType], variable: str, value) -> int:
64
+ """Count the number of records in a table by a specific variable."""
65
+ with self.open_session() as session:
66
+ count: int = session.query(table_obj).filter(getattr(table_obj, variable) == value).count()
67
+ return count
68
+
69
+ @contextmanager
70
+ def open_session(self) -> Generator[Session, Any, None]:
71
+ """Provide a transactional scope around a series of operations."""
72
+ session: Session = self.session()
73
+ try:
74
+ yield session
75
+ session.commit()
76
+ except Exception:
77
+ session.rollback()
78
+ raise
79
+
80
+ def get_session(self) -> Session:
81
+ """Get a new session."""
82
+ return self.session()
83
+
84
+ def close_session(self):
85
+ """Close the session."""
86
+ self.session.remove()
87
+
88
+ def create_tables(self):
89
+ """Create all tables defined by Base"""
90
+ self.metadata.create_all(self.engine)
91
+
92
+ def close_all(self):
93
+ """Close all sessions and connections."""
94
+ self.session.close()
95
+ self.engine.dispose()
96
+
97
+
98
+ class SingletonDB(DatabaseManager, SingletonBase):
99
+ """Singleton class for DatabaseManager, uses SingletonBase to inject singleton pattern."""
100
+
101
+ ...
102
+
103
+
104
+ __all__ = ["DatabaseManager", "SingletonDB"]
@@ -0,0 +1,16 @@
1
+ from .events_class import Events
2
+ from .events_module import clear_all, clear_handlers_for_event, dispatch_event, event_handler, set_handler
3
+
4
+ subscribe = event_handler
5
+ publish = dispatch_event
6
+
7
+ __all__ = [
8
+ "Events",
9
+ "clear_all",
10
+ "clear_handlers_for_event",
11
+ "dispatch_event",
12
+ "event_handler",
13
+ "set_handler",
14
+ "subscribe",
15
+ "publish",
16
+ ]
@@ -0,0 +1,52 @@
1
+ from collections.abc import Callable
2
+ from typing import Any
3
+
4
+ from .events_module import clear_all as _clear_all
5
+ from .events_module import clear_handlers_for_event as _clear_handlers_for_event
6
+ from .events_module import dispatch_event as _dispatch_event
7
+ from .events_module import event_handler as _event_handler
8
+ from .events_module import set_handler as _set_handler
9
+
10
+ Callback = Callable[..., Any]
11
+
12
+
13
+ class Events:
14
+ """Simple wrapper exposing :mod:`events_module` functionality as methods."""
15
+
16
+ # Method names mirror functions from ``events_module`` for familiarity
17
+
18
+ def event_handler(self, event_name: str, func: Callback | None = None):
19
+ """Register ``func`` as a handler for ``event_name``.
20
+
21
+ Can be used as a decorator when ``func`` is omitted."""
22
+
23
+ if func is None:
24
+ return _event_handler(event_name)
25
+ _set_handler(event_name, func)
26
+ return func
27
+
28
+ def dispatch_event(self, event_name: str, *args, **kwargs) -> Any | None:
29
+ """Dispatch ``event_name`` to all subscribed handlers."""
30
+
31
+ return _dispatch_event(event_name, *args, **kwargs)
32
+
33
+ def set_handler(self, event_name: str, func: Callback) -> None:
34
+ """Register ``func`` as a handler for ``event_name``."""
35
+
36
+ _set_handler(event_name, func)
37
+
38
+ def clear_handlers_for_event(self, event_name: str) -> None:
39
+ """Remove all handlers associated with ``event_name``."""
40
+
41
+ _clear_handlers_for_event(event_name)
42
+
43
+ def clear_all(self) -> None:
44
+ """Remove all registered event handlers."""
45
+
46
+ _clear_all()
47
+
48
+ subscribe = event_handler
49
+ publish = dispatch_event
50
+
51
+
52
+ __all__ = ["Events"]