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,57 @@
|
|
1
|
+
"""A module for detecting the current operating system."""
|
2
|
+
|
3
|
+
from enum import StrEnum
|
4
|
+
import platform
|
5
|
+
|
6
|
+
|
7
|
+
class OS(StrEnum):
|
8
|
+
"""Enumeration of operating systems."""
|
9
|
+
|
10
|
+
DARWIN = "Darwin"
|
11
|
+
LINUX = "Linux"
|
12
|
+
WINDOWS = "Windows"
|
13
|
+
OTHER = "Other"
|
14
|
+
|
15
|
+
|
16
|
+
DARWIN = OS.DARWIN
|
17
|
+
LINUX = OS.LINUX
|
18
|
+
WINDOWS = OS.WINDOWS
|
19
|
+
OTHER = OS.OTHER
|
20
|
+
|
21
|
+
|
22
|
+
def get_platform() -> OS:
|
23
|
+
"""Return the current operating system as an :class:`OS` enum.
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
OS: The current operating system as an enum member, or `OS.OTHER` if the platform is not recognized.
|
27
|
+
"""
|
28
|
+
system = platform.system()
|
29
|
+
return OS(system) if system in OS.__members__.values() else OS.OTHER
|
30
|
+
|
31
|
+
|
32
|
+
def is_macos() -> bool:
|
33
|
+
"""Return ``True`` if running on macOS."""
|
34
|
+
return get_platform() == DARWIN
|
35
|
+
|
36
|
+
|
37
|
+
def is_windows() -> bool:
|
38
|
+
"""Return ``True`` if running on Windows."""
|
39
|
+
return get_platform() == WINDOWS
|
40
|
+
|
41
|
+
|
42
|
+
def is_linux() -> bool:
|
43
|
+
"""Return ``True`` if running on Linux."""
|
44
|
+
return get_platform() == LINUX
|
45
|
+
|
46
|
+
|
47
|
+
if __name__ == "__main__":
|
48
|
+
detected_platform: OS = get_platform()
|
49
|
+
match detected_platform:
|
50
|
+
case OS.DARWIN:
|
51
|
+
print("Detected macOS")
|
52
|
+
case OS.LINUX:
|
53
|
+
print("Detected Linux")
|
54
|
+
case OS.WINDOWS:
|
55
|
+
print("Detected Windows")
|
56
|
+
case _:
|
57
|
+
print(f"Detected unsupported platform: {detected_platform}")
|
@@ -0,0 +1,451 @@
|
|
1
|
+
"""Function Response Class for handling function call results."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import json
|
6
|
+
from subprocess import CompletedProcess
|
7
|
+
from types import SimpleNamespace as Namespace
|
8
|
+
from typing import TYPE_CHECKING, Any, Literal, Self, overload
|
9
|
+
|
10
|
+
from pydantic import BaseModel, Field, field_validator
|
11
|
+
|
12
|
+
from bear_utils.extras._async_helpers import AsyncResponseModel, create_async_task, is_async_function
|
13
|
+
|
14
|
+
# Pydantic will yell if we put this into a TYPE_CHECKING block.
|
15
|
+
from bear_utils.logger_manager import AsyncLoggerProtocol, LoggerProtocol # noqa: TC001
|
16
|
+
|
17
|
+
if TYPE_CHECKING:
|
18
|
+
from collections.abc import Callable
|
19
|
+
|
20
|
+
|
21
|
+
class FunctionResponse(BaseModel):
|
22
|
+
"""A class to represent the response of a function call, including success status, content, and error messages."""
|
23
|
+
|
24
|
+
name: str = Field(default="", description="Name of the function that was called.")
|
25
|
+
returncode: int = Field(default=0, description="Return code of the function, 0 for success, !=0 for failure.")
|
26
|
+
extra: dict = Field(default_factory=dict, description="Additional metadata or information related to the response.")
|
27
|
+
content: list[str] = Field(default=[], description="Content returned by the function call")
|
28
|
+
error: list[str] = Field(default=[], description="Error message if the function call failed")
|
29
|
+
sub_tasks: list[FunctionResponse] = Field(default_factory=list, description="List of sub-tasks.")
|
30
|
+
number_of_tasks: int = Field(default=0, description="Number of tasks processed in this response.")
|
31
|
+
logger: LoggerProtocol | AsyncLoggerProtocol | None = Field(
|
32
|
+
default=None, description="Logger instance for logging messages."
|
33
|
+
)
|
34
|
+
attrs: Namespace = Field(default_factory=Namespace, description="Storing additional attributes dynamically.")
|
35
|
+
|
36
|
+
model_config = {
|
37
|
+
"arbitrary_types_allowed": True,
|
38
|
+
}
|
39
|
+
|
40
|
+
def _has_attr(self, key: str) -> bool:
|
41
|
+
"""Check if the attribute exists in the attrs Namespace."""
|
42
|
+
return hasattr(self.attrs, key)
|
43
|
+
|
44
|
+
def _get_attr(self, key: str, default: Any = None) -> Any:
|
45
|
+
"""Get the attribute from the attrs Namespace, returning default if not found."""
|
46
|
+
if self._has_attr(key):
|
47
|
+
return getattr(self.attrs, key, default)
|
48
|
+
return default
|
49
|
+
|
50
|
+
def _get_attrs(self) -> dict[str, Any]:
|
51
|
+
"""Get all attributes from the attrs Namespace as a dictionary."""
|
52
|
+
return {k: getattr(self.attrs, k, None) for k in self.attrs.__dict__ if not k.startswith("_")}
|
53
|
+
|
54
|
+
def __getattr__(self, key: str, default: Any = None) -> Any:
|
55
|
+
if key in FunctionResponse.model_fields:
|
56
|
+
raise AttributeError(f"This should never be called, {key} is a model field.")
|
57
|
+
return self._get_attr(key, default)
|
58
|
+
|
59
|
+
def __setattr__(self, key: str, value: Any) -> None:
|
60
|
+
if key in FunctionResponse.model_fields:
|
61
|
+
object.__setattr__(self, key, value)
|
62
|
+
return
|
63
|
+
setattr(self.attrs, key, value)
|
64
|
+
|
65
|
+
def __repr__(self) -> str:
|
66
|
+
"""Return a string representation of Response."""
|
67
|
+
parts: list[str] = []
|
68
|
+
|
69
|
+
def add(k: str, v: Any, _bool: bool = True, fmt_func: Callable | None = None) -> None:
|
70
|
+
if _bool:
|
71
|
+
formatted_value: str = fmt_func(v) if fmt_func else repr(v)
|
72
|
+
parts.append(f"{k}={formatted_value}")
|
73
|
+
|
74
|
+
add("name", self.name, bool(self.name))
|
75
|
+
add("content", ", ".join(self.content), bool(self.content))
|
76
|
+
|
77
|
+
# error state depends on returncode or error
|
78
|
+
add("error", ", ".join(self.error), self.error_state)
|
79
|
+
add("success", self.success, _bool=True)
|
80
|
+
add("returncode", self.returncode, self.error_state)
|
81
|
+
add("number_of_tasks", self.number_of_tasks, self.error_state)
|
82
|
+
add("extra", self.extra, bool(self.extra), json.dumps)
|
83
|
+
attrs = self._get_attrs()
|
84
|
+
for attr in attrs:
|
85
|
+
add(attr, attrs[attr])
|
86
|
+
return f"Response({', '.join(parts)})"
|
87
|
+
|
88
|
+
def __str__(self) -> str:
|
89
|
+
"""Return a string representation of Response."""
|
90
|
+
return self.__repr__()
|
91
|
+
|
92
|
+
@field_validator("name", mode="before")
|
93
|
+
@classmethod
|
94
|
+
def validate_name(cls, value: str | Any) -> str:
|
95
|
+
"""Ensure name is a string, lowercased, and without spaces."""
|
96
|
+
if value is None:
|
97
|
+
return ""
|
98
|
+
if not isinstance(value, str):
|
99
|
+
try:
|
100
|
+
value = str(value)
|
101
|
+
except Exception as e:
|
102
|
+
raise TypeError(f"Name must be a string, got {type(value).__name__}.") from e
|
103
|
+
return value.lower().replace(" ", "_")
|
104
|
+
|
105
|
+
@field_validator("returncode")
|
106
|
+
@classmethod
|
107
|
+
def validate_returncode(cls, value: int) -> int:
|
108
|
+
"""Ensure returncode is an integer above or equal to zero."""
|
109
|
+
if not isinstance(value, int) or value < 0:
|
110
|
+
raise ValueError("Return code must be a non-negative integer.")
|
111
|
+
return value
|
112
|
+
|
113
|
+
@field_validator("extra", mode="before")
|
114
|
+
@classmethod
|
115
|
+
def validate_extra(cls, value: dict | Any) -> dict:
|
116
|
+
"""Ensure extra is always a dictionary."""
|
117
|
+
if value is None:
|
118
|
+
return {}
|
119
|
+
if not isinstance(value, dict):
|
120
|
+
raise TypeError("Extra must be a dictionary.")
|
121
|
+
return value
|
122
|
+
|
123
|
+
@field_validator("content", mode="before")
|
124
|
+
@classmethod
|
125
|
+
def validate_content(cls, value: str | list[str] | Any) -> list[str]:
|
126
|
+
"""Ensure content is always a list of strings."""
|
127
|
+
if isinstance(value, str):
|
128
|
+
return [value]
|
129
|
+
if isinstance(value, list):
|
130
|
+
if not all(isinstance(item, str) for item in value):
|
131
|
+
raise TypeError("Content must be a list of strings.")
|
132
|
+
return value
|
133
|
+
raise TypeError("Content must be a string or a list of strings.")
|
134
|
+
|
135
|
+
@field_validator("error", mode="before")
|
136
|
+
@classmethod
|
137
|
+
def validate_error(cls, value: str | list[str] | Any) -> list[str]:
|
138
|
+
"""Ensure error is always a list of strings."""
|
139
|
+
if isinstance(value, str):
|
140
|
+
return [value]
|
141
|
+
if isinstance(value, list):
|
142
|
+
if not all(isinstance(item, str) for item in value):
|
143
|
+
raise TypeError("Error must be a list of strings.")
|
144
|
+
return value
|
145
|
+
raise TypeError("Error must be a string or a list of strings.")
|
146
|
+
|
147
|
+
@classmethod
|
148
|
+
def from_process(cls, process: CompletedProcess[str], **kwargs) -> Self:
|
149
|
+
"""Create a FunctionResponse from a CompletedProcess object."""
|
150
|
+
returncode: int = process.returncode if process.returncode is not None else 0
|
151
|
+
content: str = process.stdout.strip() if process.stdout else ""
|
152
|
+
error: str = process.stderr.strip() if process.stderr else ""
|
153
|
+
|
154
|
+
if returncode == 0 and not content and error: # Some processes return empty stdout on success
|
155
|
+
error, content = content, error
|
156
|
+
return cls().add(returncode=returncode, content=content, error=error, **kwargs)
|
157
|
+
|
158
|
+
@property
|
159
|
+
def error_state(self) -> bool:
|
160
|
+
"""Check if the returncode or error field indicates an error state."""
|
161
|
+
return self.returncode != 0 or bool(self.error)
|
162
|
+
|
163
|
+
@property
|
164
|
+
def success(self) -> bool:
|
165
|
+
"""Check if the response indicates success."""
|
166
|
+
return self.returncode == 0 and not bool(self.error)
|
167
|
+
|
168
|
+
def sub_task(
|
169
|
+
self,
|
170
|
+
name: str = "",
|
171
|
+
content: str | list[str] = "",
|
172
|
+
error: str | list[str] = "",
|
173
|
+
extra: dict[str, Any] | None = None,
|
174
|
+
returncode: int | None = None,
|
175
|
+
log_output: bool = False,
|
176
|
+
) -> Self:
|
177
|
+
"""Add a sub-task response to the FunctionResponse."""
|
178
|
+
func_response: FunctionResponse = FunctionResponse(name=name, logger=self.logger).add(
|
179
|
+
content=content,
|
180
|
+
error=error,
|
181
|
+
returncode=returncode or self.returncode,
|
182
|
+
log_output=log_output,
|
183
|
+
extra=extra,
|
184
|
+
)
|
185
|
+
self.add(content=func_response)
|
186
|
+
self.sub_tasks.append(func_response)
|
187
|
+
return self
|
188
|
+
|
189
|
+
def successful(
|
190
|
+
self,
|
191
|
+
content: str | list[str] | CompletedProcess | FunctionResponse,
|
192
|
+
error: str | list[str] = "",
|
193
|
+
returncode: int | None = None,
|
194
|
+
log_output: bool = False,
|
195
|
+
**kwargs,
|
196
|
+
) -> Self:
|
197
|
+
"""Set the response to a success state with optional content."""
|
198
|
+
return_code: int = returncode if returncode is not None else 0
|
199
|
+
self.add(content=content, error=error, returncode=return_code, log_output=log_output, **kwargs)
|
200
|
+
return self
|
201
|
+
|
202
|
+
def fail(
|
203
|
+
self,
|
204
|
+
content: list[str] | str | CompletedProcess = "",
|
205
|
+
error: str | list[str] = "",
|
206
|
+
returncode: int | None = None,
|
207
|
+
log_output: bool = False,
|
208
|
+
**kwargs,
|
209
|
+
) -> Self:
|
210
|
+
"""Set the response to a failure state with an error message."""
|
211
|
+
return_code: int = returncode if returncode is not None else 1
|
212
|
+
self.add(content=content, error=error, returncode=return_code, log_output=log_output, **kwargs)
|
213
|
+
return self
|
214
|
+
|
215
|
+
def _add_item(self, item: str, target_list: list[str]) -> None:
|
216
|
+
"""Append an item to the target list if not empty."""
|
217
|
+
target_list.append(item) if item != "" else None
|
218
|
+
|
219
|
+
def _add_to_list(self, items: str | list[str], target_list: list[str], name: str | None = None) -> None:
|
220
|
+
"""Append items to the target list with optional name prefix."""
|
221
|
+
try:
|
222
|
+
if isinstance(items, list):
|
223
|
+
for item in items:
|
224
|
+
self._add_item(f"{name}: {item}" if name else item, target_list)
|
225
|
+
elif isinstance(items, str):
|
226
|
+
self._add_item(f"{name}: {items}" if name else items, target_list)
|
227
|
+
except Exception as e:
|
228
|
+
raise ValueError(f"Failed to add items: {e!s}") from e
|
229
|
+
|
230
|
+
def _add_content(self, content: str | list[str], name: str | None = None) -> None:
|
231
|
+
"""Add content to the FunctionResponse content list."""
|
232
|
+
return self._add_to_list(items=content, target_list=self.content, name=name)
|
233
|
+
|
234
|
+
def _add_error(self, error: str | list[str], name: str | None = None) -> None:
|
235
|
+
"""Add error messages to the FunctionResponse error list."""
|
236
|
+
return self._add_to_list(items=error, target_list=self.error, name=name)
|
237
|
+
|
238
|
+
def _handle_function_response(self, func_response: FunctionResponse) -> None:
|
239
|
+
"""Handle a FunctionResponse object and update the current response."""
|
240
|
+
if func_response.extra:
|
241
|
+
self.extra.update(func_response.extra)
|
242
|
+
self._add_content(func_response.content, name=func_response.name)
|
243
|
+
self._add_error(func_response.error, name=func_response.name)
|
244
|
+
|
245
|
+
def _handle_completed_process(self, result: CompletedProcess[str]) -> None:
|
246
|
+
"""Handle a CompletedProcess object and update the FunctionResponse."""
|
247
|
+
self._add_content(result.stdout.strip())
|
248
|
+
self._add_error(result.stderr.strip())
|
249
|
+
self.returncode = result.returncode
|
250
|
+
|
251
|
+
def _handle_content(self, content: list[str] | str | FunctionResponse | CompletedProcess | Any) -> None:
|
252
|
+
"""Handle different types of content and update the FunctionResponse."""
|
253
|
+
if isinstance(content, FunctionResponse):
|
254
|
+
self._handle_function_response(func_response=content)
|
255
|
+
elif isinstance(content, CompletedProcess):
|
256
|
+
self._handle_completed_process(result=content)
|
257
|
+
elif isinstance(content, (str | list)):
|
258
|
+
self._add_to_list(content, self.content)
|
259
|
+
else:
|
260
|
+
return
|
261
|
+
self.number_of_tasks += 1
|
262
|
+
|
263
|
+
@overload
|
264
|
+
def add(
|
265
|
+
self,
|
266
|
+
content: list[str] | str | FunctionResponse | CompletedProcess | None = None,
|
267
|
+
error: str | list[str] | None = None,
|
268
|
+
returncode: int | None = None,
|
269
|
+
log_output: bool = False,
|
270
|
+
extra: dict[str, Any] | None = None,
|
271
|
+
*,
|
272
|
+
to_dict: Literal[True],
|
273
|
+
) -> dict[str, Any]: ...
|
274
|
+
|
275
|
+
@overload
|
276
|
+
def add(
|
277
|
+
self,
|
278
|
+
content: list[str] | str | FunctionResponse | CompletedProcess | None = None,
|
279
|
+
error: str | list[str] | None = None,
|
280
|
+
returncode: int | None = None,
|
281
|
+
log_output: bool = False,
|
282
|
+
extra: dict[str, Any] | None = None,
|
283
|
+
*,
|
284
|
+
to_dict: Literal[False] = False,
|
285
|
+
) -> Self: ...
|
286
|
+
|
287
|
+
def add(
|
288
|
+
self,
|
289
|
+
content: list[str] | str | FunctionResponse | CompletedProcess | None = None,
|
290
|
+
error: str | list[str] | None = None,
|
291
|
+
returncode: int | None = None,
|
292
|
+
log_output: bool = False,
|
293
|
+
extra: dict[str, Any] | None = None,
|
294
|
+
*,
|
295
|
+
to_dict: bool = False,
|
296
|
+
) -> Self | dict[str, Any]:
|
297
|
+
"""Append additional content to the existing content."""
|
298
|
+
try:
|
299
|
+
if content is not None:
|
300
|
+
self._handle_content(content=content)
|
301
|
+
if error is not None and isinstance(error, (str | list)):
|
302
|
+
self._add_to_list(error, target_list=self.error)
|
303
|
+
if isinstance(returncode, int):
|
304
|
+
self.returncode = returncode
|
305
|
+
if isinstance(extra, dict):
|
306
|
+
self.extra.update(extra)
|
307
|
+
if log_output and self.logger and (content or error):
|
308
|
+
self._log_handling(content=content, error=error, logger=self.logger)
|
309
|
+
except Exception as e:
|
310
|
+
raise ValueError(f"Failed to add content: {e!s}") from e
|
311
|
+
return self.done(to_dict=True) if to_dict else self
|
312
|
+
|
313
|
+
def _log_handling(
|
314
|
+
self,
|
315
|
+
content: list[str] | str | FunctionResponse | CompletedProcess | None,
|
316
|
+
error: str | list[str] | None,
|
317
|
+
logger: LoggerProtocol | AsyncLoggerProtocol,
|
318
|
+
) -> None:
|
319
|
+
"""Log the content and error messages if they exist."""
|
320
|
+
|
321
|
+
async def loop_logging(messages: list[str], func: Callable) -> None:
|
322
|
+
for msg in messages:
|
323
|
+
if msg == "":
|
324
|
+
continue
|
325
|
+
if is_async_function(func):
|
326
|
+
await func(f"{msg}")
|
327
|
+
else:
|
328
|
+
func(f"{msg}")
|
329
|
+
|
330
|
+
async def _log_messages(
|
331
|
+
content: str | list[str], error: str | list[str], info_func: Callable, error_func: Callable
|
332
|
+
) -> None:
|
333
|
+
if isinstance(content, str):
|
334
|
+
content = [content]
|
335
|
+
if isinstance(error, str):
|
336
|
+
error = [error]
|
337
|
+
|
338
|
+
await loop_logging(messages=content, func=info_func)
|
339
|
+
await loop_logging(messages=error, func=error_func)
|
340
|
+
|
341
|
+
if isinstance(content, (FunctionResponse | CompletedProcess | None)):
|
342
|
+
content = []
|
343
|
+
if not isinstance(error, (list | str)):
|
344
|
+
error = []
|
345
|
+
if not content and not error:
|
346
|
+
return
|
347
|
+
res: AsyncResponseModel = create_async_task(
|
348
|
+
_log_messages,
|
349
|
+
content=content,
|
350
|
+
error=error,
|
351
|
+
info_func=logger.info,
|
352
|
+
error_func=logger.error,
|
353
|
+
)
|
354
|
+
res.conditional_run()
|
355
|
+
|
356
|
+
@overload
|
357
|
+
def done(
|
358
|
+
self,
|
359
|
+
to_dict: Literal[True],
|
360
|
+
suppress: list[str] | None = None,
|
361
|
+
include: list[str] | None = None,
|
362
|
+
) -> dict[str, Any]: ...
|
363
|
+
|
364
|
+
@overload
|
365
|
+
def done(
|
366
|
+
self,
|
367
|
+
to_dict: Literal[False],
|
368
|
+
suppress: list[str] | None = None,
|
369
|
+
include: list[str] | None = None,
|
370
|
+
) -> Self: ...
|
371
|
+
|
372
|
+
def done(
|
373
|
+
self,
|
374
|
+
to_dict: bool = False,
|
375
|
+
suppress: list[str] | None = None,
|
376
|
+
include: list[str] | None = None,
|
377
|
+
) -> dict[str, Any] | Self:
|
378
|
+
"""Convert the FunctionResponse to a dictionary or return the instance itself.
|
379
|
+
|
380
|
+
Args:
|
381
|
+
to_dict (bool): If True, return a dictionary representation.
|
382
|
+
If False, return the FunctionResponse instance.
|
383
|
+
suppress (list[str] | None): List of keys to suppress in the output dictionary.
|
384
|
+
include (list[str] | None): List of keys to include in the output dictionary.
|
385
|
+
|
386
|
+
Returns:
|
387
|
+
dict[str, Any] | Self: The dictionary representation or the FunctionResponse instance.
|
388
|
+
"""
|
389
|
+
if not to_dict:
|
390
|
+
return self
|
391
|
+
|
392
|
+
if suppress is None:
|
393
|
+
suppress = []
|
394
|
+
|
395
|
+
if include is None:
|
396
|
+
include = []
|
397
|
+
|
398
|
+
result: dict[str, Any] = {}
|
399
|
+
|
400
|
+
def add(k: str, v: Any, _bool: bool = True) -> None:
|
401
|
+
if k not in suppress and _bool:
|
402
|
+
result[k] = v
|
403
|
+
|
404
|
+
def dict_include(_bool: bool = True) -> dict[str, Any]:
|
405
|
+
result.update(self.extra)
|
406
|
+
for k in include:
|
407
|
+
if hasattr(self.attrs, k) and _bool:
|
408
|
+
result[k] = getattr(self.attrs, k)
|
409
|
+
return result
|
410
|
+
|
411
|
+
add("name", self.name, bool(self.name))
|
412
|
+
add("content", self.content, bool(self.content))
|
413
|
+
add("success", self.success, _bool=True)
|
414
|
+
# depends on the error state
|
415
|
+
add("error", self.error, self.error_state)
|
416
|
+
add("returncode", self.returncode, self.error_state)
|
417
|
+
add("number_of_tasks", self.number_of_tasks, self.error_state)
|
418
|
+
|
419
|
+
return dict_include()
|
420
|
+
|
421
|
+
|
422
|
+
def success(
|
423
|
+
content: str | list[str] | CompletedProcess[str] | FunctionResponse,
|
424
|
+
error: str = "",
|
425
|
+
log_output: bool = False,
|
426
|
+
**kwargs,
|
427
|
+
) -> FunctionResponse:
|
428
|
+
"""Create a successful FunctionResponse."""
|
429
|
+
res = FunctionResponse()
|
430
|
+
return res.successful(content, error, 0, log_output, **kwargs)
|
431
|
+
|
432
|
+
|
433
|
+
def fail(
|
434
|
+
content: str | list[str] | CompletedProcess[str] = "",
|
435
|
+
error: str | list[str] = "",
|
436
|
+
returncode: int | None = None,
|
437
|
+
log_output: bool = False,
|
438
|
+
**kwargs,
|
439
|
+
) -> FunctionResponse:
|
440
|
+
"""Create a failed FunctionResponse."""
|
441
|
+
res = FunctionResponse()
|
442
|
+
return res.fail(content, error, returncode, log_output, **kwargs)
|
443
|
+
|
444
|
+
|
445
|
+
if __name__ == "__main__":
|
446
|
+
# For testing purposes, you can run this module directly.
|
447
|
+
|
448
|
+
response = FunctionResponse(name="test_function")
|
449
|
+
response.add(content=["This is a test content."], error=["No errors."], returncode=1, extra={"smelly": "value"})
|
450
|
+
response.poop = "farts"
|
451
|
+
print(response.done(to_dict=True))
|
@@ -0,0 +1 @@
|
|
1
|
+
"""A module for wrappers in Bear Utils."""
|
@@ -0,0 +1,100 @@
|
|
1
|
+
"""A module for adding rich comparison methods to classes based on an attribute."""
|
2
|
+
|
3
|
+
from collections.abc import Callable
|
4
|
+
from types import NotImplementedType
|
5
|
+
from typing import Any, TypeVar
|
6
|
+
|
7
|
+
T = TypeVar("T")
|
8
|
+
|
9
|
+
PRIMITIVE_TYPES: tuple[type[str], type[int], type[float], type[bool]] = (str, int, float, bool)
|
10
|
+
|
11
|
+
|
12
|
+
def add_comparison_methods(attribute: str) -> Callable[[type[T]], type[T]]:
|
13
|
+
"""Class decorator that adds rich comparison methods based on a specific attribute.
|
14
|
+
|
15
|
+
This decorator adds __eq__, __ne__, __lt__, __gt__, __le__, __ge__, and __hash__ methods
|
16
|
+
to a class, all of which delegate to the specified attribute. This allows instances
|
17
|
+
of the decorated class to be compared with each other, as well as with primitive values
|
18
|
+
that the attribute can be compared with.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
attribute: Name of the instance attribute to use for comparisons
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
Class decorator function that adds comparison methods to a class
|
25
|
+
|
26
|
+
Example:
|
27
|
+
@add_comparison_methods('name')
|
28
|
+
class Person:
|
29
|
+
def __init__(self, name):
|
30
|
+
self.name = name
|
31
|
+
"""
|
32
|
+
|
33
|
+
def decorator(cls: type[T]) -> type[T]:
|
34
|
+
def extract_comparable_value(self: object, other: Any) -> NotImplementedType | Any: # noqa: ARG001
|
35
|
+
"""Helper to extract the comparable value from the other object."""
|
36
|
+
if isinstance(other, PRIMITIVE_TYPES):
|
37
|
+
return other
|
38
|
+
|
39
|
+
if hasattr(other, attribute):
|
40
|
+
return getattr(other, attribute)
|
41
|
+
|
42
|
+
return NotImplemented
|
43
|
+
|
44
|
+
def equals_method(self: object, other: Any) -> NotImplementedType | bool:
|
45
|
+
"""Equal comparison method (__eq__)."""
|
46
|
+
other_val: NotImplementedType | Any = extract_comparable_value(self, other)
|
47
|
+
if other_val is NotImplemented:
|
48
|
+
return NotImplemented
|
49
|
+
return getattr(self, attribute) == other_val
|
50
|
+
|
51
|
+
def not_equals_method(self: object, other: Any) -> NotImplementedType | bool:
|
52
|
+
"""Not equal comparison method (__ne__)."""
|
53
|
+
other_val: NotImplementedType | Any = extract_comparable_value(self, other)
|
54
|
+
if other_val is NotImplemented:
|
55
|
+
return NotImplemented
|
56
|
+
return getattr(self, attribute) != other_val
|
57
|
+
|
58
|
+
def less_than_method(self: object, other: Any) -> NotImplementedType | bool:
|
59
|
+
"""Less than comparison method (__lt__)."""
|
60
|
+
other_val: NotImplementedType | Any = extract_comparable_value(self, other)
|
61
|
+
if other_val is NotImplemented:
|
62
|
+
return NotImplemented
|
63
|
+
return getattr(self, attribute) < other_val
|
64
|
+
|
65
|
+
def greater_than_method(self: object, other: Any) -> NotImplementedType | bool:
|
66
|
+
"""Greater than comparison method (__gt__)."""
|
67
|
+
other_val: NotImplementedType | Any = extract_comparable_value(self, other)
|
68
|
+
if other_val is NotImplemented:
|
69
|
+
return NotImplemented
|
70
|
+
return getattr(self, attribute) > other_val
|
71
|
+
|
72
|
+
def less_than_or_equal_method(self: object, other: Any) -> NotImplementedType | bool:
|
73
|
+
"""Less than or equal comparison method (__le__)."""
|
74
|
+
other_val: NotImplementedType | Any = extract_comparable_value(self, other)
|
75
|
+
if other_val is NotImplemented:
|
76
|
+
return NotImplemented
|
77
|
+
return getattr(self, attribute) <= other_val
|
78
|
+
|
79
|
+
def greater_than_or_equal_method(self: object, other: Any) -> NotImplementedType | bool:
|
80
|
+
"""Greater than or equal comparison method (__ge__)."""
|
81
|
+
other_val: NotImplementedType | Any = extract_comparable_value(self, other)
|
82
|
+
if other_val is NotImplemented:
|
83
|
+
return NotImplemented
|
84
|
+
return getattr(self, attribute) >= other_val
|
85
|
+
|
86
|
+
def hash_method(self: object) -> int:
|
87
|
+
"""Generate hash based on the attribute used for equality."""
|
88
|
+
return hash(getattr(self, attribute))
|
89
|
+
|
90
|
+
cls.__eq__ = equals_method
|
91
|
+
cls.__ne__ = not_equals_method
|
92
|
+
cls.__lt__ = less_than_method # type: ignore[assignment]
|
93
|
+
cls.__gt__ = greater_than_method # type: ignore[assignment]
|
94
|
+
cls.__le__ = less_than_or_equal_method # type: ignore[assignment]
|
95
|
+
cls.__ge__ = greater_than_or_equal_method # type: ignore[assignment]
|
96
|
+
cls.__hash__ = hash_method
|
97
|
+
|
98
|
+
return cls
|
99
|
+
|
100
|
+
return decorator
|
@@ -0,0 +1,46 @@
|
|
1
|
+
"""A Simple wrapper around StringIO to make things easier."""
|
2
|
+
|
3
|
+
from io import BytesIO, StringIO
|
4
|
+
from typing import Any, cast
|
5
|
+
|
6
|
+
|
7
|
+
class BaseIOWrapper[T: StringIO | BytesIO]:
|
8
|
+
"""A Base wrapper around IO objects to make things easier."""
|
9
|
+
|
10
|
+
def __init__(self, io_obj: T | Any) -> None:
|
11
|
+
"""Initialize the IOWrapper with a IO Object object."""
|
12
|
+
if not isinstance(io_obj, (StringIO | BytesIO)):
|
13
|
+
raise TypeError("io_obj must be an instance of StringIO or BytesIO")
|
14
|
+
self._value: T = cast("T", io_obj)
|
15
|
+
self._cached_value = None
|
16
|
+
|
17
|
+
def _reset_io(self) -> None:
|
18
|
+
"""Reset the current a IO Object."""
|
19
|
+
self._value.truncate(0)
|
20
|
+
self._value.seek(0)
|
21
|
+
self._cached_value = None
|
22
|
+
|
23
|
+
|
24
|
+
class StringIOWrapper(BaseIOWrapper[StringIO]):
|
25
|
+
"""A Simple wrapper around StringIO to make things easier."""
|
26
|
+
|
27
|
+
def __init__(self, **kwargs) -> None:
|
28
|
+
"""Initialize the IOWrapper with a a IO Object object."""
|
29
|
+
super().__init__(StringIO(**kwargs))
|
30
|
+
self._cached_value: str = ""
|
31
|
+
|
32
|
+
def _reset_io(self) -> None:
|
33
|
+
"""Reset the current a IO Object."""
|
34
|
+
self._value.truncate(0)
|
35
|
+
self._value.seek(0)
|
36
|
+
self._cached_value = ""
|
37
|
+
|
38
|
+
def write(self, *values: str) -> None:
|
39
|
+
"""Write values to the a IO Object object."""
|
40
|
+
for value in values:
|
41
|
+
self._value.write(value)
|
42
|
+
|
43
|
+
def getvalue(self) -> str:
|
44
|
+
"""Get the string value from the a IO Object object."""
|
45
|
+
self._cached_value = self._value.getvalue()
|
46
|
+
return self._cached_value
|