bear-utils 0.7.20__py3-none-any.whl → 0.7.22__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 +24 -1
- bear_utils/ai/__init__.py +5 -5
- bear_utils/ai/ai_helpers/__init__.py +24 -18
- bear_utils/ai/ai_helpers/_parsers.py +27 -21
- bear_utils/ai/ai_helpers/_types.py +2 -7
- bear_utils/cache/__init__.py +35 -23
- bear_utils/cli/__init__.py +13 -0
- bear_utils/cli/commands.py +14 -8
- bear_utils/cli/prompt_helpers.py +40 -34
- bear_utils/cli/shell/__init__.py +1 -0
- bear_utils/cli/shell/_base_command.py +17 -18
- bear_utils/cli/shell/_base_shell.py +37 -34
- bear_utils/config/__init__.py +4 -2
- bear_utils/config/config_manager.py +193 -56
- bear_utils/config/dir_manager.py +8 -3
- bear_utils/config/settings_manager.py +94 -171
- bear_utils/constants/__init__.py +2 -1
- bear_utils/constants/_exceptions.py +6 -1
- bear_utils/constants/date_related.py +2 -0
- bear_utils/constants/logger_protocol.py +28 -0
- bear_utils/constants/time_related.py +2 -0
- bear_utils/database/__init__.py +2 -0
- bear_utils/database/_db_manager.py +10 -11
- bear_utils/events/__init__.py +3 -1
- bear_utils/events/events_class.py +11 -11
- bear_utils/events/events_module.py +17 -8
- bear_utils/extras/__init__.py +8 -6
- bear_utils/extras/_async_helpers.py +2 -3
- bear_utils/extras/_tools.py +54 -52
- bear_utils/extras/platform_utils.py +5 -1
- bear_utils/extras/responses/__init__.py +1 -0
- bear_utils/extras/responses/function_response.py +301 -0
- bear_utils/extras/wrappers/__init__.py +1 -0
- bear_utils/extras/wrappers/add_methods.py +17 -15
- bear_utils/files/__init__.py +3 -1
- bear_utils/files/file_handlers/__init__.py +2 -0
- bear_utils/files/file_handlers/_base_file_handler.py +23 -3
- bear_utils/files/file_handlers/file_handler_factory.py +38 -38
- bear_utils/files/file_handlers/json_file_handler.py +49 -22
- bear_utils/files/file_handlers/log_file_handler.py +19 -12
- bear_utils/files/file_handlers/toml_file_handler.py +13 -5
- bear_utils/files/file_handlers/txt_file_handler.py +56 -14
- bear_utils/files/file_handlers/yaml_file_handler.py +19 -13
- bear_utils/files/ignore_parser.py +52 -57
- bear_utils/graphics/__init__.py +3 -1
- bear_utils/graphics/bear_gradient.py +17 -12
- bear_utils/graphics/image_helpers.py +11 -5
- bear_utils/gui/__init__.py +7 -2
- bear_utils/gui/gui_tools/__init__.py +9 -4
- bear_utils/gui/gui_tools/_settings.py +0 -1
- bear_utils/gui/gui_tools/qt_app.py +16 -11
- bear_utils/gui/gui_tools/qt_color_picker.py +24 -13
- bear_utils/gui/gui_tools/qt_file_handler.py +30 -38
- bear_utils/gui/gui_tools/qt_input_dialog.py +11 -14
- bear_utils/logging/__init__.py +6 -4
- bear_utils/logging/logger_manager/__init__.py +1 -0
- bear_utils/logging/logger_manager/_common.py +0 -1
- bear_utils/logging/logger_manager/_console_junk.py +15 -11
- bear_utils/logging/logger_manager/_styles.py +1 -2
- bear_utils/logging/logger_manager/loggers/__init__.py +1 -0
- bear_utils/logging/logger_manager/loggers/_base_logger.py +33 -33
- bear_utils/logging/logger_manager/loggers/_base_logger.pyi +6 -5
- bear_utils/logging/logger_manager/loggers/_buffer_logger.py +2 -3
- bear_utils/logging/logger_manager/loggers/_console_logger.py +54 -26
- bear_utils/logging/logger_manager/loggers/_console_logger.pyi +7 -21
- bear_utils/logging/logger_manager/loggers/_file_logger.py +20 -13
- bear_utils/logging/logger_manager/loggers/_level_sin.py +15 -15
- bear_utils/logging/logger_manager/loggers/_logger.py +4 -6
- bear_utils/logging/logger_manager/loggers/_sub_logger.py +16 -23
- bear_utils/logging/logger_manager/loggers/_sub_logger.pyi +4 -19
- bear_utils/logging/loggers.py +9 -13
- bear_utils/monitoring/__init__.py +7 -4
- bear_utils/monitoring/_common.py +28 -0
- bear_utils/monitoring/host_monitor.py +44 -48
- bear_utils/time/__init__.py +13 -6
- {bear_utils-0.7.20.dist-info → bear_utils-0.7.22.dist-info}/METADATA +50 -7
- bear_utils-0.7.22.dist-info/RECORD +83 -0
- bear_utils-0.7.20.dist-info/RECORD +0 -79
- {bear_utils-0.7.20.dist-info → bear_utils-0.7.22.dist-info}/WHEEL +0 -0
bear_utils/extras/_tools.py
CHANGED
@@ -1,29 +1,40 @@
|
|
1
1
|
import asyncio
|
2
|
-
import shutil
|
3
2
|
from asyncio.subprocess import PIPE
|
4
3
|
from collections import deque
|
5
4
|
from functools import cached_property
|
6
|
-
|
5
|
+
import shutil
|
6
|
+
from typing import TYPE_CHECKING
|
7
7
|
|
8
|
-
from
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from .
|
8
|
+
from bear_utils.cli.shell._base_command import BaseShellCommand as ShellCommand
|
9
|
+
from bear_utils.cli.shell._base_shell import AsyncShellSession
|
10
|
+
from bear_utils.extras.platform_utils import OS, get_platform
|
11
|
+
from bear_utils.logging.logger_manager.loggers._base_logger import BaseLogger
|
12
|
+
|
13
|
+
if TYPE_CHECKING:
|
14
|
+
from subprocess import CompletedProcess
|
12
15
|
|
13
16
|
|
14
17
|
class TextHelper:
|
15
18
|
@cached_property
|
16
19
|
def local_console(self) -> BaseLogger:
|
17
|
-
from
|
20
|
+
from bear_utils.logging.loggers import BaseLogger # noqa: PLC0415
|
18
21
|
|
19
22
|
init: bool = not BaseLogger.has_instance()
|
20
23
|
return BaseLogger.get_instance(init=init)
|
21
24
|
|
22
|
-
def print_header(
|
25
|
+
def print_header(
|
26
|
+
self,
|
27
|
+
title: str,
|
28
|
+
sep: str = "#",
|
29
|
+
length: int = 60,
|
30
|
+
s1: str = "bold red",
|
31
|
+
s2: str = "bold blue",
|
32
|
+
return_txt: bool = False,
|
33
|
+
) -> str:
|
23
34
|
"""Generate a header string"""
|
24
35
|
# FIXME: There are probably better ways to do this, but this is OK.
|
25
|
-
fill: str = sep *
|
26
|
-
title = f" {title} ".center(
|
36
|
+
fill: str = sep * length
|
37
|
+
title = f" {title} ".center(length, sep).replace(title, f"[{s1}]{title}[/{s1}]")
|
27
38
|
output_text: str = f"\n{fill}\n{title}\n{fill}\n"
|
28
39
|
if not return_txt:
|
29
40
|
self.local_console.print(output_text, style=s2)
|
@@ -31,13 +42,14 @@ class TextHelper:
|
|
31
42
|
|
32
43
|
|
33
44
|
class ClipboardManager:
|
34
|
-
"""
|
35
|
-
|
45
|
+
"""A class to manage clipboard operations such as copying, pasting, and clearing.
|
46
|
+
|
36
47
|
This class provides methods to interact with the system clipboard.
|
37
48
|
"""
|
38
49
|
|
39
50
|
def __init__(self, maxlen: int = 10) -> None:
|
40
|
-
|
51
|
+
"""Initialize the ClipboardManager with a maximum history length."""
|
52
|
+
self.clipboard_history: deque[str] = deque(maxlen=maxlen)
|
41
53
|
self.shell = AsyncShellSession(env={"LANG": "en_US.UTF-8"}, verbose=False)
|
42
54
|
self._copy: ShellCommand[str]
|
43
55
|
self._paste: ShellCommand[str]
|
@@ -45,20 +57,20 @@ class ClipboardManager:
|
|
45
57
|
platform: OS = get_platform()
|
46
58
|
match platform:
|
47
59
|
case OS.DARWIN:
|
48
|
-
self._copy = ShellCommand.adhoc("pbcopy")
|
49
|
-
self._paste = ShellCommand.adhoc("pbpaste")
|
60
|
+
self._copy = ShellCommand.adhoc(name="pbcopy")
|
61
|
+
self._paste = ShellCommand.adhoc(name="pbpaste")
|
50
62
|
case OS.LINUX:
|
51
|
-
if shutil.which("wl-copy") and shutil.which("wl-paste"):
|
52
|
-
self._copy = ShellCommand.adhoc("wl-copy")
|
53
|
-
self._paste = ShellCommand.adhoc("wl-paste")
|
54
|
-
elif shutil.which("xclip"):
|
55
|
-
self._copy = ShellCommand.adhoc("xclip").sub("-selection", "clipboard")
|
56
|
-
self._paste = ShellCommand.adhoc("xclip").sub("-selection", "clipboard").value("-o")
|
63
|
+
if shutil.which(cmd="wl-copy") and shutil.which(cmd="wl-paste"):
|
64
|
+
self._copy = ShellCommand.adhoc(name="wl-copy")
|
65
|
+
self._paste = ShellCommand.adhoc(name="wl-paste")
|
66
|
+
elif shutil.which(cmd="xclip"):
|
67
|
+
self._copy = ShellCommand.adhoc(name="xclip").sub("-selection", "clipboard")
|
68
|
+
self._paste = ShellCommand.adhoc(name="xclip").sub("-selection", "clipboard").value("-o")
|
57
69
|
else:
|
58
70
|
raise RuntimeError("No clipboard command found on Linux")
|
59
71
|
case OS.WINDOWS:
|
60
|
-
self._copy = ShellCommand.adhoc("clip")
|
61
|
-
self._paste = ShellCommand.adhoc("powershell").sub("Get-Clipboard")
|
72
|
+
self._copy = ShellCommand.adhoc(name="clip")
|
73
|
+
self._paste = ShellCommand.adhoc(name="powershell").sub("Get-Clipboard")
|
62
74
|
case _:
|
63
75
|
raise RuntimeError(f"Unsupported platform: {platform}")
|
64
76
|
|
@@ -71,8 +83,7 @@ class ClipboardManager:
|
|
71
83
|
return self.clipboard_history
|
72
84
|
|
73
85
|
async def copy(self, output: str) -> int:
|
74
|
-
"""
|
75
|
-
A function that copies the output to the clipboard.
|
86
|
+
"""A function that copies the output to the clipboard.
|
76
87
|
|
77
88
|
Args:
|
78
89
|
output (str): The output to copy to the clipboard.
|
@@ -80,15 +91,14 @@ class ClipboardManager:
|
|
80
91
|
Returns:
|
81
92
|
int: The return code of the command.
|
82
93
|
"""
|
83
|
-
await self.shell.run(self._copy, stdin=PIPE)
|
94
|
+
await self.shell.run(cmd=self._copy, stdin=PIPE)
|
84
95
|
result: CompletedProcess[str] = await self.shell.communicate(stdin=output)
|
85
96
|
if result.returncode == 0:
|
86
97
|
self.clipboard_history.append(output) # Only append to history if the copy was successful
|
87
98
|
return result.returncode
|
88
99
|
|
89
100
|
async def paste(self) -> str:
|
90
|
-
"""
|
91
|
-
Paste the output from the clipboard.
|
101
|
+
"""Paste the output from the clipboard.
|
92
102
|
|
93
103
|
Returns:
|
94
104
|
str: The content of the clipboard.
|
@@ -97,27 +107,25 @@ class ClipboardManager:
|
|
97
107
|
RuntimeError: If the paste command fails.
|
98
108
|
"""
|
99
109
|
try:
|
100
|
-
await self.shell.run(self._paste)
|
110
|
+
await self.shell.run(cmd=self._paste)
|
101
111
|
result: CompletedProcess[str] = await self.shell.communicate()
|
102
|
-
except Exception as e:
|
112
|
+
except Exception as e:
|
103
113
|
raise RuntimeError(f"Error pasting from clipboard: {e}") from e
|
104
114
|
if result.returncode != 0:
|
105
115
|
raise RuntimeError(f"{self._paste.cmd} failed with return code {result.returncode}")
|
106
116
|
return result.stdout
|
107
117
|
|
108
118
|
async def clear(self) -> int:
|
109
|
-
"""
|
110
|
-
A function that clears the clipboard.
|
119
|
+
"""A function that clears the clipboard.
|
111
120
|
|
112
121
|
Returns:
|
113
122
|
int: The return code of the command.
|
114
123
|
"""
|
115
|
-
return await self.copy("")
|
124
|
+
return await self.copy(output="")
|
116
125
|
|
117
126
|
|
118
127
|
def copy_to_clipboard(output: str) -> int:
|
119
|
-
"""
|
120
|
-
Copy the output to the clipboard.
|
128
|
+
"""Copy the output to the clipboard.
|
121
129
|
|
122
130
|
Args:
|
123
131
|
output (str): The output to copy to the clipboard.
|
@@ -127,12 +135,11 @@ def copy_to_clipboard(output: str) -> int:
|
|
127
135
|
"""
|
128
136
|
clipboard_manager = ClipboardManager()
|
129
137
|
loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
|
130
|
-
return loop.run_until_complete(clipboard_manager.copy(output))
|
138
|
+
return loop.run_until_complete(future=clipboard_manager.copy(output))
|
131
139
|
|
132
140
|
|
133
141
|
async def copy_to_clipboard_async(output: str) -> int:
|
134
|
-
"""
|
135
|
-
Asynchronously copy the output to the clipboard.
|
142
|
+
"""Asynchronously copy the output to the clipboard.
|
136
143
|
|
137
144
|
Args:
|
138
145
|
output (str): The output to copy to the clipboard.
|
@@ -141,24 +148,22 @@ async def copy_to_clipboard_async(output: str) -> int:
|
|
141
148
|
int: The return code of the command.
|
142
149
|
"""
|
143
150
|
clipboard_manager = ClipboardManager()
|
144
|
-
return await clipboard_manager.copy(output)
|
151
|
+
return await clipboard_manager.copy(output=output)
|
145
152
|
|
146
153
|
|
147
154
|
def paste_from_clipboard() -> str:
|
148
|
-
"""
|
149
|
-
Paste the output from the clipboard.
|
155
|
+
"""Paste the output from the clipboard.
|
150
156
|
|
151
157
|
Returns:
|
152
158
|
str: The content of the clipboard.
|
153
159
|
"""
|
154
160
|
clipboard_manager = ClipboardManager()
|
155
161
|
loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
|
156
|
-
return loop.run_until_complete(clipboard_manager.paste())
|
162
|
+
return loop.run_until_complete(future=clipboard_manager.paste())
|
157
163
|
|
158
164
|
|
159
165
|
async def paste_from_clipboard_async() -> str:
|
160
|
-
"""
|
161
|
-
Asynchronously paste the output from the clipboard.
|
166
|
+
"""Asynchronously paste the output from the clipboard.
|
162
167
|
|
163
168
|
Returns:
|
164
169
|
str: The content of the clipboard.
|
@@ -168,8 +173,7 @@ async def paste_from_clipboard_async() -> str:
|
|
168
173
|
|
169
174
|
|
170
175
|
def clear_clipboard() -> int:
|
171
|
-
"""
|
172
|
-
Clear the clipboard.
|
176
|
+
"""Clear the clipboard.
|
173
177
|
|
174
178
|
Returns:
|
175
179
|
int: The return code of the command.
|
@@ -180,8 +184,7 @@ def clear_clipboard() -> int:
|
|
180
184
|
|
181
185
|
|
182
186
|
async def clear_clipboard_async() -> int:
|
183
|
-
"""
|
184
|
-
Asynchronously clear the clipboard.
|
187
|
+
"""Asynchronously clear the clipboard.
|
185
188
|
|
186
189
|
Returns:
|
187
190
|
int: The return code of the command.
|
@@ -198,8 +201,7 @@ def fmt_header(
|
|
198
201
|
style2: str = "bold blue",
|
199
202
|
print_out: bool = True,
|
200
203
|
) -> str:
|
201
|
-
"""
|
202
|
-
Generate a header string for visual tests.
|
204
|
+
"""Generate a header string for visual tests.
|
203
205
|
|
204
206
|
Args:
|
205
207
|
title (str): The title to display in the header.
|
@@ -210,6 +212,6 @@ def fmt_header(
|
|
210
212
|
"""
|
211
213
|
text_helper = TextHelper()
|
212
214
|
if print_out:
|
213
|
-
text_helper.print_header(title, sep, length, style1, style2, return_txt=False)
|
215
|
+
text_helper.print_header(title=title, sep=sep, length=length, s1=style1, s2=style2, return_txt=False)
|
214
216
|
return ""
|
215
|
-
return text_helper.print_header(title, sep, length, style1, style2, return_txt=True)
|
217
|
+
return text_helper.print_header(title=title, sep=sep, length=length, s1=style1, s2=style2, return_txt=True)
|
@@ -1,8 +1,12 @@
|
|
1
|
-
|
1
|
+
"""A module for detecting the current operating system."""
|
2
|
+
|
2
3
|
from enum import StrEnum
|
4
|
+
import platform
|
3
5
|
|
4
6
|
|
5
7
|
class OS(StrEnum):
|
8
|
+
"""Enumeration of operating systems."""
|
9
|
+
|
6
10
|
DARWIN = "Darwin"
|
7
11
|
LINUX = "Linux"
|
8
12
|
WINDOWS = "Windows"
|
@@ -0,0 +1 @@
|
|
1
|
+
"""A module for handling responses for functions, methods, and classes in Bear Utils."""
|
@@ -0,0 +1,301 @@
|
|
1
|
+
"""Function Response Class for handling function call results."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from io import StringIO
|
6
|
+
import json
|
7
|
+
from subprocess import CompletedProcess
|
8
|
+
from typing import TYPE_CHECKING, Any, Literal, Self, overload
|
9
|
+
|
10
|
+
from pydantic import BaseModel, Field, field_validator
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from bear_utils.constants.logger_protocol import LoggerProtocol
|
14
|
+
|
15
|
+
SUCCESS: list[str] = ["name", "success"]
|
16
|
+
FAILURE: list[str] = ["name"]
|
17
|
+
|
18
|
+
|
19
|
+
class FunctionResponse(BaseModel):
|
20
|
+
"""A class to represent the response of a function call, including success status, content, and error messages."""
|
21
|
+
|
22
|
+
name: str = Field(default="", description="Name of the function that was called.")
|
23
|
+
returncode: int = Field(default=0, description="Return code of the function, 0 for success, !=0 for failure.")
|
24
|
+
extra: dict = Field(default_factory=dict, description="Additional metadata or information related to the response.")
|
25
|
+
content: list[str] = Field(default=[], description="Content returned by the function call")
|
26
|
+
error: list[str] = Field(default=[], description="Error message if the function call failed")
|
27
|
+
number_of_tasks: int = Field(default=0, description="Number of tasks processed in this response.")
|
28
|
+
logger: LoggerProtocol | None = Field(default=None, description="Logger instance for logging messages.")
|
29
|
+
|
30
|
+
model_config = {
|
31
|
+
"arbitrary_types_allowed": True,
|
32
|
+
}
|
33
|
+
|
34
|
+
def __repr__(self) -> str:
|
35
|
+
"""Return a string representation of Response."""
|
36
|
+
result = StringIO()
|
37
|
+
result.write("Response(")
|
38
|
+
if self.name:
|
39
|
+
result.write(f"name={self.name!r}, ")
|
40
|
+
if self.returncode:
|
41
|
+
result.write(f"success={self.success!r}, ")
|
42
|
+
if self.content:
|
43
|
+
content: str = ", ".join(self.content)
|
44
|
+
result.write(f"content={content!r}, ")
|
45
|
+
if self.error:
|
46
|
+
error: str = ", ".join(self.error)
|
47
|
+
result.write(f"error={error!r}, ")
|
48
|
+
if self.extra:
|
49
|
+
result.write(f"extra={json.dumps(self.extra)!r}, ")
|
50
|
+
if self.number_of_tasks > 0:
|
51
|
+
result.write(f"number_of_tasks={self.number_of_tasks!r}, ")
|
52
|
+
result.write(")")
|
53
|
+
returned_result: str = result.getvalue().replace(", )", ")")
|
54
|
+
result.close()
|
55
|
+
return returned_result
|
56
|
+
|
57
|
+
def __str__(self) -> str:
|
58
|
+
"""Return a string representation of Response."""
|
59
|
+
return self.__repr__()
|
60
|
+
|
61
|
+
@field_validator("name", mode="before")
|
62
|
+
@classmethod
|
63
|
+
def validate_name(cls, value: str | Any) -> str:
|
64
|
+
"""Ensure name is a string, lowercased, and without spaces."""
|
65
|
+
if value is None:
|
66
|
+
return ""
|
67
|
+
if not isinstance(value, str):
|
68
|
+
try:
|
69
|
+
value = str(value)
|
70
|
+
except Exception as e:
|
71
|
+
raise TypeError(f"Name must be a string, got {type(value).__name__}.") from e
|
72
|
+
return value.lower().replace(" ", "_")
|
73
|
+
|
74
|
+
@field_validator("returncode")
|
75
|
+
@classmethod
|
76
|
+
def validate_returncode(cls, value: int) -> int:
|
77
|
+
"""Ensure returncode is an integer above or equal to zero."""
|
78
|
+
if not isinstance(value, int) or value < 0:
|
79
|
+
raise ValueError("Return code must be a non-negative integer.")
|
80
|
+
return value
|
81
|
+
|
82
|
+
@field_validator("extra", mode="before")
|
83
|
+
@classmethod
|
84
|
+
def validate_extra(cls, value: dict | Any) -> dict:
|
85
|
+
"""Ensure extra is always a dictionary."""
|
86
|
+
if value is None:
|
87
|
+
return {}
|
88
|
+
if not isinstance(value, dict):
|
89
|
+
raise TypeError("Extra must be a dictionary.")
|
90
|
+
return value
|
91
|
+
|
92
|
+
@field_validator("content", mode="before")
|
93
|
+
@classmethod
|
94
|
+
def validate_content(cls, value: str | list[str] | Any) -> list[str]:
|
95
|
+
"""Ensure content is always a list of strings."""
|
96
|
+
if isinstance(value, str):
|
97
|
+
return [value]
|
98
|
+
if isinstance(value, list):
|
99
|
+
if not all(isinstance(item, str) for item in value):
|
100
|
+
raise TypeError("Content must be a list of strings.")
|
101
|
+
return value
|
102
|
+
raise TypeError("Content must be a string or a list of strings.")
|
103
|
+
|
104
|
+
@field_validator("error", mode="before")
|
105
|
+
@classmethod
|
106
|
+
def validate_error(cls, value: str | list[str] | Any) -> list[str]:
|
107
|
+
"""Ensure error is always a list of strings."""
|
108
|
+
if isinstance(value, str):
|
109
|
+
return [value]
|
110
|
+
if isinstance(value, list):
|
111
|
+
if not all(isinstance(item, str) for item in value):
|
112
|
+
raise TypeError("Error must be a list of strings.")
|
113
|
+
return value
|
114
|
+
raise TypeError("Error must be a string or a list of strings.")
|
115
|
+
|
116
|
+
@classmethod
|
117
|
+
def from_process(cls, process: CompletedProcess[str], **kwargs) -> Self:
|
118
|
+
"""Create a FunctionResponse from a CompletedProcess object."""
|
119
|
+
returncode: int = process.returncode if process.returncode is not None else 0
|
120
|
+
content: str = process.stdout.strip() if process.stdout else ""
|
121
|
+
error: str = process.stderr.strip() if process.stderr else ""
|
122
|
+
|
123
|
+
if returncode == 0 and not content and error:
|
124
|
+
error, content = content, error
|
125
|
+
|
126
|
+
return cls().add(returncode=returncode, content=content, error=error, **kwargs)
|
127
|
+
|
128
|
+
@property
|
129
|
+
def success(self) -> bool:
|
130
|
+
"""Check if the response indicates success."""
|
131
|
+
return self.returncode == 0
|
132
|
+
|
133
|
+
def successful(
|
134
|
+
self,
|
135
|
+
content: str | list[str] | CompletedProcess,
|
136
|
+
error: str | list[str] = "",
|
137
|
+
returncode: int | None = None,
|
138
|
+
**kwargs,
|
139
|
+
) -> Self:
|
140
|
+
"""Set the response to a success state with optional content."""
|
141
|
+
self.add(content=content, error=error, returncode=returncode or 0, **kwargs)
|
142
|
+
return self
|
143
|
+
|
144
|
+
def fail(
|
145
|
+
self,
|
146
|
+
content: list[str] | str | CompletedProcess = "",
|
147
|
+
error: str | list[str] = "",
|
148
|
+
returncode: int | None = None,
|
149
|
+
**kwargs,
|
150
|
+
) -> Self:
|
151
|
+
"""Set the response to a failure state with an error message."""
|
152
|
+
self.add(content=content, error=error, returncode=returncode or 1, **kwargs)
|
153
|
+
return self
|
154
|
+
|
155
|
+
def _add_error(self, error: str) -> None:
|
156
|
+
"""Append an error message to the existing error."""
|
157
|
+
if error != "":
|
158
|
+
self.error.append(error)
|
159
|
+
|
160
|
+
def _add_to_error(self, error: str | list[str], name: str | None = None) -> None:
|
161
|
+
"""Append additional error messages to the existing error."""
|
162
|
+
try:
|
163
|
+
if isinstance(error, list):
|
164
|
+
for err in error:
|
165
|
+
self._add_error(error=f"{name}: {err}" if name else err)
|
166
|
+
elif isinstance(error, str):
|
167
|
+
self._add_error(error=f"{name}: {error}" if name else error)
|
168
|
+
except Exception as e:
|
169
|
+
raise ValueError(f"Failed to add error: {e!s}") from e
|
170
|
+
|
171
|
+
def _add_content(self, content: str) -> None:
|
172
|
+
"""Append content to the existing content."""
|
173
|
+
if content != "":
|
174
|
+
self.content.append(content)
|
175
|
+
|
176
|
+
def _add_to_content(self, content: str | list[str], name: str | None = None) -> None:
|
177
|
+
"""Append additional content to the existing content."""
|
178
|
+
try:
|
179
|
+
if isinstance(content, list):
|
180
|
+
for item in content:
|
181
|
+
self._add_content(content=f"{name}: {item}" if name else item)
|
182
|
+
elif isinstance(content, str):
|
183
|
+
self._add_content(content=f"{name}: {content}" if name else content)
|
184
|
+
except Exception as e:
|
185
|
+
raise ValueError(f"Failed to add content: {e!s}") from e
|
186
|
+
|
187
|
+
def add(
|
188
|
+
self,
|
189
|
+
content: list[str] | str | FunctionResponse | CompletedProcess | None = None,
|
190
|
+
error: str | list[str] | None = None,
|
191
|
+
returncode: int | None = None,
|
192
|
+
log_output: bool = False,
|
193
|
+
extra: dict[str, Any] | None = None,
|
194
|
+
) -> Self:
|
195
|
+
"""Append additional content to the existing content.
|
196
|
+
|
197
|
+
Args:
|
198
|
+
content (list[str] | str | FunctionResponse | CompletedProcess): The content to add.
|
199
|
+
error (str | list[str] | None): The error message(s) to add.
|
200
|
+
returncode (int | None): The return code of the function call.
|
201
|
+
log_output (bool): Whether to log the output using the logger.
|
202
|
+
**kwargs: Additional metadata to include in the response.
|
203
|
+
|
204
|
+
Returns:
|
205
|
+
Self: The updated FunctionResponse instance.
|
206
|
+
"""
|
207
|
+
try:
|
208
|
+
if isinstance(content, FunctionResponse):
|
209
|
+
if content.extra:
|
210
|
+
self.extra.update(content.extra)
|
211
|
+
self._add_to_error(error=content.error, name=content.name)
|
212
|
+
self._add_to_content(content=content.content, name=content.name)
|
213
|
+
self.number_of_tasks += 1
|
214
|
+
elif isinstance(content, CompletedProcess):
|
215
|
+
result: CompletedProcess[str] = content
|
216
|
+
self._add_to_content(content=result.stdout.strip() if result.stdout else "")
|
217
|
+
self._add_to_error(error=result.stderr.strip() if result.stderr else "")
|
218
|
+
self.returncode = result.returncode
|
219
|
+
self.number_of_tasks += 1
|
220
|
+
elif isinstance(content, str | list):
|
221
|
+
self._add_to_content(content=content)
|
222
|
+
self.number_of_tasks += 1
|
223
|
+
if isinstance(error, str | list):
|
224
|
+
self._add_to_error(error=error)
|
225
|
+
if returncode is not None:
|
226
|
+
self.returncode = returncode
|
227
|
+
if extra is not None and isinstance(extra, dict):
|
228
|
+
self.extra.update(extra)
|
229
|
+
if log_output and self.logger is not None:
|
230
|
+
if content is not None and error is None:
|
231
|
+
if isinstance(content, list):
|
232
|
+
for item in content:
|
233
|
+
self.logger.info(message=f"{self.name}: {item}" if self.name else item)
|
234
|
+
elif isinstance(content, str):
|
235
|
+
self.logger.info(message=f"{self.name}: {content}" if self.name else content)
|
236
|
+
elif error is not None and content is None:
|
237
|
+
if isinstance(error, list):
|
238
|
+
for err in error:
|
239
|
+
self.logger.error(message=f"{self.name}: {err}" if self.name else err)
|
240
|
+
elif isinstance(error, str):
|
241
|
+
self.logger.error(message=f"{self.name}: {error}" if self.name else error)
|
242
|
+
|
243
|
+
except Exception as e:
|
244
|
+
raise ValueError(f"Failed to add content: {e!s}") from e
|
245
|
+
return self
|
246
|
+
|
247
|
+
@overload
|
248
|
+
def done(self, to_dict: Literal[True], suppress: list[str] | None = None) -> dict[str, Any]: ...
|
249
|
+
|
250
|
+
@overload
|
251
|
+
def done(self, to_dict: Literal[False], suppress: list[str] | None = None) -> Self: ...
|
252
|
+
|
253
|
+
def done(self, to_dict: bool = False, suppress: list[str] | None = None) -> dict[str, Any] | Self:
|
254
|
+
"""Convert the FunctionResponse to a dictionary or return the instance itself.
|
255
|
+
|
256
|
+
Args:
|
257
|
+
to_dict (bool): If True, return a dictionary representation.
|
258
|
+
If False, return the FunctionResponse instance.
|
259
|
+
|
260
|
+
Returns:
|
261
|
+
dict[str, Any] | Self: The dictionary representation or the FunctionResponse instance.
|
262
|
+
"""
|
263
|
+
if suppress is None:
|
264
|
+
suppress = []
|
265
|
+
if to_dict:
|
266
|
+
result: dict[str, Any] = {}
|
267
|
+
if self.name and "name" not in suppress:
|
268
|
+
result["name"] = self.name
|
269
|
+
if "success" not in suppress:
|
270
|
+
result.update({"success": self.success})
|
271
|
+
if self.returncode > 0 and "returncode" not in suppress:
|
272
|
+
result["returncode"] = self.returncode
|
273
|
+
if self.number_of_tasks > 0 and "number_of_tasks" not in suppress:
|
274
|
+
result["number_of_tasks"] = self.number_of_tasks
|
275
|
+
if self.content and "content" not in suppress:
|
276
|
+
result["content"] = self.content
|
277
|
+
if self.error and "error" not in suppress:
|
278
|
+
result["error"] = self.error
|
279
|
+
if self.extra:
|
280
|
+
result.update(self.extra)
|
281
|
+
return result
|
282
|
+
return self
|
283
|
+
|
284
|
+
|
285
|
+
def success(
|
286
|
+
content: str | list[str] | CompletedProcess[str] | FunctionResponse,
|
287
|
+
error: str = "",
|
288
|
+
**kwargs,
|
289
|
+
) -> FunctionResponse:
|
290
|
+
"""Create a successful FunctionResponse."""
|
291
|
+
return FunctionResponse().add(content=content, error=error, **kwargs)
|
292
|
+
|
293
|
+
|
294
|
+
def fail(
|
295
|
+
content: str | list[str] | CompletedProcess[str] = "",
|
296
|
+
error: str | list[str] = "",
|
297
|
+
returncode: int | None = None,
|
298
|
+
**kwargs,
|
299
|
+
) -> FunctionResponse:
|
300
|
+
"""Create a failed FunctionResponse."""
|
301
|
+
return FunctionResponse().fail(content=content, error=error, returncode=returncode, **kwargs)
|
@@ -0,0 +1 @@
|
|
1
|
+
"""A module for wrappers in Bear Utils."""
|
@@ -1,3 +1,5 @@
|
|
1
|
+
"""A module for adding rich comparison methods to classes based on an attribute."""
|
2
|
+
|
1
3
|
from collections.abc import Callable
|
2
4
|
from types import NotImplementedType
|
3
5
|
from typing import Any, TypeVar
|
@@ -29,7 +31,7 @@ def add_comparison_methods(attribute: str) -> Callable[[type[T]], type[T]]:
|
|
29
31
|
"""
|
30
32
|
|
31
33
|
def decorator(cls: type[T]) -> type[T]:
|
32
|
-
def extract_comparable_value(self, other: Any) -> NotImplementedType | Any:
|
34
|
+
def extract_comparable_value(self: object, other: Any) -> NotImplementedType | Any: # noqa: ARG001
|
33
35
|
"""Helper to extract the comparable value from the other object."""
|
34
36
|
if isinstance(other, PRIMITIVE_TYPES):
|
35
37
|
return other
|
@@ -39,59 +41,59 @@ def add_comparison_methods(attribute: str) -> Callable[[type[T]], type[T]]:
|
|
39
41
|
|
40
42
|
return NotImplemented
|
41
43
|
|
42
|
-
def equals_method(self, other: Any) -> NotImplementedType | bool:
|
44
|
+
def equals_method(self: object, other: Any) -> NotImplementedType | bool:
|
43
45
|
"""Equal comparison method (__eq__)."""
|
44
46
|
other_val: NotImplementedType | Any = extract_comparable_value(self, other)
|
45
47
|
if other_val is NotImplemented:
|
46
48
|
return NotImplemented
|
47
49
|
return getattr(self, attribute) == other_val
|
48
50
|
|
49
|
-
def not_equals_method(self, other: Any) -> NotImplementedType | bool:
|
51
|
+
def not_equals_method(self: object, other: Any) -> NotImplementedType | bool:
|
50
52
|
"""Not equal comparison method (__ne__)."""
|
51
53
|
other_val: NotImplementedType | Any = extract_comparable_value(self, other)
|
52
54
|
if other_val is NotImplemented:
|
53
55
|
return NotImplemented
|
54
56
|
return getattr(self, attribute) != other_val
|
55
57
|
|
56
|
-
def less_than_method(self, other: Any) -> NotImplementedType | bool:
|
58
|
+
def less_than_method(self: object, other: Any) -> NotImplementedType | bool:
|
57
59
|
"""Less than comparison method (__lt__)."""
|
58
60
|
other_val: NotImplementedType | Any = extract_comparable_value(self, other)
|
59
61
|
if other_val is NotImplemented:
|
60
62
|
return NotImplemented
|
61
63
|
return getattr(self, attribute) < other_val
|
62
64
|
|
63
|
-
def greater_than_method(self, other: Any) -> NotImplementedType | bool:
|
65
|
+
def greater_than_method(self: object, other: Any) -> NotImplementedType | bool:
|
64
66
|
"""Greater than comparison method (__gt__)."""
|
65
67
|
other_val: NotImplementedType | Any = extract_comparable_value(self, other)
|
66
68
|
if other_val is NotImplemented:
|
67
69
|
return NotImplemented
|
68
70
|
return getattr(self, attribute) > other_val
|
69
71
|
|
70
|
-
def less_than_or_equal_method(self, other: Any) -> NotImplementedType | bool:
|
72
|
+
def less_than_or_equal_method(self: object, other: Any) -> NotImplementedType | bool:
|
71
73
|
"""Less than or equal comparison method (__le__)."""
|
72
74
|
other_val: NotImplementedType | Any = extract_comparable_value(self, other)
|
73
75
|
if other_val is NotImplemented:
|
74
76
|
return NotImplemented
|
75
77
|
return getattr(self, attribute) <= other_val
|
76
78
|
|
77
|
-
def greater_than_or_equal_method(self, other: Any) -> NotImplementedType | bool:
|
79
|
+
def greater_than_or_equal_method(self: object, other: Any) -> NotImplementedType | bool:
|
78
80
|
"""Greater than or equal comparison method (__ge__)."""
|
79
81
|
other_val: NotImplementedType | Any = extract_comparable_value(self, other)
|
80
82
|
if other_val is NotImplemented:
|
81
83
|
return NotImplemented
|
82
84
|
return getattr(self, attribute) >= other_val
|
83
85
|
|
84
|
-
def hash_method(self) -> int:
|
86
|
+
def hash_method(self: object) -> int:
|
85
87
|
"""Generate hash based on the attribute used for equality."""
|
86
88
|
return hash(getattr(self, attribute))
|
87
89
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
95
97
|
|
96
98
|
return cls
|
97
99
|
|
bear_utils/files/__init__.py
CHANGED