bear-utils 0.0.1__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.
- bear_utils/__init__.py +51 -0
- bear_utils/__main__.py +14 -0
- bear_utils/_internal/__init__.py +0 -0
- bear_utils/_internal/_version.py +1 -0
- bear_utils/_internal/cli.py +119 -0
- bear_utils/_internal/debug.py +174 -0
- bear_utils/ai/__init__.py +30 -0
- bear_utils/ai/ai_helpers/__init__.py +136 -0
- bear_utils/ai/ai_helpers/_common.py +19 -0
- bear_utils/ai/ai_helpers/_config.py +24 -0
- bear_utils/ai/ai_helpers/_parsers.py +194 -0
- bear_utils/ai/ai_helpers/_types.py +15 -0
- bear_utils/cache/__init__.py +131 -0
- bear_utils/cli/__init__.py +22 -0
- bear_utils/cli/_args.py +12 -0
- bear_utils/cli/_get_version.py +207 -0
- bear_utils/cli/commands.py +105 -0
- bear_utils/cli/prompt_helpers.py +186 -0
- bear_utils/cli/shell/__init__.py +1 -0
- bear_utils/cli/shell/_base_command.py +81 -0
- bear_utils/cli/shell/_base_shell.py +430 -0
- bear_utils/cli/shell/_common.py +19 -0
- bear_utils/cli/typer_bridge.py +90 -0
- bear_utils/config/__init__.py +13 -0
- bear_utils/config/config_manager.py +229 -0
- bear_utils/config/dir_manager.py +69 -0
- bear_utils/config/settings_manager.py +179 -0
- bear_utils/constants/__init__.py +90 -0
- bear_utils/constants/_exceptions.py +8 -0
- bear_utils/constants/_exit_code.py +60 -0
- bear_utils/constants/_http_status_code.py +37 -0
- bear_utils/constants/_lazy_typing.py +15 -0
- bear_utils/constants/_meta.py +196 -0
- bear_utils/constants/date_related.py +25 -0
- bear_utils/constants/time_related.py +24 -0
- bear_utils/database/__init__.py +8 -0
- bear_utils/database/_db_manager.py +98 -0
- bear_utils/events/__init__.py +18 -0
- bear_utils/events/events_class.py +52 -0
- bear_utils/events/events_module.py +74 -0
- bear_utils/extras/__init__.py +28 -0
- bear_utils/extras/_async_helpers.py +67 -0
- bear_utils/extras/_tools.py +185 -0
- bear_utils/extras/_zapper.py +399 -0
- bear_utils/extras/platform_utils.py +57 -0
- bear_utils/extras/responses/__init__.py +5 -0
- bear_utils/extras/responses/function_response.py +451 -0
- bear_utils/extras/wrappers/__init__.py +1 -0
- bear_utils/extras/wrappers/add_methods.py +100 -0
- bear_utils/extras/wrappers/string_io.py +46 -0
- bear_utils/files/__init__.py +6 -0
- bear_utils/files/file_handlers/__init__.py +5 -0
- bear_utils/files/file_handlers/_base_file_handler.py +107 -0
- bear_utils/files/file_handlers/file_handler_factory.py +280 -0
- bear_utils/files/file_handlers/json_file_handler.py +71 -0
- bear_utils/files/file_handlers/log_file_handler.py +40 -0
- bear_utils/files/file_handlers/toml_file_handler.py +76 -0
- bear_utils/files/file_handlers/txt_file_handler.py +76 -0
- bear_utils/files/file_handlers/yaml_file_handler.py +64 -0
- bear_utils/files/ignore_parser.py +293 -0
- bear_utils/graphics/__init__.py +6 -0
- bear_utils/graphics/bear_gradient.py +145 -0
- bear_utils/graphics/font/__init__.py +13 -0
- bear_utils/graphics/font/_raw_block_letters.py +463 -0
- bear_utils/graphics/font/_theme.py +31 -0
- bear_utils/graphics/font/_utils.py +220 -0
- bear_utils/graphics/font/block_font.py +192 -0
- bear_utils/graphics/font/glitch_font.py +63 -0
- bear_utils/graphics/image_helpers.py +45 -0
- bear_utils/gui/__init__.py +8 -0
- bear_utils/gui/gui_tools/__init__.py +10 -0
- bear_utils/gui/gui_tools/_settings.py +36 -0
- bear_utils/gui/gui_tools/_types.py +12 -0
- bear_utils/gui/gui_tools/qt_app.py +150 -0
- bear_utils/gui/gui_tools/qt_color_picker.py +130 -0
- bear_utils/gui/gui_tools/qt_file_handler.py +130 -0
- bear_utils/gui/gui_tools/qt_input_dialog.py +303 -0
- bear_utils/logger_manager/__init__.py +109 -0
- bear_utils/logger_manager/_common.py +63 -0
- bear_utils/logger_manager/_console_junk.py +135 -0
- bear_utils/logger_manager/_log_level.py +50 -0
- bear_utils/logger_manager/_styles.py +95 -0
- bear_utils/logger_manager/logger_protocol.py +42 -0
- bear_utils/logger_manager/loggers/__init__.py +1 -0
- bear_utils/logger_manager/loggers/_console.py +223 -0
- bear_utils/logger_manager/loggers/_level_sin.py +61 -0
- bear_utils/logger_manager/loggers/_logger.py +19 -0
- bear_utils/logger_manager/loggers/base_logger.py +244 -0
- bear_utils/logger_manager/loggers/base_logger.pyi +51 -0
- bear_utils/logger_manager/loggers/basic_logger/__init__.py +5 -0
- bear_utils/logger_manager/loggers/basic_logger/logger.py +80 -0
- bear_utils/logger_manager/loggers/basic_logger/logger.pyi +19 -0
- bear_utils/logger_manager/loggers/buffer_logger.py +57 -0
- bear_utils/logger_manager/loggers/console_logger.py +278 -0
- bear_utils/logger_manager/loggers/console_logger.pyi +50 -0
- bear_utils/logger_manager/loggers/fastapi_logger.py +333 -0
- bear_utils/logger_manager/loggers/file_logger.py +151 -0
- bear_utils/logger_manager/loggers/simple_logger.py +98 -0
- bear_utils/logger_manager/loggers/sub_logger.py +105 -0
- bear_utils/logger_manager/loggers/sub_logger.pyi +23 -0
- bear_utils/monitoring/__init__.py +13 -0
- bear_utils/monitoring/_common.py +28 -0
- bear_utils/monitoring/host_monitor.py +346 -0
- bear_utils/time/__init__.py +59 -0
- bear_utils-0.0.1.dist-info/METADATA +305 -0
- bear_utils-0.0.1.dist-info/RECORD +107 -0
- bear_utils-0.0.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
from bear_utils.constants._meta import IntValue as Value, RichIntEnum
|
2
|
+
|
3
|
+
|
4
|
+
class ExitCode(RichIntEnum):
|
5
|
+
"""An enumeration of common exit codes used in shell commands."""
|
6
|
+
|
7
|
+
SUCCESS = Value(0, "Success")
|
8
|
+
FAILURE = Value(1, "General error")
|
9
|
+
MISUSE_OF_SHELL_COMMAND = Value(2, "Misuse of shell command")
|
10
|
+
COMMAND_CANNOT_EXECUTE = Value(126, "Command invoked cannot execute")
|
11
|
+
COMMAND_NOT_FOUND = Value(127, "Command not found")
|
12
|
+
INVALID_ARGUMENT_TO_EXIT = Value(128, "Invalid argument to exit")
|
13
|
+
SCRIPT_TERMINATED_BY_CONTROL_C = Value(130, "Script terminated by Control-C")
|
14
|
+
PROCESS_KILLED_BY_SIGKILL = Value(137, "Process killed by SIGKILL (9)")
|
15
|
+
SEGMENTATION_FAULT = Value(139, "Segmentation fault (core dumped)")
|
16
|
+
PROCESS_TERMINATED_BY_SIGTERM = Value(143, "Process terminated by SIGTERM (15)")
|
17
|
+
EXIT_STATUS_OUT_OF_RANGE = Value(255, "Exit status out of range")
|
18
|
+
|
19
|
+
|
20
|
+
SUCCESS = ExitCode.SUCCESS
|
21
|
+
"""An exit code indicating success."""
|
22
|
+
FAIL = ExitCode.FAILURE
|
23
|
+
"""Deprecated alias for ExitCode.FAILURE."""
|
24
|
+
FAILURE = ExitCode.FAILURE
|
25
|
+
"""An exit code indicating a general error."""
|
26
|
+
MISUSE_OF_SHELL_COMMAND = ExitCode.MISUSE_OF_SHELL_COMMAND
|
27
|
+
"""An exit code indicating misuse of a shell command."""
|
28
|
+
COMMAND_CANNOT_EXECUTE = ExitCode.COMMAND_CANNOT_EXECUTE
|
29
|
+
"""An exit code indicating that the command invoked cannot execute."""
|
30
|
+
COMMAND_NOT_FOUND = ExitCode.COMMAND_NOT_FOUND
|
31
|
+
"""An exit code indicating that the command was not found."""
|
32
|
+
INVALID_ARGUMENT_TO_EXIT = ExitCode.INVALID_ARGUMENT_TO_EXIT
|
33
|
+
"""An exit code indicating an invalid argument to exit."""
|
34
|
+
SCRIPT_TERMINATED_BY_CONTROL_C = ExitCode.SCRIPT_TERMINATED_BY_CONTROL_C
|
35
|
+
"""An exit code indicating that the script was terminated by Control-C."""
|
36
|
+
PROCESS_KILLED_BY_SIGKILL = ExitCode.PROCESS_KILLED_BY_SIGKILL
|
37
|
+
"""An exit code indicating that the process was killed by SIGKILL (9)."""
|
38
|
+
SEGMENTATION_FAULT = ExitCode.SEGMENTATION_FAULT
|
39
|
+
"""An exit code indicating a segmentation fault (core dumped)."""
|
40
|
+
PROCESS_TERMINATED_BY_SIGTERM = ExitCode.PROCESS_TERMINATED_BY_SIGTERM
|
41
|
+
"""An exit code indicating that the process was terminated by SIGTERM (15)."""
|
42
|
+
EXIT_STATUS_OUT_OF_RANGE = ExitCode.EXIT_STATUS_OUT_OF_RANGE
|
43
|
+
"""An exit code indicating that the exit status is out of range."""
|
44
|
+
|
45
|
+
|
46
|
+
__all__ = [
|
47
|
+
"COMMAND_CANNOT_EXECUTE",
|
48
|
+
"COMMAND_NOT_FOUND",
|
49
|
+
"EXIT_STATUS_OUT_OF_RANGE",
|
50
|
+
"FAIL",
|
51
|
+
"FAILURE",
|
52
|
+
"INVALID_ARGUMENT_TO_EXIT",
|
53
|
+
"MISUSE_OF_SHELL_COMMAND",
|
54
|
+
"PROCESS_KILLED_BY_SIGKILL",
|
55
|
+
"PROCESS_TERMINATED_BY_SIGTERM",
|
56
|
+
"SCRIPT_TERMINATED_BY_CONTROL_C",
|
57
|
+
"SEGMENTATION_FAULT",
|
58
|
+
"SUCCESS",
|
59
|
+
"ExitCode",
|
60
|
+
]
|
@@ -0,0 +1,37 @@
|
|
1
|
+
"""HTTP status codes."""
|
2
|
+
|
3
|
+
from bear_utils.constants._meta import IntValue as Value, RichIntEnum
|
4
|
+
|
5
|
+
|
6
|
+
class HTTPStatusCode(RichIntEnum):
|
7
|
+
"""An enumeration of common HTTP status codes."""
|
8
|
+
|
9
|
+
SERVER_ERROR = Value(500, "Internal Server Error")
|
10
|
+
SERVER_OK = Value(200, "OK")
|
11
|
+
PAGE_NOT_FOUND = Value(404, "Not Found")
|
12
|
+
BAD_REQUEST = Value(400, "Bad Request")
|
13
|
+
UNPROCESSABLE_CONTENT = Value(422, "Unprocessable Content")
|
14
|
+
UNAUTHORIZED = Value(401, "Unauthorized")
|
15
|
+
FORBIDDEN = Value(403, "Forbidden")
|
16
|
+
CONFLICT = Value(409, "Conflict")
|
17
|
+
METHOD_NOT_ALLOWED = Value(405, "Method Not Allowed")
|
18
|
+
|
19
|
+
|
20
|
+
SERVER_ERROR = HTTPStatusCode.SERVER_ERROR
|
21
|
+
"""Internal Server Error"""
|
22
|
+
SERVER_OK = HTTPStatusCode.SERVER_OK
|
23
|
+
"""OK"""
|
24
|
+
PAGE_NOT_FOUND = HTTPStatusCode.PAGE_NOT_FOUND
|
25
|
+
"""Not Found"""
|
26
|
+
BAD_REQUEST = HTTPStatusCode.BAD_REQUEST
|
27
|
+
"""Bad Request"""
|
28
|
+
UNPROCESSABLE_CONTENT = HTTPStatusCode.UNPROCESSABLE_CONTENT
|
29
|
+
"""Unprocessable Content"""
|
30
|
+
UNAUTHORIZED = HTTPStatusCode.UNAUTHORIZED
|
31
|
+
"""Unauthorized"""
|
32
|
+
FORBIDDEN = HTTPStatusCode.FORBIDDEN
|
33
|
+
"""Forbidden"""
|
34
|
+
CONFLICT = HTTPStatusCode.CONFLICT
|
35
|
+
"""Conflict"""
|
36
|
+
METHOD_NOT_ALLOWED = HTTPStatusCode.METHOD_NOT_ALLOWED
|
37
|
+
"""Method Not Allowed"""
|
@@ -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,196 @@
|
|
1
|
+
from contextlib import suppress
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from enum import IntEnum, StrEnum
|
4
|
+
from typing import Any, Self, TextIO, overload
|
5
|
+
|
6
|
+
|
7
|
+
@dataclass(frozen=True)
|
8
|
+
class IntValue:
|
9
|
+
"""A frozen dataclass for holding constant integer values."""
|
10
|
+
|
11
|
+
value: int
|
12
|
+
text: str
|
13
|
+
default: int = 0
|
14
|
+
|
15
|
+
|
16
|
+
@dataclass(frozen=True)
|
17
|
+
class StrValue:
|
18
|
+
"""A frozen dataclass for holding constant string values."""
|
19
|
+
|
20
|
+
value: str
|
21
|
+
text: str
|
22
|
+
default: str = ""
|
23
|
+
|
24
|
+
|
25
|
+
class RichStrEnum(StrEnum):
|
26
|
+
"""Base class for StrEnums with rich metadata."""
|
27
|
+
|
28
|
+
text: str
|
29
|
+
default: str
|
30
|
+
|
31
|
+
def __new__(cls, value: StrValue) -> Self:
|
32
|
+
obj: Self = str.__new__(cls, value.value)
|
33
|
+
obj._value_ = value.value
|
34
|
+
obj.text = value.text
|
35
|
+
obj.default = value.default
|
36
|
+
return obj
|
37
|
+
|
38
|
+
@classmethod
|
39
|
+
def keys(cls) -> list[str]:
|
40
|
+
"""Return a list of all enum member names."""
|
41
|
+
return [item.name for item in cls]
|
42
|
+
|
43
|
+
@overload
|
44
|
+
@classmethod
|
45
|
+
def get(cls, value: str | Self, default: Self) -> Self: ...
|
46
|
+
|
47
|
+
@overload
|
48
|
+
@classmethod
|
49
|
+
def get(cls, value: str | Self, default: None = None) -> None: ...
|
50
|
+
|
51
|
+
@classmethod
|
52
|
+
def get(cls, value: str | Self, default: Self | None = None) -> Self | None:
|
53
|
+
"""Try to get an enum member by its value or name."""
|
54
|
+
if isinstance(value, cls):
|
55
|
+
return value
|
56
|
+
with suppress(ValueError):
|
57
|
+
if isinstance(value, str):
|
58
|
+
return cls.from_text(value)
|
59
|
+
return default
|
60
|
+
|
61
|
+
@classmethod
|
62
|
+
def from_text(cls, text: str) -> Self:
|
63
|
+
"""Convert a string text to its corresponding enum member."""
|
64
|
+
for item in cls:
|
65
|
+
if item.text == text:
|
66
|
+
return item
|
67
|
+
raise ValueError(f"Text {text} not found in {cls.__name__}")
|
68
|
+
|
69
|
+
@classmethod
|
70
|
+
def from_name(cls, name: str) -> Self:
|
71
|
+
"""Convert a string name to its corresponding enum member."""
|
72
|
+
try:
|
73
|
+
return cls[name.upper()]
|
74
|
+
except KeyError as e:
|
75
|
+
raise ValueError(f"Name {name} not found in {cls.__name__}") from e
|
76
|
+
|
77
|
+
def __str__(self) -> str:
|
78
|
+
"""Return a string representation of the enum."""
|
79
|
+
return self.value
|
80
|
+
|
81
|
+
def str(self) -> str:
|
82
|
+
"""Return the string value of the enum."""
|
83
|
+
return self.value
|
84
|
+
|
85
|
+
|
86
|
+
class RichIntEnum(IntEnum):
|
87
|
+
"""Base class for IntEnums with rich metadata."""
|
88
|
+
|
89
|
+
text: str
|
90
|
+
default: int
|
91
|
+
|
92
|
+
def __new__(cls, value: IntValue) -> Self:
|
93
|
+
obj: Self = int.__new__(cls, value.value)
|
94
|
+
obj._value_ = value.value
|
95
|
+
obj.text = value.text
|
96
|
+
obj.default = value.default
|
97
|
+
return obj
|
98
|
+
|
99
|
+
def __int__(self) -> int:
|
100
|
+
"""Return the integer value of the enum."""
|
101
|
+
return self.value
|
102
|
+
|
103
|
+
def __str__(self) -> str:
|
104
|
+
"""Return a string representation of the enum."""
|
105
|
+
return f"{self.name} ({self.value}): {self.text}"
|
106
|
+
|
107
|
+
@classmethod
|
108
|
+
def keys(cls) -> list[str]:
|
109
|
+
"""Return a list of all enum member names."""
|
110
|
+
return [item.name for item in cls]
|
111
|
+
|
112
|
+
@overload
|
113
|
+
@classmethod
|
114
|
+
def get(cls, value: str | int | Self, default: Self) -> Self: ...
|
115
|
+
|
116
|
+
@overload
|
117
|
+
@classmethod
|
118
|
+
def get(cls, value: str | int | Self, default: None = None) -> None: ...
|
119
|
+
|
120
|
+
@classmethod
|
121
|
+
def get(cls, value: str | int | Self | Any, default: Self | None = None) -> Self | None:
|
122
|
+
"""Try to get an enum member by its value, name, or text."""
|
123
|
+
if isinstance(value, cls):
|
124
|
+
return value
|
125
|
+
with suppress(ValueError):
|
126
|
+
if isinstance(value, int):
|
127
|
+
return cls.from_int(value)
|
128
|
+
if isinstance(value, str):
|
129
|
+
return cls.from_name(value)
|
130
|
+
return default
|
131
|
+
|
132
|
+
@classmethod
|
133
|
+
def from_name(cls, name: str) -> Self:
|
134
|
+
"""Convert a string name to its corresponding enum member."""
|
135
|
+
try:
|
136
|
+
return cls[name.upper()]
|
137
|
+
except KeyError as e:
|
138
|
+
raise ValueError(f"Name {name} not found in {cls.__name__}") from e
|
139
|
+
|
140
|
+
@classmethod
|
141
|
+
def from_int(cls, code: int) -> Self:
|
142
|
+
"""Convert an integer to its corresponding enum member."""
|
143
|
+
for item in cls:
|
144
|
+
if item.value == code:
|
145
|
+
return item
|
146
|
+
raise ValueError(f"Value {code} not found in {cls.__name__}")
|
147
|
+
|
148
|
+
@classmethod
|
149
|
+
def int_to_text(cls, code: int) -> str:
|
150
|
+
"""Convert an integer to its text representation."""
|
151
|
+
try:
|
152
|
+
return cls.from_int(code).text
|
153
|
+
except ValueError:
|
154
|
+
return "Unknown value"
|
155
|
+
|
156
|
+
|
157
|
+
class MockTextIO(TextIO):
|
158
|
+
"""A mock TextIO class that captures written output for testing purposes."""
|
159
|
+
|
160
|
+
def __init__(self) -> None:
|
161
|
+
"""Initialize the mock TextIO."""
|
162
|
+
self._buffer: list[str] = []
|
163
|
+
|
164
|
+
def write(self, _s: str, *_) -> None: # type: ignore[override]
|
165
|
+
"""Mock write method that appends to the buffer."""
|
166
|
+
if _s == "\n":
|
167
|
+
return
|
168
|
+
self._buffer.append(_s)
|
169
|
+
|
170
|
+
def output_buffer(self) -> list[str]:
|
171
|
+
"""Get the output buffer."""
|
172
|
+
return self._buffer
|
173
|
+
|
174
|
+
def clear(self) -> None:
|
175
|
+
"""Clear the output buffer."""
|
176
|
+
self._buffer.clear()
|
177
|
+
|
178
|
+
def flush(self) -> None:
|
179
|
+
"""Mock flush method that does nothing."""
|
180
|
+
|
181
|
+
|
182
|
+
class NullFile(TextIO):
|
183
|
+
"""A class that acts as a null file, discarding all writes."""
|
184
|
+
|
185
|
+
def write(self, _s: str, *_: Any) -> None: # type: ignore[override]
|
186
|
+
"""Discard the string written to this null file."""
|
187
|
+
|
188
|
+
def flush(self) -> None:
|
189
|
+
"""Flush the null file (no operation)."""
|
190
|
+
|
191
|
+
def __enter__(self) -> Self:
|
192
|
+
"""Enter context manager and return self."""
|
193
|
+
return self
|
194
|
+
|
195
|
+
def __exit__(self, *_: object) -> None:
|
196
|
+
"""Exit context manager (no operation)."""
|
@@ -0,0 +1,25 @@
|
|
1
|
+
"""A module containing constants related to date and time formatting."""
|
2
|
+
|
3
|
+
from bear_utils.time import (
|
4
|
+
DATE_FORMAT,
|
5
|
+
DATE_TIME_FORMAT,
|
6
|
+
DT_FORMAT_WITH_SECONDS,
|
7
|
+
DT_FORMAT_WITH_TZ,
|
8
|
+
DT_FORMAT_WITH_TZ_AND_SECONDS,
|
9
|
+
ET_TIME_ZONE,
|
10
|
+
PT_TIME_ZONE,
|
11
|
+
TIME_FORMAT_WITH_SECONDS,
|
12
|
+
UTC_TIME_ZONE,
|
13
|
+
)
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
"DATE_FORMAT",
|
17
|
+
"DATE_TIME_FORMAT",
|
18
|
+
"DT_FORMAT_WITH_SECONDS",
|
19
|
+
"DT_FORMAT_WITH_TZ",
|
20
|
+
"DT_FORMAT_WITH_TZ_AND_SECONDS",
|
21
|
+
"ET_TIME_ZONE",
|
22
|
+
"PT_TIME_ZONE",
|
23
|
+
"TIME_FORMAT_WITH_SECONDS",
|
24
|
+
"UTC_TIME_ZONE",
|
25
|
+
]
|
@@ -0,0 +1,24 @@
|
|
1
|
+
"""A module containing constants related to time calculations."""
|
2
|
+
|
3
|
+
from typing import Literal
|
4
|
+
|
5
|
+
MINUTES_IN_HOUR: Literal[60] = 60
|
6
|
+
"""60 minutes in an hour"""
|
7
|
+
|
8
|
+
HOURS_IN_DAY: Literal[24] = 24
|
9
|
+
"""24 hours in a day"""
|
10
|
+
|
11
|
+
DAYS_IN_MONTH: Literal[30] = 30
|
12
|
+
"""30 days in a month, approximation for a month"""
|
13
|
+
|
14
|
+
SECONDS_IN_MINUTE: Literal[60] = 60
|
15
|
+
"""60 seconds in a minute"""
|
16
|
+
|
17
|
+
SECONDS_IN_HOUR: Literal[3600] = SECONDS_IN_MINUTE * MINUTES_IN_HOUR
|
18
|
+
"""60 * 60 = 3600 seconds in an hour"""
|
19
|
+
|
20
|
+
SECONDS_IN_DAY: Literal[86400] = SECONDS_IN_HOUR * HOURS_IN_DAY
|
21
|
+
"""24 * 60 * 60 = 86400 seconds in a day"""
|
22
|
+
|
23
|
+
SECONDS_IN_MONTH: Literal[2592000] = SECONDS_IN_DAY * DAYS_IN_MONTH
|
24
|
+
"""30 * 24 * 60 * 60 = 2592000 seconds in a month"""
|
@@ -0,0 +1,98 @@
|
|
1
|
+
"""Database Manager Module for managing database connections and operations."""
|
2
|
+
|
3
|
+
import atexit
|
4
|
+
from collections.abc import Generator
|
5
|
+
from contextlib import contextmanager
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import Any, ClassVar
|
8
|
+
|
9
|
+
from singleton_base import SingletonBase
|
10
|
+
from sqlalchemy import Engine, MetaData, create_engine
|
11
|
+
from sqlalchemy.ext.declarative import DeclarativeMeta
|
12
|
+
from sqlalchemy.orm import declarative_base, scoped_session, sessionmaker
|
13
|
+
from sqlalchemy.orm.session import Session
|
14
|
+
|
15
|
+
from bear_utils.constants._lazy_typing import TableType
|
16
|
+
|
17
|
+
|
18
|
+
class DatabaseManager:
|
19
|
+
_base: ClassVar[DeclarativeMeta | None] = None
|
20
|
+
|
21
|
+
@classmethod
|
22
|
+
def set_base(cls, base: DeclarativeMeta) -> None:
|
23
|
+
"""Set the base class for the database manager."""
|
24
|
+
cls._base = base
|
25
|
+
|
26
|
+
@classmethod
|
27
|
+
def get_base(cls) -> DeclarativeMeta:
|
28
|
+
"""Get the base class for the database manager."""
|
29
|
+
if cls._base is None:
|
30
|
+
cls.set_base(declarative_base())
|
31
|
+
if cls._base is None:
|
32
|
+
raise ValueError("Base class is not set, failed to set base.")
|
33
|
+
return cls._base
|
34
|
+
|
35
|
+
def __init__(self, db_url: str | Path | None = None, default_schema: str = "sqlite:///"):
|
36
|
+
if db_url is None or db_url == "":
|
37
|
+
raise ValueError("Database URL cannot be None or empty.")
|
38
|
+
if isinstance(db_url, str) and not db_url.startswith(default_schema):
|
39
|
+
db_url = f"{default_schema}{db_url}"
|
40
|
+
self.db_url: str = str(db_url)
|
41
|
+
self.engine: Engine = create_engine(self.db_url, echo=False)
|
42
|
+
base: DeclarativeMeta = DatabaseManager.get_base()
|
43
|
+
self.metadata: MetaData = base.metadata
|
44
|
+
self.SessionFactory: sessionmaker[Session] = sessionmaker(bind=self.engine)
|
45
|
+
self.session: scoped_session[Session] = scoped_session(self.SessionFactory)
|
46
|
+
atexit.register(self.close_all)
|
47
|
+
self.create_tables()
|
48
|
+
|
49
|
+
def get_all_records(self, table_obj: type[TableType]) -> list[TableType]:
|
50
|
+
"""Get all records from a table."""
|
51
|
+
return self.session().query(table_obj).all()
|
52
|
+
|
53
|
+
def count_records(self, table_obj: type[TableType]) -> int:
|
54
|
+
"""Count the number of records in a table."""
|
55
|
+
return self.session().query(table_obj).count()
|
56
|
+
|
57
|
+
def get_records_by_var(self, table_obj: type[TableType], variable: str, value: str) -> list[TableType]:
|
58
|
+
"""Get records from a table by a specific variable."""
|
59
|
+
return self.session().query(table_obj).filter(getattr(table_obj, variable) == value).all()
|
60
|
+
|
61
|
+
def count_records_by_var(self, table_obj: type[TableType], variable: str, value: str) -> int:
|
62
|
+
"""Count the number of records in a table by a specific variable."""
|
63
|
+
return self.session().query(table_obj).filter(getattr(table_obj, variable) == value).count()
|
64
|
+
|
65
|
+
@contextmanager
|
66
|
+
def open_session(self) -> Generator[Session, Any]:
|
67
|
+
"""Provide a transactional scope around a series of operations."""
|
68
|
+
session: Session = self.session()
|
69
|
+
try:
|
70
|
+
yield session
|
71
|
+
session.commit()
|
72
|
+
except Exception:
|
73
|
+
session.rollback()
|
74
|
+
raise
|
75
|
+
|
76
|
+
def get_session(self) -> Session:
|
77
|
+
"""Get a new session."""
|
78
|
+
return self.session()
|
79
|
+
|
80
|
+
def close_session(self) -> None:
|
81
|
+
"""Close the session."""
|
82
|
+
self.session.remove()
|
83
|
+
|
84
|
+
def create_tables(self) -> None:
|
85
|
+
"""Create all tables defined by Base"""
|
86
|
+
self.metadata.create_all(self.engine)
|
87
|
+
|
88
|
+
def close_all(self) -> None:
|
89
|
+
"""Close all sessions and connections."""
|
90
|
+
self.session.close()
|
91
|
+
self.engine.dispose()
|
92
|
+
|
93
|
+
|
94
|
+
class SingletonDB(DatabaseManager, SingletonBase):
|
95
|
+
"""Singleton class for DatabaseManager, uses SingletonBase to inject singleton pattern."""
|
96
|
+
|
97
|
+
|
98
|
+
__all__ = ["DatabaseManager", "SingletonDB"]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
"""A module for event handling in Bear Utils."""
|
2
|
+
|
3
|
+
from .events_class import Events
|
4
|
+
from .events_module import clear_all, clear_handlers_for_event, dispatch_event, event_handler, set_handler
|
5
|
+
|
6
|
+
subscribe = event_handler
|
7
|
+
publish = dispatch_event
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"Events",
|
11
|
+
"clear_all",
|
12
|
+
"clear_handlers_for_event",
|
13
|
+
"dispatch_event",
|
14
|
+
"event_handler",
|
15
|
+
"publish",
|
16
|
+
"set_handler",
|
17
|
+
"subscribe",
|
18
|
+
]
|
@@ -0,0 +1,52 @@
|
|
1
|
+
"""A module for event handling in Bear Utils."""
|
2
|
+
|
3
|
+
from collections.abc import Callable
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
from .events_module import (
|
7
|
+
clear_all as _clear_all,
|
8
|
+
clear_handlers_for_event as _clear_handlers_for_event,
|
9
|
+
dispatch_event as _dispatch_event,
|
10
|
+
event_handler as _event_handler,
|
11
|
+
set_handler as _set_handler,
|
12
|
+
)
|
13
|
+
|
14
|
+
Callback = Callable[..., Any]
|
15
|
+
|
16
|
+
|
17
|
+
class Events:
|
18
|
+
"""Simple wrapper exposing :mod:`events_module` functionality as methods."""
|
19
|
+
|
20
|
+
# Method names mirror functions from ``events_module`` for familiarity
|
21
|
+
|
22
|
+
def event_handler(self, event_name: str, func: Callback | None = None):
|
23
|
+
"""Register ``func`` as a handler for ``event_name``.
|
24
|
+
|
25
|
+
Can be used as a decorator when ``func`` is omitted.
|
26
|
+
"""
|
27
|
+
if func is None:
|
28
|
+
return _event_handler(event_name)
|
29
|
+
_set_handler(event_name, func)
|
30
|
+
return func
|
31
|
+
|
32
|
+
def dispatch_event(self, event_name: str, *args, **kwargs) -> Any | None:
|
33
|
+
"""Dispatch ``event_name`` to all subscribed handlers."""
|
34
|
+
return _dispatch_event(event_name, *args, **kwargs)
|
35
|
+
|
36
|
+
def set_handler(self, event_name: str, func: Callback) -> None:
|
37
|
+
"""Register ``func`` as a handler for ``event_name``."""
|
38
|
+
_set_handler(event_name, func)
|
39
|
+
|
40
|
+
def clear_handlers_for_event(self, event_name: str) -> None:
|
41
|
+
"""Remove all handlers associated with ``event_name``."""
|
42
|
+
_clear_handlers_for_event(event_name)
|
43
|
+
|
44
|
+
def clear_all(self) -> None:
|
45
|
+
"""Remove all registered event handlers."""
|
46
|
+
_clear_all()
|
47
|
+
|
48
|
+
subscribe = event_handler
|
49
|
+
publish = dispatch_event
|
50
|
+
|
51
|
+
|
52
|
+
__all__ = ["Events"]
|
@@ -0,0 +1,74 @@
|
|
1
|
+
"""Event handling module for Bear Utils."""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
from collections import defaultdict
|
5
|
+
from collections.abc import Callable
|
6
|
+
from functools import wraps
|
7
|
+
from types import MethodType
|
8
|
+
from typing import Any
|
9
|
+
import weakref
|
10
|
+
from weakref import WeakMethod, ref
|
11
|
+
|
12
|
+
from bear_utils.extras._async_helpers import is_async_function
|
13
|
+
|
14
|
+
Callback = Callable[..., Any]
|
15
|
+
|
16
|
+
_event_registry: dict[str, weakref.WeakSet[Callback]] = defaultdict(weakref.WeakSet)
|
17
|
+
|
18
|
+
|
19
|
+
def clear_handlers_for_event(event_name: str) -> None:
|
20
|
+
"""Remove all handlers associated with a specific event."""
|
21
|
+
_event_registry.pop(event_name, None)
|
22
|
+
|
23
|
+
|
24
|
+
def clear_all() -> None:
|
25
|
+
"""Remove all registered event handlers."""
|
26
|
+
_event_registry.clear()
|
27
|
+
|
28
|
+
|
29
|
+
def _make_callback(name: str) -> Callable[[Any], None]:
|
30
|
+
"""Create an internal callback to remove dead handlers."""
|
31
|
+
|
32
|
+
def callback(weak_method: Any) -> None:
|
33
|
+
_event_registry[name].remove(weak_method)
|
34
|
+
if not _event_registry[name]:
|
35
|
+
del _event_registry[name]
|
36
|
+
|
37
|
+
return callback
|
38
|
+
|
39
|
+
|
40
|
+
def set_handler(name: str, func: Callback) -> None:
|
41
|
+
"""Register a function as a handler for a specific event."""
|
42
|
+
if isinstance(func, MethodType):
|
43
|
+
_event_registry[name].add(WeakMethod(func, _make_callback(name)))
|
44
|
+
else:
|
45
|
+
_event_registry[name].add(ref(func, _make_callback(name)))
|
46
|
+
|
47
|
+
|
48
|
+
def dispatch_event(name: str, *args, **kwargs) -> Any | None:
|
49
|
+
"""Dispatch an event to all registered handlers."""
|
50
|
+
results: list[Any] = []
|
51
|
+
for func in _event_registry.get(name, []):
|
52
|
+
if is_async_function(func):
|
53
|
+
result: Any = asyncio.run(func(*args, **kwargs)) # FIXME: This will crash if called from an async context
|
54
|
+
else:
|
55
|
+
result: Any = func(*args, **kwargs)
|
56
|
+
results.append(result)
|
57
|
+
if not results:
|
58
|
+
return None
|
59
|
+
return results[0] if len(results) == 1 else results
|
60
|
+
|
61
|
+
|
62
|
+
def event_handler(event_name: str) -> Callable[[Callback], Callback]:
|
63
|
+
"""Decorator to register a callback as an event handler for a specific event."""
|
64
|
+
|
65
|
+
def decorator(callback: Callback) -> Callback:
|
66
|
+
@wraps(callback)
|
67
|
+
def wrapper(*args, **kwargs) -> Any:
|
68
|
+
"""Wrapper to register the callback and call it."""
|
69
|
+
return callback(*args, **kwargs)
|
70
|
+
|
71
|
+
set_handler(event_name, wrapper)
|
72
|
+
return wrapper
|
73
|
+
|
74
|
+
return decorator
|
@@ -0,0 +1,28 @@
|
|
1
|
+
"""A module for various utilities in Bear Utils extras."""
|
2
|
+
|
3
|
+
from singleton_base import SingletonBase
|
4
|
+
|
5
|
+
from ._tools import ClipboardManager, ascii_header, clear_clipboard, copy_to_clipboard, paste_from_clipboard
|
6
|
+
from ._zapper import zap, zap_as, zap_as_multi, zap_get, zap_multi
|
7
|
+
from .platform_utils import OS, get_platform, is_linux, is_macos, is_windows
|
8
|
+
from .wrappers.add_methods import add_comparison_methods
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
"OS",
|
12
|
+
"ClipboardManager",
|
13
|
+
"SingletonBase",
|
14
|
+
"add_comparison_methods",
|
15
|
+
"ascii_header",
|
16
|
+
"clear_clipboard",
|
17
|
+
"copy_to_clipboard",
|
18
|
+
"get_platform",
|
19
|
+
"is_linux",
|
20
|
+
"is_macos",
|
21
|
+
"is_windows",
|
22
|
+
"paste_from_clipboard",
|
23
|
+
"zap",
|
24
|
+
"zap_as",
|
25
|
+
"zap_as_multi",
|
26
|
+
"zap_get",
|
27
|
+
"zap_multi",
|
28
|
+
]
|