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.
Files changed (107) hide show
  1. bear_utils/__init__.py +51 -0
  2. bear_utils/__main__.py +14 -0
  3. bear_utils/_internal/__init__.py +0 -0
  4. bear_utils/_internal/_version.py +1 -0
  5. bear_utils/_internal/cli.py +119 -0
  6. bear_utils/_internal/debug.py +174 -0
  7. bear_utils/ai/__init__.py +30 -0
  8. bear_utils/ai/ai_helpers/__init__.py +136 -0
  9. bear_utils/ai/ai_helpers/_common.py +19 -0
  10. bear_utils/ai/ai_helpers/_config.py +24 -0
  11. bear_utils/ai/ai_helpers/_parsers.py +194 -0
  12. bear_utils/ai/ai_helpers/_types.py +15 -0
  13. bear_utils/cache/__init__.py +131 -0
  14. bear_utils/cli/__init__.py +22 -0
  15. bear_utils/cli/_args.py +12 -0
  16. bear_utils/cli/_get_version.py +207 -0
  17. bear_utils/cli/commands.py +105 -0
  18. bear_utils/cli/prompt_helpers.py +186 -0
  19. bear_utils/cli/shell/__init__.py +1 -0
  20. bear_utils/cli/shell/_base_command.py +81 -0
  21. bear_utils/cli/shell/_base_shell.py +430 -0
  22. bear_utils/cli/shell/_common.py +19 -0
  23. bear_utils/cli/typer_bridge.py +90 -0
  24. bear_utils/config/__init__.py +13 -0
  25. bear_utils/config/config_manager.py +229 -0
  26. bear_utils/config/dir_manager.py +69 -0
  27. bear_utils/config/settings_manager.py +179 -0
  28. bear_utils/constants/__init__.py +90 -0
  29. bear_utils/constants/_exceptions.py +8 -0
  30. bear_utils/constants/_exit_code.py +60 -0
  31. bear_utils/constants/_http_status_code.py +37 -0
  32. bear_utils/constants/_lazy_typing.py +15 -0
  33. bear_utils/constants/_meta.py +196 -0
  34. bear_utils/constants/date_related.py +25 -0
  35. bear_utils/constants/time_related.py +24 -0
  36. bear_utils/database/__init__.py +8 -0
  37. bear_utils/database/_db_manager.py +98 -0
  38. bear_utils/events/__init__.py +18 -0
  39. bear_utils/events/events_class.py +52 -0
  40. bear_utils/events/events_module.py +74 -0
  41. bear_utils/extras/__init__.py +28 -0
  42. bear_utils/extras/_async_helpers.py +67 -0
  43. bear_utils/extras/_tools.py +185 -0
  44. bear_utils/extras/_zapper.py +399 -0
  45. bear_utils/extras/platform_utils.py +57 -0
  46. bear_utils/extras/responses/__init__.py +5 -0
  47. bear_utils/extras/responses/function_response.py +451 -0
  48. bear_utils/extras/wrappers/__init__.py +1 -0
  49. bear_utils/extras/wrappers/add_methods.py +100 -0
  50. bear_utils/extras/wrappers/string_io.py +46 -0
  51. bear_utils/files/__init__.py +6 -0
  52. bear_utils/files/file_handlers/__init__.py +5 -0
  53. bear_utils/files/file_handlers/_base_file_handler.py +107 -0
  54. bear_utils/files/file_handlers/file_handler_factory.py +280 -0
  55. bear_utils/files/file_handlers/json_file_handler.py +71 -0
  56. bear_utils/files/file_handlers/log_file_handler.py +40 -0
  57. bear_utils/files/file_handlers/toml_file_handler.py +76 -0
  58. bear_utils/files/file_handlers/txt_file_handler.py +76 -0
  59. bear_utils/files/file_handlers/yaml_file_handler.py +64 -0
  60. bear_utils/files/ignore_parser.py +293 -0
  61. bear_utils/graphics/__init__.py +6 -0
  62. bear_utils/graphics/bear_gradient.py +145 -0
  63. bear_utils/graphics/font/__init__.py +13 -0
  64. bear_utils/graphics/font/_raw_block_letters.py +463 -0
  65. bear_utils/graphics/font/_theme.py +31 -0
  66. bear_utils/graphics/font/_utils.py +220 -0
  67. bear_utils/graphics/font/block_font.py +192 -0
  68. bear_utils/graphics/font/glitch_font.py +63 -0
  69. bear_utils/graphics/image_helpers.py +45 -0
  70. bear_utils/gui/__init__.py +8 -0
  71. bear_utils/gui/gui_tools/__init__.py +10 -0
  72. bear_utils/gui/gui_tools/_settings.py +36 -0
  73. bear_utils/gui/gui_tools/_types.py +12 -0
  74. bear_utils/gui/gui_tools/qt_app.py +150 -0
  75. bear_utils/gui/gui_tools/qt_color_picker.py +130 -0
  76. bear_utils/gui/gui_tools/qt_file_handler.py +130 -0
  77. bear_utils/gui/gui_tools/qt_input_dialog.py +303 -0
  78. bear_utils/logger_manager/__init__.py +109 -0
  79. bear_utils/logger_manager/_common.py +63 -0
  80. bear_utils/logger_manager/_console_junk.py +135 -0
  81. bear_utils/logger_manager/_log_level.py +50 -0
  82. bear_utils/logger_manager/_styles.py +95 -0
  83. bear_utils/logger_manager/logger_protocol.py +42 -0
  84. bear_utils/logger_manager/loggers/__init__.py +1 -0
  85. bear_utils/logger_manager/loggers/_console.py +223 -0
  86. bear_utils/logger_manager/loggers/_level_sin.py +61 -0
  87. bear_utils/logger_manager/loggers/_logger.py +19 -0
  88. bear_utils/logger_manager/loggers/base_logger.py +244 -0
  89. bear_utils/logger_manager/loggers/base_logger.pyi +51 -0
  90. bear_utils/logger_manager/loggers/basic_logger/__init__.py +5 -0
  91. bear_utils/logger_manager/loggers/basic_logger/logger.py +80 -0
  92. bear_utils/logger_manager/loggers/basic_logger/logger.pyi +19 -0
  93. bear_utils/logger_manager/loggers/buffer_logger.py +57 -0
  94. bear_utils/logger_manager/loggers/console_logger.py +278 -0
  95. bear_utils/logger_manager/loggers/console_logger.pyi +50 -0
  96. bear_utils/logger_manager/loggers/fastapi_logger.py +333 -0
  97. bear_utils/logger_manager/loggers/file_logger.py +151 -0
  98. bear_utils/logger_manager/loggers/simple_logger.py +98 -0
  99. bear_utils/logger_manager/loggers/sub_logger.py +105 -0
  100. bear_utils/logger_manager/loggers/sub_logger.pyi +23 -0
  101. bear_utils/monitoring/__init__.py +13 -0
  102. bear_utils/monitoring/_common.py +28 -0
  103. bear_utils/monitoring/host_monitor.py +346 -0
  104. bear_utils/time/__init__.py +59 -0
  105. bear_utils-0.0.1.dist-info/METADATA +305 -0
  106. bear_utils-0.0.1.dist-info/RECORD +107 -0
  107. bear_utils-0.0.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,8 @@
1
+ """Custom exceptions for the application."""
2
+
3
+
4
+ class UserCancelledError(Exception):
5
+ """Exception raised when a user cancels an operation."""
6
+
7
+ def __init__(self, message: str = "User cancelled the operation"):
8
+ super().__init__(message)
@@ -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,8 @@
1
+ """Database Manager Module for managing database connections and operations."""
2
+
3
+ from ._db_manager import DatabaseManager, SingletonDB
4
+
5
+ __all__ = [
6
+ "DatabaseManager",
7
+ "SingletonDB",
8
+ ]
@@ -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
+ ]