bear-utils 0.8.6__tar.gz → 0.8.7__tar.gz
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-0.8.6 → bear_utils-0.8.7}/.bumpversion.cfg +1 -1
- {bear_utils-0.8.6 → bear_utils-0.8.7}/PKG-INFO +2 -2
- {bear_utils-0.8.6 → bear_utils-0.8.7}/README.md +1 -1
- {bear_utils-0.8.6 → bear_utils-0.8.7}/pyproject.toml +1 -1
- bear_utils-0.8.7/src/bear_utils/cli/prompt_helpers.py +185 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/extras/responses/function_response.py +116 -83
- {bear_utils-0.8.6 → bear_utils-0.8.7}/tests/test_function_response.py +42 -7
- bear_utils-0.8.6/src/bear_utils/cli/prompt_helpers.py +0 -172
- {bear_utils-0.8.6 → bear_utils-0.8.7}/.gitignore +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/.python-version +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/AGENTS.md +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/config/coverage.ini +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/config/default.toml +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/config/git-changelog.toml +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/config/pytest.ini +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/config/ruff.toml +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/config/vscode/launch.json +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/config/vscode/settings.json +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/config/vscode/tasks.json +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/directory_structure.txt +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/maskfile.md +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/noxfile.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/__main__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/_internal/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/_internal/cli.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/_internal/debug.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/ai/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/ai/ai_helpers/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/ai/ai_helpers/_common.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/ai/ai_helpers/_config.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/ai/ai_helpers/_parsers.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/ai/ai_helpers/_types.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/cache/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/cli/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/cli/commands.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/cli/shell/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/cli/shell/_base_command.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/cli/shell/_base_shell.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/cli/shell/_common.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/config/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/config/config_manager.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/config/dir_manager.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/config/settings_manager.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/constants/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/constants/_exceptions.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/constants/_lazy_typing.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/constants/date_related.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/constants/logger_protocol.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/constants/time_related.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/database/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/database/_db_manager.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/events/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/events/events_class.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/events/events_module.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/extras/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/extras/_async_helpers.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/extras/_tools.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/extras/platform_utils.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/extras/responses/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/extras/wrappers/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/extras/wrappers/add_methods.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/file_handlers/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/file_handlers/_base_file_handler.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/file_handlers/file_handler_factory.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/file_handlers/json_file_handler.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/file_handlers/log_file_handler.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/file_handlers/toml_file_handler.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/file_handlers/txt_file_handler.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/file_handlers/yaml_file_handler.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/ignore_parser.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/graphics/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/graphics/bear_gradient.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/graphics/image_helpers.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/gui/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/gui/gui_tools/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/gui/gui_tools/_settings.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/gui/gui_tools/_types.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/gui/gui_tools/qt_app.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/gui/gui_tools/qt_color_picker.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/gui/gui_tools/qt_file_handler.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/gui/gui_tools/qt_input_dialog.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/_common.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/_console_junk.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/_styles.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/loggers/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/loggers/_base_logger.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/loggers/_base_logger.pyi +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/loggers/_buffer_logger.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/loggers/_console_logger.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/loggers/_console_logger.pyi +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/loggers/_file_logger.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/loggers/_level_sin.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/loggers/_logger.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/loggers/_sub_logger.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/loggers/_sub_logger.pyi +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/monitoring/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/monitoring/_common.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/monitoring/host_monitor.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/time/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/tests/__init__.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/tests/test_add_ord_suffix.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/tests/test_clipboard.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/tests/test_default_shell.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/tests/test_gradient.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/tests/test_logger.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/tests/test_platform_utils.py +0 -0
- {bear_utils-0.8.6 → bear_utils-0.8.7}/uv.lock +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: bear-utils
|
3
|
-
Version: 0.8.
|
3
|
+
Version: 0.8.7
|
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
|
@@ -20,7 +20,7 @@ Requires-Dist: tinydb>=4.8.2
|
|
20
20
|
Requires-Dist: toml>=0.10.2
|
21
21
|
Description-Content-Type: text/markdown
|
22
22
|
|
23
|
-
# Bear Utils v# Bear Utils v0.8.
|
23
|
+
# Bear Utils v# Bear Utils v0.8.7
|
24
24
|
|
25
25
|
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.
|
26
26
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Bear Utils v# Bear Utils v0.8.
|
1
|
+
# Bear Utils v# Bear Utils v0.8.7
|
2
2
|
|
3
3
|
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.
|
4
4
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "bear-utils"
|
3
|
-
version = "0.8.
|
3
|
+
version = "0.8.7"
|
4
4
|
description = "Various utilities for Bear programmers, including a rich logging utility, a disk cache, and a SQLite database wrapper amongst other things."
|
5
5
|
authors = [{ name = "chaz", email = "bright.lid5647@fastmail.com" }]
|
6
6
|
readme = "README.md"
|
@@ -0,0 +1,185 @@
|
|
1
|
+
"""Prompt Helpers Module for user input handling."""
|
2
|
+
|
3
|
+
from typing import Any, overload
|
4
|
+
|
5
|
+
from prompt_toolkit import prompt
|
6
|
+
from prompt_toolkit.completion import WordCompleter
|
7
|
+
from prompt_toolkit.validation import ValidationError, Validator
|
8
|
+
|
9
|
+
from bear_utils.constants._exceptions import UserCancelledError
|
10
|
+
from bear_utils.constants._lazy_typing import OptBool, OptFloat, OptInt, OptStr
|
11
|
+
from bear_utils.logger_manager import get_console
|
12
|
+
|
13
|
+
# TODO: ehhhhhhhh, it is okay
|
14
|
+
|
15
|
+
|
16
|
+
def _parse_bool(value: str) -> bool:
|
17
|
+
"""Parse a string into a boolean value."""
|
18
|
+
lower_value: str = value.lower().strip()
|
19
|
+
if lower_value in ("true", "t", "yes", "y", "1"):
|
20
|
+
return True
|
21
|
+
if lower_value in ("false", "f", "no", "n", "0"):
|
22
|
+
return False
|
23
|
+
raise ValueError(f"Cannot convert '{value}' to boolean")
|
24
|
+
|
25
|
+
|
26
|
+
def _convert_value(value: str, target_type: type) -> str | int | float | bool:
|
27
|
+
"""Convert a string value to the target type."""
|
28
|
+
if target_type is str:
|
29
|
+
return value
|
30
|
+
if target_type is int:
|
31
|
+
return int(value)
|
32
|
+
if target_type is float:
|
33
|
+
return float(value)
|
34
|
+
if target_type is bool:
|
35
|
+
return _parse_bool(value)
|
36
|
+
raise ValueError(f"Unsupported type: {target_type}")
|
37
|
+
|
38
|
+
|
39
|
+
@overload
|
40
|
+
def ask_question(question: str, expected_type: type[bool], default: OptBool = None) -> bool: ...
|
41
|
+
|
42
|
+
|
43
|
+
@overload
|
44
|
+
def ask_question(question: str, expected_type: type[int], default: OptInt = None) -> int: ...
|
45
|
+
|
46
|
+
|
47
|
+
@overload
|
48
|
+
def ask_question(question: str, expected_type: type[float], default: OptFloat = None) -> float: ...
|
49
|
+
|
50
|
+
|
51
|
+
@overload
|
52
|
+
def ask_question(question: str, expected_type: type[str], default: OptStr = None) -> str: ...
|
53
|
+
|
54
|
+
|
55
|
+
def ask_question(question: str, expected_type: type, default: Any = None) -> Any:
|
56
|
+
"""Ask a question and return the answer, ensuring the entered type is correct.
|
57
|
+
|
58
|
+
This function will keep asking until it gets a valid response or the user cancels with Ctrl+C.
|
59
|
+
If the user cancels, a UserCancelledError is raised.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
question: The prompt question to display
|
63
|
+
expected_type: The expected type class (int, float, str, bool)
|
64
|
+
default: Default value if no input is provided
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
The user's response in the expected type
|
68
|
+
|
69
|
+
Raises:
|
70
|
+
UserCancelledError: If the user cancels input with Ctrl+C
|
71
|
+
ValueError: If an unsupported type is specified
|
72
|
+
"""
|
73
|
+
console, sub = get_console("prompt_helpers.py")
|
74
|
+
|
75
|
+
try:
|
76
|
+
while True:
|
77
|
+
console.print(question)
|
78
|
+
response = prompt("> ").strip()
|
79
|
+
|
80
|
+
if not response:
|
81
|
+
if default is not None:
|
82
|
+
return default
|
83
|
+
sub.error("Input required. Please enter a value.")
|
84
|
+
continue
|
85
|
+
try:
|
86
|
+
result: str | int | float | bool = _convert_value(response, expected_type)
|
87
|
+
sub.verbose(f"{expected_type.__name__} detected")
|
88
|
+
return result
|
89
|
+
except ValueError as e:
|
90
|
+
sub.error(f"Invalid input: {e}. Please enter a valid {expected_type.__name__}.")
|
91
|
+
|
92
|
+
except KeyboardInterrupt:
|
93
|
+
raise UserCancelledError("User cancelled input") from None
|
94
|
+
|
95
|
+
|
96
|
+
def ask_yes_no(question: str, default: bool | None = None) -> bool | None:
|
97
|
+
"""Ask a yes or no question and return the answer.
|
98
|
+
|
99
|
+
Args:
|
100
|
+
question: The prompt question to display
|
101
|
+
default: Default value if no input is provided
|
102
|
+
|
103
|
+
Returns:
|
104
|
+
True for yes, False for no, or None if user exits
|
105
|
+
"""
|
106
|
+
console, sub = get_console("prompt_helpers.py")
|
107
|
+
|
108
|
+
try:
|
109
|
+
while True:
|
110
|
+
console.print(question)
|
111
|
+
response = prompt("> ").strip().lower()
|
112
|
+
|
113
|
+
if not response:
|
114
|
+
if default is not None:
|
115
|
+
return default
|
116
|
+
sub.error("Please enter 'yes', 'no', or 'exit'.")
|
117
|
+
continue
|
118
|
+
|
119
|
+
if response in ("exit", "quit"):
|
120
|
+
return None
|
121
|
+
try:
|
122
|
+
return _parse_bool(response)
|
123
|
+
except ValueError:
|
124
|
+
sub.error("Invalid input. Please enter 'yes', 'no', or 'exit'.")
|
125
|
+
except KeyboardInterrupt:
|
126
|
+
sub.warning("KeyboardInterrupt: Exiting the prompt.")
|
127
|
+
return None
|
128
|
+
|
129
|
+
|
130
|
+
def restricted_prompt(
|
131
|
+
question: str, valid_options: list[str], exit_command: str = "exit", case_sensitive: bool = False
|
132
|
+
) -> str | None:
|
133
|
+
"""Continuously prompt the user until they provide a valid response or exit.
|
134
|
+
|
135
|
+
Args:
|
136
|
+
question: The prompt question to display
|
137
|
+
valid_options: List of valid responses
|
138
|
+
exit_command: Command to exit the prompt (default: "exit")
|
139
|
+
case_sensitive: Whether options are case-sensitive (default: False)
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
The user's response or None if they chose to exit
|
143
|
+
"""
|
144
|
+
console, sub = get_console("prompt_helpers.py")
|
145
|
+
completer_options = [*valid_options, exit_command]
|
146
|
+
completer = WordCompleter(completer_options)
|
147
|
+
|
148
|
+
comparison_options = valid_options if case_sensitive else [opt.lower() for opt in valid_options]
|
149
|
+
comparison_exit = exit_command if case_sensitive else exit_command.lower()
|
150
|
+
|
151
|
+
class OptionValidator(Validator):
|
152
|
+
def validate(self, document: Any) -> None:
|
153
|
+
"""Validate the user's input against the valid options."""
|
154
|
+
text: Any = document.text if case_sensitive else document.text.lower()
|
155
|
+
if text and text != comparison_exit and text not in comparison_options:
|
156
|
+
raise ValidationError(
|
157
|
+
message=f"Invalid option. Choose from: {', '.join(valid_options)} or '{exit_command}'",
|
158
|
+
cursor_position=len(document.text),
|
159
|
+
)
|
160
|
+
|
161
|
+
try:
|
162
|
+
while True:
|
163
|
+
console.print(question)
|
164
|
+
response: str = prompt(
|
165
|
+
"> ", completer=completer, validator=OptionValidator(), complete_while_typing=True
|
166
|
+
).strip()
|
167
|
+
comparison_response: str = response if case_sensitive else response.lower()
|
168
|
+
if not response:
|
169
|
+
sub.error("Please enter a valid option or 'exit'.")
|
170
|
+
continue
|
171
|
+
if comparison_response == comparison_exit:
|
172
|
+
return None
|
173
|
+
if comparison_response in comparison_options:
|
174
|
+
if not case_sensitive:
|
175
|
+
idx: int = comparison_options.index(comparison_response)
|
176
|
+
return valid_options[idx]
|
177
|
+
return response
|
178
|
+
|
179
|
+
except KeyboardInterrupt:
|
180
|
+
sub.warning("KeyboardInterrupt: Exiting the prompt.")
|
181
|
+
return None
|
182
|
+
|
183
|
+
|
184
|
+
if __name__ == "__main__":
|
185
|
+
ask_question("What is your age?", int)
|
@@ -2,9 +2,10 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
from
|
5
|
+
from collections.abc import Callable
|
6
6
|
import json
|
7
7
|
from subprocess import CompletedProcess
|
8
|
+
from types import SimpleNamespace as Namespace
|
8
9
|
from typing import Any, Literal, Self, overload
|
9
10
|
|
10
11
|
from pydantic import BaseModel, Field, field_validator
|
@@ -23,35 +24,46 @@ class FunctionResponse(BaseModel):
|
|
23
24
|
extra: dict = Field(default_factory=dict, description="Additional metadata or information related to the response.")
|
24
25
|
content: list[str] = Field(default=[], description="Content returned by the function call")
|
25
26
|
error: list[str] = Field(default=[], description="Error message if the function call failed")
|
27
|
+
sub_tasks: list[FunctionResponse] = Field(default_factory=list, description="List of sub-tasks.")
|
26
28
|
number_of_tasks: int = Field(default=0, description="Number of tasks processed in this response.")
|
27
29
|
logger: LoggerProtocol | None = Field(default=None, description="Logger instance for logging messages.")
|
30
|
+
attrs: Namespace = Field(default_factory=Namespace, description="Storing additional attributes dynamically.")
|
28
31
|
|
29
32
|
model_config = {
|
30
33
|
"arbitrary_types_allowed": True,
|
31
34
|
}
|
32
35
|
|
36
|
+
def __getattr__(self, key: str, default: Any = None) -> Any:
|
37
|
+
if key in FunctionResponse.model_fields:
|
38
|
+
raise AttributeError(f"This should never be called, {key} is a model field.")
|
39
|
+
if hasattr(self.attrs, key):
|
40
|
+
return getattr(self.attrs, key)
|
41
|
+
return default
|
42
|
+
|
43
|
+
def __setattr__(self, key: str, value: Any) -> None:
|
44
|
+
if key in FunctionResponse.model_fields:
|
45
|
+
object.__setattr__(self, key, value)
|
46
|
+
return
|
47
|
+
setattr(self.attrs, key, value)
|
48
|
+
|
33
49
|
def __repr__(self) -> str:
|
34
50
|
"""Return a string representation of Response."""
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
result.write(")")
|
52
|
-
returned_result: str = result.getvalue().replace(", )", ")")
|
53
|
-
result.close()
|
54
|
-
return returned_result
|
51
|
+
parts: list[str] = []
|
52
|
+
|
53
|
+
def add(k: str, v: Any, _bool: bool = True, formatter: Callable | None = None) -> None:
|
54
|
+
if _bool:
|
55
|
+
formatted_value: str = formatter(v) if formatter else repr(v)
|
56
|
+
parts.append(f"{k}={formatted_value}")
|
57
|
+
|
58
|
+
add("name", self.name, bool(self.name))
|
59
|
+
add("returncode", self.returncode, self.returncode != 0)
|
60
|
+
add("success", self.success, bool(self.returncode))
|
61
|
+
add("content", ", ".join(self.content), bool(self.content))
|
62
|
+
add("error", ", ".join(self.error), bool(self.error))
|
63
|
+
add("extra", self.extra, bool(self.extra), json.dumps)
|
64
|
+
add("number_of_tasks", self.number_of_tasks, self.number_of_tasks > 0)
|
65
|
+
|
66
|
+
return f"Response({', '.join(parts)})"
|
55
67
|
|
56
68
|
def __str__(self) -> str:
|
57
69
|
"""Return a string representation of Response."""
|
@@ -124,6 +136,19 @@ class FunctionResponse(BaseModel):
|
|
124
136
|
|
125
137
|
return cls().add(returncode=returncode, content=content, error=error, **kwargs)
|
126
138
|
|
139
|
+
def from_response(self, response: FunctionResponse | Any, **kwargs) -> Self:
|
140
|
+
"""Create a FunctionResponse from another FunctionResponse object."""
|
141
|
+
if not isinstance(response, FunctionResponse):
|
142
|
+
raise TypeError("Expected a FunctionResponse instance.")
|
143
|
+
self.sub_tasks.append(response)
|
144
|
+
return self.add(
|
145
|
+
content=response.content,
|
146
|
+
error=response.error,
|
147
|
+
returncode=response.returncode,
|
148
|
+
log_output=kwargs.pop("log_output", False),
|
149
|
+
**kwargs,
|
150
|
+
)
|
151
|
+
|
127
152
|
@property
|
128
153
|
def success(self) -> bool:
|
129
154
|
"""Check if the response indicates success."""
|
@@ -137,7 +162,7 @@ class FunctionResponse(BaseModel):
|
|
137
162
|
extra: dict[str, Any] | None = None,
|
138
163
|
returncode: int | None = None,
|
139
164
|
log_output: bool = False,
|
140
|
-
) ->
|
165
|
+
) -> Self:
|
141
166
|
"""Add a sub-task response to the FunctionResponse."""
|
142
167
|
func_response: FunctionResponse = FunctionResponse(name=name, logger=self.logger).add(
|
143
168
|
content=content,
|
@@ -147,6 +172,8 @@ class FunctionResponse(BaseModel):
|
|
147
172
|
extra=extra,
|
148
173
|
)
|
149
174
|
self.add(content=func_response)
|
175
|
+
self.sub_tasks.append(func_response)
|
176
|
+
return self
|
150
177
|
|
151
178
|
def successful(
|
152
179
|
self,
|
@@ -170,49 +197,41 @@ class FunctionResponse(BaseModel):
|
|
170
197
|
self.add(content=content, error=error, returncode=returncode or 1, **kwargs)
|
171
198
|
return self
|
172
199
|
|
173
|
-
def
|
174
|
-
"""Append an
|
175
|
-
if
|
176
|
-
|
200
|
+
def _add_item(self, item: str, target_list: list[str]) -> None:
|
201
|
+
"""Append an item to the target list if not empty."""
|
202
|
+
if item != "":
|
203
|
+
target_list.append(item)
|
177
204
|
|
178
|
-
def
|
179
|
-
"""Append
|
205
|
+
def _add_to_list(self, items: str | list[str], target_list: list[str], name: str | None = None) -> None:
|
206
|
+
"""Append items to the target list with optional name prefix."""
|
180
207
|
try:
|
181
|
-
if isinstance(
|
182
|
-
for
|
183
|
-
self.
|
184
|
-
elif isinstance(
|
185
|
-
self.
|
208
|
+
if isinstance(items, list):
|
209
|
+
for item in items:
|
210
|
+
self._add_item(f"{name}: {item}" if name else item, target_list)
|
211
|
+
elif isinstance(items, str):
|
212
|
+
self._add_item(f"{name}: {items}" if name else items, target_list)
|
186
213
|
except Exception as e:
|
187
|
-
raise ValueError(f"Failed to add
|
214
|
+
raise ValueError(f"Failed to add items: {e!s}") from e
|
188
215
|
|
189
|
-
def _add_content(self, content: str) -> None:
|
216
|
+
def _add_content(self, content: str | list[str], name: str | None = None) -> None:
|
190
217
|
"""Append content to the existing content."""
|
191
|
-
|
192
|
-
self.content.append(content)
|
218
|
+
self._add_to_list(content, self.content, name)
|
193
219
|
|
194
|
-
def
|
195
|
-
"""Append
|
196
|
-
|
197
|
-
if isinstance(content, list):
|
198
|
-
for item in content:
|
199
|
-
self._add_content(content=f"{name}: {item}" if name else item)
|
200
|
-
elif isinstance(content, str):
|
201
|
-
self._add_content(content=f"{name}: {content}" if name else content)
|
202
|
-
except Exception as e:
|
203
|
-
raise ValueError(f"Failed to add content: {e!s}") from e
|
220
|
+
def _add_error(self, error: str | list[str], name: str | None = None) -> None:
|
221
|
+
"""Append error to the existing error."""
|
222
|
+
self._add_to_list(error, self.error, name)
|
204
223
|
|
205
224
|
def _handle_function_response(self, func_response: FunctionResponse) -> None:
|
206
225
|
"""Handle a FunctionResponse object and update the current response."""
|
207
226
|
if func_response.extra:
|
208
227
|
self.extra.update(func_response.extra)
|
209
|
-
self.
|
210
|
-
self.
|
228
|
+
self._add_error(error=func_response.error, name=func_response.name)
|
229
|
+
self._add_content(content=func_response.content, name=func_response.name)
|
211
230
|
|
212
231
|
def _handle_completed_process(self, result: CompletedProcess[str]) -> None:
|
213
232
|
"""Handle a CompletedProcess object and update the FunctionResponse."""
|
214
|
-
self.
|
215
|
-
self.
|
233
|
+
self._add_content(content=result.stdout.strip() if result.stdout else "")
|
234
|
+
self._add_error(error=result.stderr.strip() if result.stderr else "")
|
216
235
|
self.returncode = result.returncode
|
217
236
|
|
218
237
|
def add(
|
@@ -244,13 +263,13 @@ class FunctionResponse(BaseModel):
|
|
244
263
|
self._handle_completed_process(result=content)
|
245
264
|
self.number_of_tasks += 1
|
246
265
|
case str() | list() if content:
|
247
|
-
self.
|
266
|
+
self._add_content(content=content)
|
248
267
|
self.number_of_tasks += 1
|
249
268
|
case None:
|
250
269
|
content = None
|
251
270
|
case _:
|
252
271
|
content = None
|
253
|
-
self.
|
272
|
+
self._add_error(error=error) if isinstance(error, (str | list)) else None
|
254
273
|
self.returncode = returncode if returncode is not None else self.returncode
|
255
274
|
self.extra.update(extra) if isinstance(extra, dict) else None
|
256
275
|
if log_output and self.logger is not None and (content is not None or error is not None):
|
@@ -266,18 +285,19 @@ class FunctionResponse(BaseModel):
|
|
266
285
|
logger: LoggerProtocol,
|
267
286
|
) -> None:
|
268
287
|
"""Log the content and error messages if they exist."""
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
288
|
+
|
289
|
+
def _log_messages(messages: str | list[str], log_func: Callable) -> None:
|
290
|
+
if isinstance(messages, str):
|
291
|
+
messages = [messages]
|
292
|
+
if isinstance(messages, list):
|
293
|
+
for msg in messages:
|
294
|
+
log_func(f"{self.name}: {msg}" if self.name else msg)
|
295
|
+
|
296
|
+
if content and isinstance(content, (str | list)):
|
297
|
+
_log_messages(content, logger.info)
|
298
|
+
|
299
|
+
if error and isinstance(error, (str | list)):
|
300
|
+
_log_messages(error, logger.error)
|
281
301
|
|
282
302
|
@overload
|
283
303
|
def done(self, to_dict: Literal[True], suppress: list[str] | None = None) -> dict[str, Any]: ...
|
@@ -295,26 +315,26 @@ class FunctionResponse(BaseModel):
|
|
295
315
|
Returns:
|
296
316
|
dict[str, Any] | Self: The dictionary representation or the FunctionResponse instance.
|
297
317
|
"""
|
318
|
+
if not to_dict:
|
319
|
+
return self
|
320
|
+
|
298
321
|
if suppress is None:
|
299
322
|
suppress = []
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
if
|
305
|
-
result
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
result.update(self.extra)
|
316
|
-
return result
|
317
|
-
return self
|
323
|
+
|
324
|
+
result: dict[str, Any] = {}
|
325
|
+
|
326
|
+
def add(k: str, v: Any, _bool: bool = True) -> None:
|
327
|
+
if k not in suppress and _bool:
|
328
|
+
result[k] = v
|
329
|
+
|
330
|
+
add("name", self.name, bool(self.name))
|
331
|
+
add("success", self.success)
|
332
|
+
add("returncode", self.returncode, self.returncode > 0)
|
333
|
+
add("number_of_tasks", self.number_of_tasks, self.number_of_tasks > 0)
|
334
|
+
add("content", self.content, bool(self.content))
|
335
|
+
add("error", self.error, bool(self.error))
|
336
|
+
result.update(self.extra)
|
337
|
+
return result
|
318
338
|
|
319
339
|
|
320
340
|
def success(
|
@@ -334,3 +354,16 @@ def fail(
|
|
334
354
|
) -> FunctionResponse:
|
335
355
|
"""Create a failed FunctionResponse."""
|
336
356
|
return FunctionResponse().fail(content=content, error=error, returncode=returncode, **kwargs)
|
357
|
+
|
358
|
+
|
359
|
+
if __name__ == "__main__":
|
360
|
+
# Example usage
|
361
|
+
from rich import inspect
|
362
|
+
|
363
|
+
response = FunctionResponse(name="example_function", returncode=0, content=["Task completed successfully."])
|
364
|
+
response.task_id = 124
|
365
|
+
|
366
|
+
inspect(response)
|
367
|
+
print(response)
|
368
|
+
print(response.done(to_dict=True))
|
369
|
+
print(response.done(to_dict=False))
|
@@ -3,7 +3,9 @@
|
|
3
3
|
from subprocess import CompletedProcess
|
4
4
|
|
5
5
|
import pytest
|
6
|
+
from rich.text import Text
|
6
7
|
|
8
|
+
from bear_utils import BufferLogger
|
7
9
|
from bear_utils.extras.responses.function_response import (
|
8
10
|
FAILURE,
|
9
11
|
SUCCESS,
|
@@ -223,10 +225,7 @@ class TestSubTaskMethod:
|
|
223
225
|
main_response.sub_task(name="subtask", content="test")
|
224
226
|
# The sub_task should inherit the parent's returncode
|
225
227
|
assert main_response.number_of_tasks == 1
|
226
|
-
|
227
|
-
|
228
|
-
# The sub_task should inherit the parent's returncode
|
229
|
-
assert main_response.number_of_tasks == 1
|
228
|
+
assert main_response.returncode == 5
|
230
229
|
# Can't directly test the sub-response returncode, but the behavior should be consistent
|
231
230
|
|
232
231
|
def test_sub_task_override_returncode(self):
|
@@ -235,6 +234,7 @@ class TestSubTaskMethod:
|
|
235
234
|
main_response.sub_task(name="subtask", content="test", returncode=2)
|
236
235
|
|
237
236
|
assert main_response.number_of_tasks == 1
|
237
|
+
assert main_response.returncode == 0
|
238
238
|
# The sub-task uses the overridden returncode
|
239
239
|
|
240
240
|
def test_sub_task_with_extra_and_kwargs(self):
|
@@ -351,10 +351,45 @@ class TestAddMethod:
|
|
351
351
|
assert response.returncode == 2
|
352
352
|
assert response.extra == {"metadata": "value"}
|
353
353
|
|
354
|
-
def
|
354
|
+
def get_buffer_logger(self) -> BufferLogger:
|
355
|
+
"""Get a BufferLogger instance for testing."""
|
356
|
+
from bear_utils import BufferLogger # noqa: PLC0415
|
357
|
+
|
358
|
+
if BufferLogger.has_instance():
|
359
|
+
BufferLogger.reset_instance()
|
360
|
+
|
361
|
+
import logging # noqa: PLC0415
|
362
|
+
|
363
|
+
logging.getLogger().handlers.clear()
|
364
|
+
return BufferLogger.get_instance(init=True, name="TestBufferLogger")
|
365
|
+
|
366
|
+
def test_add_with_logging_content(self) -> None:
|
355
367
|
"""Test add method with logging enabled."""
|
356
|
-
|
357
|
-
|
368
|
+
buffer_logger: BufferLogger = self.get_buffer_logger()
|
369
|
+
response = FunctionResponse(logger=buffer_logger)
|
370
|
+
response.add(content="log entry", log_output=True)
|
371
|
+
|
372
|
+
output: str | Text = buffer_logger.trigger_buffer_flush()
|
373
|
+
assert "log entry" in str(output)
|
374
|
+
|
375
|
+
def test_add_with_logging_error(self) -> None:
|
376
|
+
"""Test add method with logging error."""
|
377
|
+
buffer_logger: BufferLogger = self.get_buffer_logger()
|
378
|
+
response = FunctionResponse(logger=buffer_logger)
|
379
|
+
response.add(error="log error", log_output=True)
|
380
|
+
|
381
|
+
output: str | Text = buffer_logger.trigger_buffer_flush()
|
382
|
+
assert "log error" in str(output)
|
383
|
+
|
384
|
+
def test_add_with_logging_content_and_error(self) -> None:
|
385
|
+
"""Test add method with logging content and error."""
|
386
|
+
buffer_logger: BufferLogger = self.get_buffer_logger()
|
387
|
+
response = FunctionResponse(logger=buffer_logger)
|
388
|
+
response.add(content="log content", error="log error", log_output=True)
|
389
|
+
|
390
|
+
output: str | Text = buffer_logger.trigger_buffer_flush()
|
391
|
+
assert "log content" in str(output)
|
392
|
+
assert "log error" in str(output)
|
358
393
|
|
359
394
|
|
360
395
|
class TestDoneMethod:
|
@@ -1,172 +0,0 @@
|
|
1
|
-
"""Prompt Helpers Module for user input handling."""
|
2
|
-
|
3
|
-
from typing import Any, overload
|
4
|
-
|
5
|
-
from prompt_toolkit import prompt
|
6
|
-
from prompt_toolkit.completion import WordCompleter
|
7
|
-
from prompt_toolkit.validation import ValidationError, Validator
|
8
|
-
|
9
|
-
from bear_utils.constants._exceptions import UserCancelledError
|
10
|
-
from bear_utils.constants._lazy_typing import LitBool, LitFloat, LitInt, LitStr, OptBool, OptFloat, OptInt, OptStr
|
11
|
-
from bear_utils.logger_manager import get_console
|
12
|
-
|
13
|
-
# TODO: Overhaul this trash, it is written like absolute garbage.
|
14
|
-
|
15
|
-
|
16
|
-
@overload
|
17
|
-
def ask_question(question: str, expected_type: LitInt, default: OptInt = None, **kwargs) -> int: ...
|
18
|
-
|
19
|
-
|
20
|
-
@overload
|
21
|
-
def ask_question(question: str, expected_type: LitFloat, default: OptFloat = None, **kwargs) -> float: ...
|
22
|
-
|
23
|
-
|
24
|
-
@overload
|
25
|
-
def ask_question(question: str, expected_type: LitStr, default: OptStr = None, **kwargs) -> str: ...
|
26
|
-
|
27
|
-
|
28
|
-
@overload
|
29
|
-
def ask_question(question: str, expected_type: LitBool, default: OptBool = None, **kwargs) -> bool: ...
|
30
|
-
|
31
|
-
|
32
|
-
def ask_question(question: str, expected_type: Any, default: Any = None, **_) -> Any:
|
33
|
-
"""Ask a question and return the answer, ensuring the entered type is correct and a value is entered.
|
34
|
-
|
35
|
-
This function will keep asking until it gets a valid response or the user cancels with Ctrl+C.
|
36
|
-
If the user cancels, a UserCancelledError is raised.
|
37
|
-
|
38
|
-
Args:
|
39
|
-
question: The prompt question to display
|
40
|
-
expected_type: The expected type of the answer (int, float, str, bool)
|
41
|
-
default: Default value if no input is provided
|
42
|
-
|
43
|
-
Returns:
|
44
|
-
The user's response in the expected type
|
45
|
-
|
46
|
-
Raises:
|
47
|
-
UserCancelledError: If the user cancels input with Ctrl+C
|
48
|
-
ValueError: If an unsupported type is specified
|
49
|
-
"""
|
50
|
-
console, sub = get_console("prompt_helpers.py")
|
51
|
-
try:
|
52
|
-
while True:
|
53
|
-
console.print(question)
|
54
|
-
response: str = prompt("> ")
|
55
|
-
if response == "":
|
56
|
-
if default is not None:
|
57
|
-
return default
|
58
|
-
continue
|
59
|
-
match expected_type:
|
60
|
-
case "int":
|
61
|
-
try:
|
62
|
-
result = int(response)
|
63
|
-
sub.verbose("int detected")
|
64
|
-
return result
|
65
|
-
except ValueError:
|
66
|
-
sub.error("Invalid input. Please enter a valid integer.")
|
67
|
-
case "float":
|
68
|
-
try:
|
69
|
-
result = float(response)
|
70
|
-
sub.verbose("float detected")
|
71
|
-
return result
|
72
|
-
except ValueError:
|
73
|
-
sub.error("Invalid input. Please enter a valid float.")
|
74
|
-
case "str":
|
75
|
-
sub.verbose("str detected")
|
76
|
-
return response
|
77
|
-
case "bool":
|
78
|
-
lower_response = response.lower()
|
79
|
-
if lower_response in ("true", "t", "yes", "y", "1"):
|
80
|
-
return True
|
81
|
-
if lower_response in ("false", "f", "no", "n", "0"):
|
82
|
-
return False
|
83
|
-
sub.error("Invalid input. Please enter a valid boolean (true/false, yes/no, etc).")
|
84
|
-
case _:
|
85
|
-
raise ValueError(f"Unsupported type: {expected_type}")
|
86
|
-
except KeyboardInterrupt:
|
87
|
-
raise UserCancelledError("User cancelled input") from None
|
88
|
-
|
89
|
-
|
90
|
-
def ask_yes_no(question: str, default: None | Any = None, **kwargs) -> None | bool:
|
91
|
-
"""Ask a yes or no question and return the answer.
|
92
|
-
|
93
|
-
Args:
|
94
|
-
question: The prompt question to display
|
95
|
-
default: Default value if no input is provided
|
96
|
-
|
97
|
-
Returns:
|
98
|
-
True for yes, False for no, or None if no valid response is given
|
99
|
-
"""
|
100
|
-
kwargs = kwargs or {}
|
101
|
-
sub, console = get_console("prompt_helpers.py")
|
102
|
-
try:
|
103
|
-
while True:
|
104
|
-
console.info(question)
|
105
|
-
response = prompt("> ")
|
106
|
-
|
107
|
-
if response == "":
|
108
|
-
if default is not None:
|
109
|
-
return default
|
110
|
-
continue
|
111
|
-
|
112
|
-
if response.lower() in ["yes", "y"]:
|
113
|
-
return True
|
114
|
-
if response.lower() in ["no", "n"]:
|
115
|
-
return False
|
116
|
-
if response.lower() in ["exit", "quit"]:
|
117
|
-
return None
|
118
|
-
console.error("Invalid input. Please enter 'yes' or 'no' or exit.")
|
119
|
-
continue
|
120
|
-
except KeyboardInterrupt:
|
121
|
-
console.warning("KeyboardInterrupt: Exiting the prompt.")
|
122
|
-
return None
|
123
|
-
|
124
|
-
|
125
|
-
def restricted_prompt(question: str, valid_options: list[str], exit_command: str = "exit", **kwargs) -> None | str:
|
126
|
-
"""Continuously prompt the user until they provide a valid response or exit.
|
127
|
-
|
128
|
-
Args:
|
129
|
-
question: The prompt question to display
|
130
|
-
valid_options: List of valid responses
|
131
|
-
exit_command: Command to exit the prompt (default: "exit")
|
132
|
-
|
133
|
-
Returns:
|
134
|
-
The user's response or None if they chose to exit
|
135
|
-
"""
|
136
|
-
kwargs = kwargs or {}
|
137
|
-
sub, console = get_console("prompt_helpers.py")
|
138
|
-
completer_options: list[str] = [*valid_options, exit_command]
|
139
|
-
completer = WordCompleter(completer_options)
|
140
|
-
|
141
|
-
class OptionValidator(Validator):
|
142
|
-
def validate(self, document: Any) -> None:
|
143
|
-
"""Validate the user's input against the valid options."""
|
144
|
-
text: str = document.text.lower()
|
145
|
-
if text != exit_command and text not in valid_options:
|
146
|
-
raise ValidationError(
|
147
|
-
message=f"Invalid option: {text}. Please choose from {', '.join(valid_options)} or type '{exit_command}' to exit.",
|
148
|
-
cursor_position=len(document.text),
|
149
|
-
)
|
150
|
-
|
151
|
-
try:
|
152
|
-
while True:
|
153
|
-
if console is not None:
|
154
|
-
console.info(question)
|
155
|
-
response = prompt("> ", completer=completer, validator=OptionValidator(), complete_while_typing=True)
|
156
|
-
response = response.lower()
|
157
|
-
else:
|
158
|
-
response = prompt(
|
159
|
-
question, completer=completer, validator=OptionValidator(), complete_while_typing=True
|
160
|
-
)
|
161
|
-
response = response.lower()
|
162
|
-
|
163
|
-
if response == exit_command:
|
164
|
-
return None
|
165
|
-
if response == "":
|
166
|
-
sub.error("No input provided. Please enter a valid option or exit.")
|
167
|
-
continue
|
168
|
-
if response in valid_options:
|
169
|
-
return response
|
170
|
-
except KeyboardInterrupt:
|
171
|
-
sub.warning("KeyboardInterrupt: Exiting the prompt.")
|
172
|
-
return None
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/file_handlers/_base_file_handler.py
RENAMED
File without changes
|
{bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/file_handlers/file_handler_factory.py
RENAMED
File without changes
|
{bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/file_handlers/json_file_handler.py
RENAMED
File without changes
|
{bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/file_handlers/log_file_handler.py
RENAMED
File without changes
|
{bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/file_handlers/toml_file_handler.py
RENAMED
File without changes
|
{bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/file_handlers/txt_file_handler.py
RENAMED
File without changes
|
{bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/files/file_handlers/yaml_file_handler.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/loggers/_base_logger.pyi
RENAMED
File without changes
|
{bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/loggers/_buffer_logger.py
RENAMED
File without changes
|
{bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/loggers/_console_logger.py
RENAMED
File without changes
|
{bear_utils-0.8.6 → bear_utils-0.8.7}/src/bear_utils/logger_manager/loggers/_console_logger.pyi
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|