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,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,5 @@
1
+ """A module for handling responses for functions, methods, and classes in Bear Utils."""
2
+
3
+ from .function_response import FunctionResponse, fail, success
4
+
5
+ __all__ = ["FunctionResponse", "fail", "success"]
@@ -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
@@ -0,0 +1,6 @@
1
+ """A module for file handling utilities in Bear Utils."""
2
+
3
+ from .file_handlers import FileHandlerFactory
4
+ from .ignore_parser import IGNORE_PATTERNS
5
+
6
+ __all__ = ["IGNORE_PATTERNS", "FileHandlerFactory"]
@@ -0,0 +1,5 @@
1
+ """A module for file handlers in Bear Utils."""
2
+
3
+ from .file_handler_factory import FileHandlerFactory
4
+
5
+ __all__ = ["FileHandlerFactory"]