bear-utils 0.8.24__py3-none-any.whl → 0.8.26__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/cli/prompt_helpers.py +12 -14
- bear_utils/cli/shell/_base_command.py +1 -4
- bear_utils/cli/shell/_base_shell.py +1 -1
- bear_utils/cli/typer_bridge.py +1 -1
- bear_utils/extras/__init__.py +2 -2
- bear_utils/extras/_tools.py +101 -33
- bear_utils/extras/responses/function_response.py +103 -54
- bear_utils/extras/wrappers/string_io.py +46 -0
- {bear_utils-0.8.24.dist-info → bear_utils-0.8.26.dist-info}/METADATA +2 -2
- {bear_utils-0.8.24.dist-info → bear_utils-0.8.26.dist-info}/RECORD +11 -10
- {bear_utils-0.8.24.dist-info → bear_utils-0.8.26.dist-info}/WHEEL +0 -0
bear_utils/cli/prompt_helpers.py
CHANGED
@@ -107,26 +107,25 @@ def ask_yes_no(question: str, default: bool | None = None) -> bool | None:
|
|
107
107
|
Returns:
|
108
108
|
True for yes, False for no, or None if user exits
|
109
109
|
"""
|
110
|
-
console,
|
110
|
+
console, _ = get_console("prompt_helpers.py")
|
111
111
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
response: str = prompt("> ").strip().lower()
|
112
|
+
while True:
|
113
|
+
try:
|
114
|
+
response: str = prompt(f"{question}\n> ").strip().lower()
|
116
115
|
if not response:
|
117
116
|
if default is not None:
|
118
117
|
return default
|
119
|
-
|
118
|
+
console.print("Please enter 'yes', 'no', or 'exit'.")
|
120
119
|
continue
|
121
120
|
if _parse_exit(response):
|
122
121
|
return None
|
123
122
|
try:
|
124
123
|
return _parse_bool(response)
|
125
124
|
except ValueError:
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
125
|
+
console.print("Invalid input. Please enter 'yes', 'no', or 'exit'.", style="red")
|
126
|
+
except KeyboardInterrupt:
|
127
|
+
console.print("KeyboardInterrupt: Exiting the prompt.", style="yellow")
|
128
|
+
return None
|
130
129
|
|
131
130
|
|
132
131
|
def restricted_prompt(
|
@@ -165,16 +164,15 @@ def restricted_prompt(
|
|
165
164
|
|
166
165
|
try:
|
167
166
|
while True:
|
168
|
-
console.print(question)
|
169
167
|
response: str = prompt(
|
170
|
-
"> ",
|
168
|
+
f"{question}\n> ",
|
171
169
|
completer=completer,
|
172
170
|
validator=OptionValidator(),
|
173
171
|
complete_while_typing=True,
|
174
172
|
).strip()
|
175
173
|
comparison_response: str = response if case_sensitive else response.lower()
|
176
174
|
if not response:
|
177
|
-
console.
|
175
|
+
console.print("Please enter a valid option or 'exit'.", style="red")
|
178
176
|
continue
|
179
177
|
if comparison_response == comparison_exit:
|
180
178
|
return None
|
@@ -184,5 +182,5 @@ def restricted_prompt(
|
|
184
182
|
return valid_options[idx]
|
185
183
|
return response
|
186
184
|
except KeyboardInterrupt:
|
187
|
-
console.
|
185
|
+
console.print("KeyboardInterrupt: Exiting the prompt.", style="yellow")
|
188
186
|
return None
|
@@ -13,8 +13,8 @@ import subprocess
|
|
13
13
|
from subprocess import CompletedProcess
|
14
14
|
from typing import Self, override
|
15
15
|
|
16
|
-
from bear_utils.logger_manager.logger_protocol import LoggerProtocol
|
17
16
|
from bear_utils.logger_manager import VERBOSE, BaseLogger, SubConsoleLogger
|
17
|
+
from bear_utils.logger_manager.logger_protocol import LoggerProtocol
|
18
18
|
|
19
19
|
from ._base_command import BaseShellCommand
|
20
20
|
from ._common import DEFAULT_SHELL
|
bear_utils/cli/typer_bridge.py
CHANGED
@@ -41,7 +41,7 @@ class TyperBridge(SingletonBase):
|
|
41
41
|
self.console: AsyncLoggerProtocol | LoggerProtocol | Console = console or Console()
|
42
42
|
self.command_meta: dict[str, CommandMeta] = {}
|
43
43
|
|
44
|
-
def
|
44
|
+
def alias(self, *alias_names: str) -> Callable[..., Callable[..., Any]]:
|
45
45
|
"""Register aliases as hidden Typer commands."""
|
46
46
|
|
47
47
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
bear_utils/extras/__init__.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
from singleton_base import SingletonBase
|
4
4
|
|
5
|
-
from ._tools import ClipboardManager, clear_clipboard, copy_to_clipboard,
|
5
|
+
from ._tools import ClipboardManager, ascii_header, clear_clipboard, copy_to_clipboard, paste_from_clipboard
|
6
6
|
from .platform_utils import OS, get_platform, is_linux, is_macos, is_windows
|
7
7
|
from .wrappers.add_methods import add_comparison_methods
|
8
8
|
|
@@ -11,9 +11,9 @@ __all__ = [
|
|
11
11
|
"ClipboardManager",
|
12
12
|
"SingletonBase",
|
13
13
|
"add_comparison_methods",
|
14
|
+
"ascii_header",
|
14
15
|
"clear_clipboard",
|
15
16
|
"copy_to_clipboard",
|
16
|
-
"fmt_header",
|
17
17
|
"get_platform",
|
18
18
|
"is_linux",
|
19
19
|
"is_macos",
|
bear_utils/extras/_tools.py
CHANGED
@@ -5,42 +5,16 @@ from functools import cached_property
|
|
5
5
|
import shutil
|
6
6
|
from typing import TYPE_CHECKING
|
7
7
|
|
8
|
+
from rich.console import Console
|
9
|
+
|
8
10
|
from bear_utils.cli.shell._base_command import BaseShellCommand as ShellCommand
|
9
11
|
from bear_utils.cli.shell._base_shell import AsyncShellSession
|
10
12
|
from bear_utils.extras.platform_utils import OS, get_platform
|
11
|
-
from bear_utils.logger_manager.loggers.base_logger import BaseLogger
|
12
13
|
|
13
14
|
if TYPE_CHECKING:
|
14
15
|
from subprocess import CompletedProcess
|
15
16
|
|
16
17
|
|
17
|
-
class TextHelper:
|
18
|
-
@cached_property
|
19
|
-
def local_console(self) -> BaseLogger:
|
20
|
-
from bear_utils.logger_manager import BaseLogger # noqa: PLC0415
|
21
|
-
|
22
|
-
init: bool = not BaseLogger.has_instance()
|
23
|
-
return BaseLogger.get_instance(init=init)
|
24
|
-
|
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:
|
34
|
-
"""Generate a header string"""
|
35
|
-
# FIXME: There are probably better ways to do this, but this is OK.
|
36
|
-
fill: str = sep * length
|
37
|
-
title = f" {title} ".center(length, sep).replace(title, f"[{s1}]{title}[/{s1}]")
|
38
|
-
output_text: str = f"\n{fill}\n{title}\n{fill}\n"
|
39
|
-
if not return_txt:
|
40
|
-
self.local_console.print(output_text, style=s2)
|
41
|
-
return output_text
|
42
|
-
|
43
|
-
|
44
18
|
class ClipboardManager:
|
45
19
|
"""A class to manage clipboard operations such as copying, pasting, and clearing.
|
46
20
|
|
@@ -201,9 +175,71 @@ async def clear_clipboard_async() -> int:
|
|
201
175
|
return await clipboard_manager.clear()
|
202
176
|
|
203
177
|
|
204
|
-
|
178
|
+
class TextHelper:
|
179
|
+
@cached_property
|
180
|
+
def local_console(self) -> Console:
|
181
|
+
return Console()
|
182
|
+
|
183
|
+
def print_header(
|
184
|
+
self,
|
185
|
+
title: str,
|
186
|
+
top_sep: str = "#",
|
187
|
+
left_sep: str = ">",
|
188
|
+
right_sep: str = "<",
|
189
|
+
bottom_sep: str = "#",
|
190
|
+
length: int = 60,
|
191
|
+
s1: str = "bold red",
|
192
|
+
s2: str = "bold blue",
|
193
|
+
return_txt: bool = False,
|
194
|
+
) -> str:
|
195
|
+
"""Generate a header string with customizable separators for each line.
|
196
|
+
|
197
|
+
Args:
|
198
|
+
title: The title text to display
|
199
|
+
top_sep: Character(s) for the top separator line
|
200
|
+
left_sep: Character(s) for the left side of title line
|
201
|
+
right_sep: Character(s) for the right side of title line
|
202
|
+
bottom_sep: Character(s) for the bottom separator line
|
203
|
+
length: Total width of each line
|
204
|
+
s1: Style for the title text
|
205
|
+
s2: Style for the entire header block
|
206
|
+
return_txt: If True, return the text instead of printing
|
207
|
+
"""
|
208
|
+
# Top line: all top_sep characters
|
209
|
+
top_line: str = top_sep * length
|
210
|
+
|
211
|
+
# Bottom line: all bottom_sep characters
|
212
|
+
bottom_line: str = bottom_sep * length
|
213
|
+
|
214
|
+
# Title line: left_sep chars + title + right_sep chars
|
215
|
+
title_with_spaces = f" {title} "
|
216
|
+
styled_title = f"[{s1}]{title}[/{s1}]"
|
217
|
+
|
218
|
+
# Calculate padding needed on each side
|
219
|
+
title_length = len(title_with_spaces)
|
220
|
+
remaining_space = length - title_length
|
221
|
+
left_padding = remaining_space // 2
|
222
|
+
right_padding = remaining_space - left_padding
|
223
|
+
|
224
|
+
# Build the title line with different left and right separators
|
225
|
+
title_line = (
|
226
|
+
(left_sep * left_padding) + title_with_spaces.replace(title, styled_title) + (right_sep * right_padding)
|
227
|
+
)
|
228
|
+
|
229
|
+
# Assemble the complete header
|
230
|
+
output_text: str = f"\n{top_line}\n{title_line}\n{bottom_line}\n"
|
231
|
+
|
232
|
+
if not return_txt:
|
233
|
+
self.local_console.print(output_text, style=s2)
|
234
|
+
return output_text
|
235
|
+
|
236
|
+
|
237
|
+
def ascii_header(
|
205
238
|
title: str,
|
206
|
-
|
239
|
+
top_sep: str = "#",
|
240
|
+
left_sep: str = ">",
|
241
|
+
right_sep: str = "<",
|
242
|
+
bottom_sep: str = "#",
|
207
243
|
length: int = 60,
|
208
244
|
style1: str = "bold red",
|
209
245
|
style2: str = "bold blue",
|
@@ -213,13 +249,45 @@ def fmt_header(
|
|
213
249
|
|
214
250
|
Args:
|
215
251
|
title (str): The title to display in the header.
|
216
|
-
|
252
|
+
top_sep (str): The character to use for the top separator line. Defaults to '#'.
|
253
|
+
left_sep (str): The character to use for the left side of title line. Defaults to '>'.
|
254
|
+
right_sep (str): The character to use for the right side of title line. Defaults to '<'.
|
255
|
+
bottom_sep (str): The character to use for the bottom separator line. Defaults to '#'.
|
217
256
|
length (int): The total length of the header line. Defaults to 60.
|
218
257
|
style1 (str): The style for the title text. Defaults to 'bold red'.
|
219
258
|
style2 (str): The style for the separator text. Defaults to 'bold blue'.
|
259
|
+
print_out (bool): Whether to print the header or just return it. Defaults to True.
|
220
260
|
"""
|
221
261
|
text_helper = TextHelper()
|
222
262
|
if print_out:
|
223
|
-
text_helper.print_header(
|
263
|
+
text_helper.print_header(
|
264
|
+
title=title,
|
265
|
+
top_sep=top_sep,
|
266
|
+
left_sep=left_sep,
|
267
|
+
right_sep=right_sep,
|
268
|
+
bottom_sep=bottom_sep,
|
269
|
+
length=length,
|
270
|
+
s1=style1,
|
271
|
+
s2=style2,
|
272
|
+
return_txt=False,
|
273
|
+
)
|
224
274
|
return ""
|
225
|
-
return text_helper.print_header(
|
275
|
+
return text_helper.print_header(
|
276
|
+
title=title,
|
277
|
+
top_sep=top_sep,
|
278
|
+
left_sep=left_sep,
|
279
|
+
right_sep=right_sep,
|
280
|
+
bottom_sep=bottom_sep,
|
281
|
+
length=length,
|
282
|
+
s1=style1,
|
283
|
+
s2=style2,
|
284
|
+
return_txt=True,
|
285
|
+
)
|
286
|
+
|
287
|
+
|
288
|
+
if __name__ == "__main__":
|
289
|
+
# Example usage of the TextHelper
|
290
|
+
text_helper = TextHelper()
|
291
|
+
text_helper.print_header("My Title", top_sep="#", bottom_sep="#")
|
292
|
+
text_helper.print_header("My Title", top_sep="=", left_sep=">", right_sep="<", bottom_sep="=")
|
293
|
+
text_helper.print_header("My Title", top_sep="-", left_sep="[", right_sep="]", bottom_sep="-")
|
@@ -41,12 +41,24 @@ class FunctionResponse(BaseModel):
|
|
41
41
|
"arbitrary_types_allowed": True,
|
42
42
|
}
|
43
43
|
|
44
|
+
def _has_attr(self, key: str) -> bool:
|
45
|
+
"""Check if the attribute exists in the attrs Namespace."""
|
46
|
+
return hasattr(self.attrs, key)
|
47
|
+
|
48
|
+
def _get_attr(self, key: str, default: Any = None) -> Any:
|
49
|
+
"""Get the attribute from the attrs Namespace, returning default if not found."""
|
50
|
+
if self._has_attr(key):
|
51
|
+
return getattr(self.attrs, key, default)
|
52
|
+
return default
|
53
|
+
|
54
|
+
def _get_attrs(self) -> dict[str, Any]:
|
55
|
+
"""Get all attributes from the attrs Namespace as a dictionary."""
|
56
|
+
return {k: getattr(self.attrs, k, None) for k in self.attrs.__dict__ if not k.startswith("_")}
|
57
|
+
|
44
58
|
def __getattr__(self, key: str, default: Any = None) -> Any:
|
45
59
|
if key in FunctionResponse.model_fields:
|
46
60
|
raise AttributeError(f"This should never be called, {key} is a model field.")
|
47
|
-
|
48
|
-
return getattr(self.attrs, key)
|
49
|
-
return default
|
61
|
+
return self._get_attr(key, default)
|
50
62
|
|
51
63
|
def __setattr__(self, key: str, value: Any) -> None:
|
52
64
|
if key in FunctionResponse.model_fields:
|
@@ -58,19 +70,23 @@ class FunctionResponse(BaseModel):
|
|
58
70
|
"""Return a string representation of Response."""
|
59
71
|
parts: list[str] = []
|
60
72
|
|
61
|
-
def add(k: str, v: Any, _bool: bool = True,
|
73
|
+
def add(k: str, v: Any, _bool: bool = True, fmt_func: Callable | None = None) -> None:
|
62
74
|
if _bool:
|
63
|
-
formatted_value: str =
|
75
|
+
formatted_value: str = fmt_func(v) if fmt_func else repr(v)
|
64
76
|
parts.append(f"{k}={formatted_value}")
|
65
77
|
|
66
78
|
add("name", self.name, bool(self.name))
|
67
|
-
add("returncode", self.returncode, self.returncode != 0)
|
68
|
-
add("success", self.success, bool(self.returncode))
|
69
79
|
add("content", ", ".join(self.content), bool(self.content))
|
70
|
-
add("error", ", ".join(self.error), bool(self.error))
|
71
|
-
add("extra", self.extra, bool(self.extra), json.dumps)
|
72
|
-
add("number_of_tasks", self.number_of_tasks, self.number_of_tasks > 0)
|
73
80
|
|
81
|
+
# error state depends on returncode or error
|
82
|
+
add("error", ", ".join(self.error), self.error_state)
|
83
|
+
add("success", self.success, _bool=True)
|
84
|
+
add("returncode", self.returncode, self.error_state)
|
85
|
+
add("number_of_tasks", self.number_of_tasks, self.error_state)
|
86
|
+
add("extra", self.extra, bool(self.extra), json.dumps)
|
87
|
+
attrs = self._get_attrs()
|
88
|
+
for attr in attrs:
|
89
|
+
add(attr, attrs[attr])
|
74
90
|
return f"Response({', '.join(parts)})"
|
75
91
|
|
76
92
|
def __str__(self) -> str:
|
@@ -139,28 +155,19 @@ class FunctionResponse(BaseModel):
|
|
139
155
|
content: str = process.stdout.strip() if process.stdout else ""
|
140
156
|
error: str = process.stderr.strip() if process.stderr else ""
|
141
157
|
|
142
|
-
if returncode == 0 and not content and error:
|
158
|
+
if returncode == 0 and not content and error: # Some processes return empty stdout on success
|
143
159
|
error, content = content, error
|
144
|
-
|
145
160
|
return cls().add(returncode=returncode, content=content, error=error, **kwargs)
|
146
161
|
|
147
|
-
|
148
|
-
|
149
|
-
if
|
150
|
-
|
151
|
-
self.sub_tasks.append(response)
|
152
|
-
return self.add(
|
153
|
-
content=response.content,
|
154
|
-
error=response.error,
|
155
|
-
returncode=response.returncode,
|
156
|
-
log_output=kwargs.pop("log_output", False),
|
157
|
-
**kwargs,
|
158
|
-
)
|
162
|
+
@property
|
163
|
+
def error_state(self) -> bool:
|
164
|
+
"""Check if the returncode or error field indicates an error state."""
|
165
|
+
return self.returncode != 0 or bool(self.error)
|
159
166
|
|
160
167
|
@property
|
161
168
|
def success(self) -> bool:
|
162
169
|
"""Check if the response indicates success."""
|
163
|
-
return self.returncode == 0
|
170
|
+
return self.returncode == 0 and not bool(self.error)
|
164
171
|
|
165
172
|
def sub_task(
|
166
173
|
self,
|
@@ -185,13 +192,15 @@ class FunctionResponse(BaseModel):
|
|
185
192
|
|
186
193
|
def successful(
|
187
194
|
self,
|
188
|
-
content: str | list[str] | CompletedProcess,
|
195
|
+
content: str | list[str] | CompletedProcess | FunctionResponse,
|
189
196
|
error: str | list[str] = "",
|
190
197
|
returncode: int | None = None,
|
198
|
+
log_output: bool = False,
|
191
199
|
**kwargs,
|
192
200
|
) -> Self:
|
193
201
|
"""Set the response to a success state with optional content."""
|
194
|
-
|
202
|
+
return_code: int = returncode if returncode is not None else 0
|
203
|
+
self.add(content=content, error=error, returncode=return_code, log_output=log_output, **kwargs)
|
195
204
|
return self
|
196
205
|
|
197
206
|
def fail(
|
@@ -199,16 +208,17 @@ class FunctionResponse(BaseModel):
|
|
199
208
|
content: list[str] | str | CompletedProcess = "",
|
200
209
|
error: str | list[str] = "",
|
201
210
|
returncode: int | None = None,
|
211
|
+
log_output: bool = False,
|
202
212
|
**kwargs,
|
203
213
|
) -> Self:
|
204
214
|
"""Set the response to a failure state with an error message."""
|
205
|
-
|
215
|
+
return_code: int = returncode if returncode is not None else 1
|
216
|
+
self.add(content=content, error=error, returncode=return_code, log_output=log_output, **kwargs)
|
206
217
|
return self
|
207
218
|
|
208
219
|
def _add_item(self, item: str, target_list: list[str]) -> None:
|
209
220
|
"""Append an item to the target list if not empty."""
|
210
|
-
if item != ""
|
211
|
-
target_list.append(item)
|
221
|
+
target_list.append(item) if item != "" else None
|
212
222
|
|
213
223
|
def _add_to_list(self, items: str | list[str], target_list: list[str], name: str | None = None) -> None:
|
214
224
|
"""Append items to the target list with optional name prefix."""
|
@@ -222,24 +232,24 @@ class FunctionResponse(BaseModel):
|
|
222
232
|
raise ValueError(f"Failed to add items: {e!s}") from e
|
223
233
|
|
224
234
|
def _add_content(self, content: str | list[str], name: str | None = None) -> None:
|
225
|
-
"""
|
226
|
-
self._add_to_list(content, self.content, name)
|
235
|
+
"""Add content to the FunctionResponse content list."""
|
236
|
+
return self._add_to_list(items=content, target_list=self.content, name=name)
|
227
237
|
|
228
238
|
def _add_error(self, error: str | list[str], name: str | None = None) -> None:
|
229
|
-
"""
|
230
|
-
self._add_to_list(error, self.error, name)
|
239
|
+
"""Add error messages to the FunctionResponse error list."""
|
240
|
+
return self._add_to_list(items=error, target_list=self.error, name=name)
|
231
241
|
|
232
242
|
def _handle_function_response(self, func_response: FunctionResponse) -> None:
|
233
243
|
"""Handle a FunctionResponse object and update the current response."""
|
234
244
|
if func_response.extra:
|
235
245
|
self.extra.update(func_response.extra)
|
236
|
-
self.
|
237
|
-
self.
|
246
|
+
self._add_content(func_response.content, name=func_response.name)
|
247
|
+
self._add_error(func_response.error, name=func_response.name)
|
238
248
|
|
239
249
|
def _handle_completed_process(self, result: CompletedProcess[str]) -> None:
|
240
250
|
"""Handle a CompletedProcess object and update the FunctionResponse."""
|
241
|
-
self._add_content(
|
242
|
-
self._add_error(
|
251
|
+
self._add_content(result.stdout.strip())
|
252
|
+
self._add_error(result.stderr.strip())
|
243
253
|
self.returncode = result.returncode
|
244
254
|
|
245
255
|
def _handle_content(self, content: list[str] | str | FunctionResponse | CompletedProcess | Any) -> None:
|
@@ -249,7 +259,7 @@ class FunctionResponse(BaseModel):
|
|
249
259
|
elif isinstance(content, CompletedProcess):
|
250
260
|
self._handle_completed_process(result=content)
|
251
261
|
elif isinstance(content, (str | list)):
|
252
|
-
self.
|
262
|
+
self._add_to_list(content, self.content)
|
253
263
|
else:
|
254
264
|
return
|
255
265
|
self.number_of_tasks += 1
|
@@ -293,7 +303,7 @@ class FunctionResponse(BaseModel):
|
|
293
303
|
if content is not None:
|
294
304
|
self._handle_content(content=content)
|
295
305
|
if error is not None and isinstance(error, (str | list)):
|
296
|
-
self.
|
306
|
+
self._add_to_list(error, target_list=self.error)
|
297
307
|
if isinstance(returncode, int):
|
298
308
|
self.returncode = returncode
|
299
309
|
if isinstance(extra, dict):
|
@@ -302,9 +312,7 @@ class FunctionResponse(BaseModel):
|
|
302
312
|
self._log_handling(content=content, error=error, logger=self.logger)
|
303
313
|
except Exception as e:
|
304
314
|
raise ValueError(f"Failed to add content: {e!s}") from e
|
305
|
-
if to_dict
|
306
|
-
return self.done(to_dict=True)
|
307
|
-
return self
|
315
|
+
return self.done(to_dict=True) if to_dict else self
|
308
316
|
|
309
317
|
def _log_handling(
|
310
318
|
self,
|
@@ -350,17 +358,34 @@ class FunctionResponse(BaseModel):
|
|
350
358
|
res.conditional_run()
|
351
359
|
|
352
360
|
@overload
|
353
|
-
def done(
|
361
|
+
def done(
|
362
|
+
self,
|
363
|
+
to_dict: Literal[True],
|
364
|
+
suppress: list[str] | None = None,
|
365
|
+
include: list[str] | None = None,
|
366
|
+
) -> dict[str, Any]: ...
|
354
367
|
|
355
368
|
@overload
|
356
|
-
def done(
|
369
|
+
def done(
|
370
|
+
self,
|
371
|
+
to_dict: Literal[False],
|
372
|
+
suppress: list[str] | None = None,
|
373
|
+
include: list[str] | None = None,
|
374
|
+
) -> Self: ...
|
357
375
|
|
358
|
-
def done(
|
376
|
+
def done(
|
377
|
+
self,
|
378
|
+
to_dict: bool = False,
|
379
|
+
suppress: list[str] | None = None,
|
380
|
+
include: list[str] | None = None,
|
381
|
+
) -> dict[str, Any] | Self:
|
359
382
|
"""Convert the FunctionResponse to a dictionary or return the instance itself.
|
360
383
|
|
361
384
|
Args:
|
362
385
|
to_dict (bool): If True, return a dictionary representation.
|
363
386
|
If False, return the FunctionResponse instance.
|
387
|
+
suppress (list[str] | None): List of keys to suppress in the output dictionary.
|
388
|
+
include (list[str] | None): List of keys to include in the output dictionary.
|
364
389
|
|
365
390
|
Returns:
|
366
391
|
dict[str, Any] | Self: The dictionary representation or the FunctionResponse instance.
|
@@ -371,36 +396,60 @@ class FunctionResponse(BaseModel):
|
|
371
396
|
if suppress is None:
|
372
397
|
suppress = []
|
373
398
|
|
399
|
+
if include is None:
|
400
|
+
include = []
|
401
|
+
|
374
402
|
result: dict[str, Any] = {}
|
375
403
|
|
376
404
|
def add(k: str, v: Any, _bool: bool = True) -> None:
|
377
405
|
if k not in suppress and _bool:
|
378
406
|
result[k] = v
|
379
407
|
|
408
|
+
def dict_include(_bool: bool = True) -> dict[str, Any]:
|
409
|
+
result.update(self.extra)
|
410
|
+
for k in include:
|
411
|
+
if hasattr(self.attrs, k) and _bool:
|
412
|
+
result[k] = getattr(self.attrs, k)
|
413
|
+
return result
|
414
|
+
|
380
415
|
add("name", self.name, bool(self.name))
|
381
|
-
add("success", self.success)
|
382
|
-
add("returncode", self.returncode, self.returncode > 0)
|
383
|
-
add("number_of_tasks", self.number_of_tasks, (self.number_of_tasks > 0 and not self.success))
|
384
416
|
add("content", self.content, bool(self.content))
|
385
|
-
add("
|
386
|
-
|
387
|
-
|
417
|
+
add("success", self.success, _bool=True)
|
418
|
+
# depends on the error state
|
419
|
+
add("error", self.error, self.error_state)
|
420
|
+
add("returncode", self.returncode, self.error_state)
|
421
|
+
add("number_of_tasks", self.number_of_tasks, self.error_state)
|
422
|
+
|
423
|
+
return dict_include()
|
388
424
|
|
389
425
|
|
390
426
|
def success(
|
391
427
|
content: str | list[str] | CompletedProcess[str] | FunctionResponse,
|
392
428
|
error: str = "",
|
429
|
+
log_output: bool = False,
|
393
430
|
**kwargs,
|
394
431
|
) -> FunctionResponse:
|
395
432
|
"""Create a successful FunctionResponse."""
|
396
|
-
|
433
|
+
res = FunctionResponse()
|
434
|
+
return res.successful(content, error, 0, log_output, **kwargs)
|
397
435
|
|
398
436
|
|
399
437
|
def fail(
|
400
438
|
content: str | list[str] | CompletedProcess[str] = "",
|
401
439
|
error: str | list[str] = "",
|
402
440
|
returncode: int | None = None,
|
441
|
+
log_output: bool = False,
|
403
442
|
**kwargs,
|
404
443
|
) -> FunctionResponse:
|
405
444
|
"""Create a failed FunctionResponse."""
|
406
|
-
|
445
|
+
res = FunctionResponse()
|
446
|
+
return res.fail(content, error, returncode, log_output, **kwargs)
|
447
|
+
|
448
|
+
|
449
|
+
if __name__ == "__main__":
|
450
|
+
# For testing purposes, you can run this module directly.
|
451
|
+
|
452
|
+
response = FunctionResponse(name="test_function")
|
453
|
+
response.add(content=["This is a test content."], error=["No errors."], returncode=1, extra={"smelly": "value"})
|
454
|
+
response.poop = "farts"
|
455
|
+
print(response.done(to_dict=True))
|
@@ -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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: bear-utils
|
3
|
-
Version: 0.8.
|
3
|
+
Version: 0.8.26
|
4
4
|
Summary: Various utilities for Bear programmers, including a rich logging utility, a disk cache, and a SQLite database wrapper amongst other things.
|
5
5
|
Author-email: chaz <bright.lid5647@fastmail.com>
|
6
6
|
Requires-Python: >=3.12
|
@@ -25,7 +25,7 @@ Provides-Extra: gui
|
|
25
25
|
Requires-Dist: pyqt6>=6.9.0; extra == 'gui'
|
26
26
|
Description-Content-Type: text/markdown
|
27
27
|
|
28
|
-
# Bear Utils v# Bear Utils v0.8.
|
28
|
+
# Bear Utils v# Bear Utils v0.8.26
|
29
29
|
|
30
30
|
Personal set of tools and utilities for Python projects, focusing on modularity and ease of use. This library includes components for caching, database management, logging, time handling, file operations, CLI prompts, image processing, clipboard interaction, gradient utilities, event systems, and async helpers.
|
31
31
|
|
@@ -12,11 +12,11 @@ bear_utils/ai/ai_helpers/_types.py,sha256=rmnl8mTlUj0LyL9USzTb-EN_31TtXY6qzhkOEu
|
|
12
12
|
bear_utils/cache/__init__.py,sha256=c9z1mLhWpZJHZdXRlviYQXl8tc9KTJCM8vin3moDO3I,4578
|
13
13
|
bear_utils/cli/__init__.py,sha256=H2QpLyHpQS_Yn3sF2px7n4KqT97LEe7Oyzafg2iHcpc,503
|
14
14
|
bear_utils/cli/commands.py,sha256=5ppEjvVV_g28WLaIFtKgz-ctzwoo-g-KpHTXNx9xBzo,3161
|
15
|
-
bear_utils/cli/prompt_helpers.py,sha256=
|
16
|
-
bear_utils/cli/typer_bridge.py,sha256=
|
15
|
+
bear_utils/cli/prompt_helpers.py,sha256=VGNwbPDYXO-FoYkACI0Zq7_IUWThexGlycvt9RBzjEU,6844
|
16
|
+
bear_utils/cli/typer_bridge.py,sha256=RE30vf7LQzdGeWvP08W_N_FZ3r2hoD372uHOqTmzcK0,3405
|
17
17
|
bear_utils/cli/shell/__init__.py,sha256=2s3oR6CqLKj1iyERy7YafWT3t3KzTr70Z1yaLKa6IiQ,42
|
18
|
-
bear_utils/cli/shell/_base_command.py,sha256
|
19
|
-
bear_utils/cli/shell/_base_shell.py,sha256=
|
18
|
+
bear_utils/cli/shell/_base_command.py,sha256=-uKvfSYo4f3vMCST19yN19ZtY3N80zQcfu39ogc9vPY,2711
|
19
|
+
bear_utils/cli/shell/_base_shell.py,sha256=6CWAdQTiSQp9t01Cb5rVSgUOD3zRKRR2CLT-XomM-ro,16702
|
20
20
|
bear_utils/cli/shell/_common.py,sha256=_KQyL5lvqOfjonFIwlEOyp3K9G3TSOj19RhgVzfNNpg,669
|
21
21
|
bear_utils/config/__init__.py,sha256=HC_lWpmLF0kbPr5i1Wa2FLER2b446E_GecgU9EPmc04,353
|
22
22
|
bear_utils/config/config_manager.py,sha256=Xj0xOmY-wo_rwfcWiXyxNZWX9NknX_Jm9W56Gx8yyHQ,8244
|
@@ -33,14 +33,15 @@ bear_utils/database/_db_manager.py,sha256=bbg5zG06DcOvd37oafLIqc480BwF9ZW9qWF3ld
|
|
33
33
|
bear_utils/events/__init__.py,sha256=EFqmuzhaEYK9kjkGlrM7bjdjPwFEDbKn6RjJKfIBEJY,414
|
34
34
|
bear_utils/events/events_class.py,sha256=vPDjWrbut8L3TFn7byyYFZpWYM5ADIqtW2Aeh-qtKfQ,1632
|
35
35
|
bear_utils/events/events_module.py,sha256=DkQsDZ5WzM1MH1Msg7mRny40a17Jl31CXbpw4-pICxc,2349
|
36
|
-
bear_utils/extras/__init__.py,sha256=
|
36
|
+
bear_utils/extras/__init__.py,sha256=0Iz6HxhFlZB9ltyHBKCqKiIFFd5u5r_nXuMDQSxA1rE,611
|
37
37
|
bear_utils/extras/_async_helpers.py,sha256=4buULZZkR0n2z13Q8_FK59dMchj0Mup03YBaqSCbveQ,2291
|
38
|
-
bear_utils/extras/_tools.py,sha256
|
38
|
+
bear_utils/extras/_tools.py,sha256=563_-nrhOzQPRLJHBgJcw0xFGghJkPK165M0j9GSxRE,9996
|
39
39
|
bear_utils/extras/platform_utils.py,sha256=Ai7ow7S-_cKb5zFwFh8dkC8xmbMJFy-0_-w3NCERdEw,1362
|
40
40
|
bear_utils/extras/responses/__init__.py,sha256=XbE4VKemrKRwx9E5jqy__OiM_AAjA58ebnqQ2hytnT0,225
|
41
|
-
bear_utils/extras/responses/function_response.py,sha256=
|
41
|
+
bear_utils/extras/responses/function_response.py,sha256=rt_fvAx2WC3o0_T_cN2IcW4JC6FN1UXUb3UauE9EJ0A,18065
|
42
42
|
bear_utils/extras/wrappers/__init__.py,sha256=crh4sKOLvuhNMVX5bJYjCFWtXtH7G47UgNPOHq3HXTk,43
|
43
43
|
bear_utils/extras/wrappers/add_methods.py,sha256=z2XZG2ZoYOB1MaGiLli4NRyyTeRgBy7tuYsiy8mTa9s,4422
|
44
|
+
bear_utils/extras/wrappers/string_io.py,sha256=8VoIoX9G7bgasvQRF7m3zaySahCA_kKU8OveQPItN2k,1563
|
44
45
|
bear_utils/files/__init__.py,sha256=mIdnFSXoDE64ElM43bN2m6KuafURnN82ki0pdqN8q2o,201
|
45
46
|
bear_utils/files/ignore_parser.py,sha256=2KlbfbEn012KGMqW4TdcV6jFbHXGmuCmA5DjjmIZ6_Y,10958
|
46
47
|
bear_utils/files/file_handlers/__init__.py,sha256=VF2IlWNr3UqeSvsbh3YCbLw9cLmlyf64mfeOKuhBdvk,136
|
@@ -87,6 +88,6 @@ bear_utils/monitoring/__init__.py,sha256=9DKNIWTp_voLnaWgiP-wJ-o_N0hYixo-MzjUmg8
|
|
87
88
|
bear_utils/monitoring/_common.py,sha256=LYQFxgTP9fk0cH71IQTuGwBYYPWCqHP_mMRNecoD76M,657
|
88
89
|
bear_utils/monitoring/host_monitor.py,sha256=e0TYRJw9iDj5Ga6y3ck1TBFEeH42Cax5mQYaNU8yams,13241
|
89
90
|
bear_utils/time/__init__.py,sha256=VctjJG17SyEHAFXytI1sZrOrq7zm3hVenIDOJFdaMN0,1424
|
90
|
-
bear_utils-0.8.
|
91
|
-
bear_utils-0.8.
|
92
|
-
bear_utils-0.8.
|
91
|
+
bear_utils-0.8.26.dist-info/METADATA,sha256=P2jf04UyGeSRqiKOfJvCvpDVA6UFCUFNTMrogmwWMx8,8792
|
92
|
+
bear_utils-0.8.26.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
93
|
+
bear_utils-0.8.26.dist-info/RECORD,,
|
File without changes
|