zrb 1.5.11__py3-none-any.whl → 1.5.13__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.
- zrb/builtin/llm/llm_chat.py +1 -1
- zrb/builtin/llm/tool/__init__.py +0 -0
- zrb/builtin/llm/tool/sub_agent.py +125 -0
- zrb/builtin/llm/tool/web.py +0 -2
- zrb/config.py +0 -3
- zrb/llm_config.py +16 -2
- zrb/task/base_task.py +38 -2
- zrb/task/llm/agent.py +5 -8
- zrb/task/llm/context.py +17 -8
- zrb/task/llm/context_enrichment.py +52 -13
- zrb/task/llm/history_summarization.py +3 -5
- zrb/task/llm/prompt.py +7 -4
- zrb/task/llm/tool_wrapper.py +115 -53
- zrb/task/llm_task.py +16 -1
- zrb/util/attr.py +84 -1
- zrb/util/cli/style.py +147 -0
- zrb/util/cli/subcommand.py +22 -1
- zrb/util/cmd/command.py +18 -0
- zrb/util/cmd/remote.py +15 -0
- zrb/util/codemod/modification_mode.py +4 -0
- zrb/util/codemod/modify_class.py +72 -0
- zrb/util/codemod/modify_class_parent.py +68 -0
- zrb/util/codemod/modify_class_property.py +67 -0
- zrb/util/codemod/modify_dict.py +62 -0
- zrb/util/codemod/modify_function.py +75 -3
- zrb/util/codemod/modify_function_call.py +72 -0
- zrb/util/codemod/modify_method.py +77 -0
- zrb/util/codemod/modify_module.py +10 -0
- zrb/util/cron.py +37 -3
- zrb/util/file.py +32 -0
- zrb/util/git.py +113 -0
- zrb/util/git_subtree.py +58 -0
- zrb/util/group.py +64 -2
- zrb/util/load.py +29 -0
- zrb/util/run.py +9 -0
- zrb/util/string/conversion.py +86 -0
- zrb/util/string/format.py +20 -0
- zrb/util/string/name.py +12 -0
- zrb/util/todo.py +165 -4
- {zrb-1.5.11.dist-info → zrb-1.5.13.dist-info}/METADATA +4 -6
- {zrb-1.5.11.dist-info → zrb-1.5.13.dist-info}/RECORD +43 -41
- {zrb-1.5.11.dist-info → zrb-1.5.13.dist-info}/WHEEL +0 -0
- {zrb-1.5.11.dist-info → zrb-1.5.13.dist-info}/entry_points.txt +0 -0
zrb/task/llm/tool_wrapper.py
CHANGED
@@ -1,58 +1,107 @@
|
|
1
1
|
import functools
|
2
2
|
import inspect
|
3
3
|
import traceback
|
4
|
+
import typing
|
4
5
|
from collections.abc import Callable
|
5
6
|
|
7
|
+
from pydantic_ai import RunContext, Tool
|
8
|
+
|
9
|
+
from zrb.context.any_context import AnyContext
|
6
10
|
from zrb.task.llm.error import ToolExecutionError
|
7
11
|
from zrb.util.run import run_async
|
8
12
|
|
9
13
|
|
10
|
-
def wrap_tool(func: Callable) ->
|
11
|
-
"""Wraps a tool function to handle exceptions and context propagation.
|
14
|
+
def wrap_tool(func: Callable, ctx: AnyContext) -> Tool:
|
15
|
+
"""Wraps a tool function to handle exceptions and context propagation."""
|
16
|
+
original_sig = inspect.signature(func)
|
17
|
+
# Use helper function for clarity
|
18
|
+
needs_run_context_for_pydantic = _has_context_parameter(original_sig, RunContext)
|
19
|
+
needs_any_context_for_injection = _has_context_parameter(original_sig, AnyContext)
|
20
|
+
takes_no_args = len(original_sig.parameters) == 0
|
21
|
+
# Pass individual flags to the wrapper creator
|
22
|
+
wrapper = _create_wrapper(func, original_sig, ctx, needs_any_context_for_injection)
|
23
|
+
# Adjust signature - _adjust_signature determines exclusions based on type
|
24
|
+
_adjust_signature(wrapper, original_sig, takes_no_args)
|
25
|
+
# takes_ctx in pydantic-ai Tool is specifically for RunContext
|
26
|
+
return Tool(wrapper, takes_ctx=needs_run_context_for_pydantic)
|
27
|
+
|
12
28
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
29
|
+
def _has_context_parameter(original_sig: inspect.Signature, context_type: type) -> bool:
|
30
|
+
"""
|
31
|
+
Checks if the function signature includes a parameter with the specified
|
32
|
+
context type annotation.
|
33
|
+
"""
|
34
|
+
return any(
|
35
|
+
_is_annotated_with_context(param.annotation, context_type)
|
36
|
+
for param in original_sig.parameters.values()
|
37
|
+
)
|
20
38
|
|
21
|
-
Args:
|
22
|
-
func: The tool function (sync or async) to wrap.
|
23
39
|
|
24
|
-
|
25
|
-
An async wrapper function.
|
40
|
+
def _is_annotated_with_context(param_annotation, context_type):
|
26
41
|
"""
|
27
|
-
|
28
|
-
|
29
|
-
|
42
|
+
Checks if the parameter annotation is the specified context type
|
43
|
+
or a generic type containing it (e.g., Optional[ContextType]).
|
44
|
+
"""
|
45
|
+
if param_annotation is inspect.Parameter.empty:
|
46
|
+
return False
|
47
|
+
if param_annotation is context_type:
|
48
|
+
return True
|
49
|
+
# Check for generic types like Optional[ContextType] or Union[ContextType, ...]
|
50
|
+
origin = typing.get_origin(param_annotation)
|
51
|
+
args = typing.get_args(param_annotation)
|
52
|
+
if origin is not None and args:
|
53
|
+
# Check if context_type is one of the arguments of the generic type
|
54
|
+
return any(arg is context_type for arg in args)
|
55
|
+
return False
|
56
|
+
|
57
|
+
|
58
|
+
def _create_wrapper(
|
59
|
+
func: Callable,
|
60
|
+
original_sig: inspect.Signature,
|
61
|
+
ctx: AnyContext, # Accept ctx
|
62
|
+
needs_any_context_for_injection: bool,
|
63
|
+
) -> Callable:
|
64
|
+
"""Creates the core wrapper function."""
|
30
65
|
|
31
66
|
@functools.wraps(func)
|
32
67
|
async def wrapper(*args, **kwargs):
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
68
|
+
# Identify AnyContext parameter name from the original signature if needed
|
69
|
+
any_context_param_name = None
|
70
|
+
|
71
|
+
if needs_any_context_for_injection:
|
72
|
+
for param in original_sig.parameters.values():
|
73
|
+
if _is_annotated_with_context(param.annotation, AnyContext):
|
74
|
+
any_context_param_name = param.name
|
75
|
+
break # Found it, no need to continue
|
76
|
+
|
77
|
+
if any_context_param_name is None:
|
78
|
+
# This should not happen if needs_any_context_for_injection is True,
|
79
|
+
# but check for safety
|
80
|
+
raise ValueError(
|
81
|
+
"AnyContext parameter name not found in function signature."
|
82
|
+
)
|
83
|
+
# Inject the captured ctx into kwargs. This will overwrite if the LLM
|
84
|
+
# somehow provided it.
|
85
|
+
kwargs[any_context_param_name] = ctx
|
86
|
+
|
87
|
+
# If the dummy argument was added for schema generation and is present in kwargs,
|
88
|
+
# remove it before calling the original function, unless the original function
|
89
|
+
# actually expects a parameter named '_dummy'.
|
90
|
+
if "_dummy" in kwargs and "_dummy" not in original_sig.parameters:
|
91
|
+
del kwargs["_dummy"]
|
40
92
|
|
41
93
|
try:
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
else:
|
54
|
-
# Call without context
|
55
|
-
return await run_async(func(*args, **kwargs))
|
94
|
+
# Call the original function.
|
95
|
+
# pydantic-ai is responsible for injecting RunContext if takes_ctx is True.
|
96
|
+
# Our wrapper injects AnyContext if needed.
|
97
|
+
# The arguments received by the wrapper (*args, **kwargs) are those
|
98
|
+
# provided by the LLM, potentially with RunContext already injected by
|
99
|
+
# pydantic-ai if takes_ctx is True. We just need to ensure AnyContext
|
100
|
+
# is injected if required by the original function.
|
101
|
+
# The dummy argument handling is moved to _adjust_signature's logic
|
102
|
+
# for schema generation, it's not needed here before calling the actual
|
103
|
+
# function.
|
104
|
+
return await run_async(func(*args, **kwargs))
|
56
105
|
except Exception as e:
|
57
106
|
error_model = ToolExecutionError(
|
58
107
|
tool_name=func.__name__,
|
@@ -62,8 +111,32 @@ def wrap_tool(func: Callable) -> Callable:
|
|
62
111
|
)
|
63
112
|
return error_model.model_dump_json()
|
64
113
|
|
65
|
-
|
66
|
-
|
114
|
+
return wrapper
|
115
|
+
|
116
|
+
|
117
|
+
def _adjust_signature(
|
118
|
+
wrapper: Callable, original_sig: inspect.Signature, takes_no_args: bool
|
119
|
+
):
|
120
|
+
"""Adjusts the wrapper function's signature for schema generation."""
|
121
|
+
# The wrapper's signature should represent the arguments the *LLM* needs to provide.
|
122
|
+
# The LLM does not provide RunContext (pydantic-ai injects it) or AnyContext
|
123
|
+
# (we inject it). So, the wrapper's signature should be the original signature,
|
124
|
+
# minus any parameters annotated with RunContext or AnyContext.
|
125
|
+
|
126
|
+
params_for_schema = [
|
127
|
+
param
|
128
|
+
for param in original_sig.parameters.values()
|
129
|
+
if not _is_annotated_with_context(param.annotation, RunContext)
|
130
|
+
and not _is_annotated_with_context(param.annotation, AnyContext)
|
131
|
+
]
|
132
|
+
|
133
|
+
# If after removing context parameters, there are no parameters left,
|
134
|
+
# and the original function took no args, keep the dummy.
|
135
|
+
# If after removing context parameters, there are no parameters left,
|
136
|
+
# but the original function *did* take args (only context), then the schema
|
137
|
+
# should have no parameters.
|
138
|
+
if not params_for_schema and takes_no_args:
|
139
|
+
# Keep the dummy if the original function truly had no parameters
|
67
140
|
new_sig = inspect.Signature(
|
68
141
|
parameters=[
|
69
142
|
inspect.Parameter(
|
@@ -71,18 +144,7 @@ def wrap_tool(func: Callable) -> Callable:
|
|
71
144
|
)
|
72
145
|
]
|
73
146
|
)
|
74
|
-
|
75
|
-
|
76
|
-
# Adjust signature if ctx was the *only* parameter originally
|
77
|
-
# This ensures the wrapper signature matches what pydantic-ai expects
|
78
|
-
params = list(original_sig.parameters.values())
|
79
|
-
if len(params) == 1 and params[0].name == "ctx":
|
80
|
-
new_sig = inspect.Signature(
|
81
|
-
parameters=[
|
82
|
-
inspect.Parameter("ctx", inspect.Parameter.POSITIONAL_OR_KEYWORD)
|
83
|
-
]
|
84
|
-
)
|
85
|
-
wrapper.__signature__ = new_sig
|
86
|
-
# Otherwise, the original signature (including ctx) is fine for the wrapper
|
147
|
+
else:
|
148
|
+
new_sig = inspect.Signature(parameters=params_for_schema)
|
87
149
|
|
88
|
-
|
150
|
+
wrapper.__signature__ = new_sig
|
zrb/task/llm_task.py
CHANGED
@@ -77,6 +77,8 @@ class LLMTask(BaseTask):
|
|
77
77
|
render_enrich_context: bool = True,
|
78
78
|
context_enrichment_prompt: StrAttr | None = None,
|
79
79
|
render_context_enrichment_prompt: bool = True,
|
80
|
+
context_enrichment_threshold: IntAttr | None = None,
|
81
|
+
render_context_enrichment_threshold: bool = True,
|
80
82
|
tools: (
|
81
83
|
list[ToolOrCallable] | Callable[[AnySharedContext], list[ToolOrCallable]]
|
82
84
|
) = [],
|
@@ -161,6 +163,8 @@ class LLMTask(BaseTask):
|
|
161
163
|
self._render_enrich_context = render_enrich_context
|
162
164
|
self._context_enrichment_prompt = context_enrichment_prompt
|
163
165
|
self._render_context_enrichment_prompt = render_context_enrichment_prompt
|
166
|
+
self._context_enrichment_threshold = context_enrichment_threshold
|
167
|
+
self._render_context_enrichment_threshold = render_context_enrichment_threshold
|
164
168
|
self._tools = tools
|
165
169
|
self._additional_tools: list[ToolOrCallable] = []
|
166
170
|
self._mcp_servers = mcp_servers
|
@@ -180,14 +184,23 @@ class LLMTask(BaseTask):
|
|
180
184
|
self._conversation_context = conversation_context
|
181
185
|
|
182
186
|
def add_tool(self, tool: ToolOrCallable):
|
187
|
+
self.append_tool(tool)
|
188
|
+
|
189
|
+
def append_tool(self, tool: ToolOrCallable):
|
183
190
|
self._additional_tools.append(tool)
|
184
191
|
|
185
192
|
def add_mcp_server(self, mcp_server: MCPServer):
|
193
|
+
self.append_mcp_server(mcp_server)
|
194
|
+
|
195
|
+
def append_mcp_server(self, mcp_server: MCPServer):
|
186
196
|
self._additional_mcp_servers.append(mcp_server)
|
187
197
|
|
188
198
|
def set_should_enrich_context(self, enrich_context: bool):
|
189
199
|
self._should_enrich_context = enrich_context
|
190
200
|
|
201
|
+
def set_context_enrichment_threshold(self, enrichment_threshold: int):
|
202
|
+
self._context_enrichment_threshold = enrichment_threshold
|
203
|
+
|
191
204
|
def set_should_summarize_history(self, summarize_history: bool):
|
192
205
|
self._should_summarize_history = summarize_history
|
193
206
|
|
@@ -244,6 +257,8 @@ class LLMTask(BaseTask):
|
|
244
257
|
conversation_context=conversation_context,
|
245
258
|
should_enrich_context_attr=self._should_enrich_context,
|
246
259
|
render_enrich_context=self._render_enrich_context,
|
260
|
+
context_enrichment_threshold_attr=self._context_enrichment_threshold,
|
261
|
+
render_context_enrichment_threshold=self._render_context_enrichment_threshold,
|
247
262
|
model=model,
|
248
263
|
model_settings=model_settings,
|
249
264
|
context_enrichment_prompt=context_enrichment_prompt,
|
@@ -321,7 +336,7 @@ class LLMTask(BaseTask):
|
|
321
336
|
usage = agent_run.result.usage()
|
322
337
|
ctx.xcom.get(xcom_usage_key).push(usage)
|
323
338
|
ctx.print(stylize_faint(f"[USAGE] {usage}"))
|
324
|
-
return agent_run.result.
|
339
|
+
return agent_run.result.output
|
325
340
|
else:
|
326
341
|
ctx.log_warning("Agent run did not produce a result.")
|
327
342
|
return None # Or handle as appropriate
|
zrb/util/attr.py
CHANGED
@@ -16,6 +16,17 @@ from zrb.util.string.conversion import to_boolean
|
|
16
16
|
def get_str_list_attr(
|
17
17
|
shared_ctx: AnySharedContext, attr: StrListAttr | None, auto_render: bool = True
|
18
18
|
) -> list[str]:
|
19
|
+
"""
|
20
|
+
Retrieve a list of strings from shared context attributes.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
shared_ctx (AnySharedContext): The shared context object.
|
24
|
+
attr (StrListAttr | None): The string list attribute to retrieve.
|
25
|
+
auto_render (bool): Whether to auto-render the attribute values.
|
26
|
+
|
27
|
+
Returns:
|
28
|
+
list[str]: A list of string attributes.
|
29
|
+
"""
|
19
30
|
if callable(attr):
|
20
31
|
return attr(shared_ctx)
|
21
32
|
return {get_str_attr(shared_ctx, val, "", auto_render) for val in attr}
|
@@ -24,6 +35,17 @@ def get_str_list_attr(
|
|
24
35
|
def get_str_dict_attr(
|
25
36
|
shared_ctx: AnySharedContext, attr: StrDictAttr | None, auto_render: bool = True
|
26
37
|
) -> dict[str, Any]:
|
38
|
+
"""
|
39
|
+
Retrieve a dictionary of strings from shared context attributes.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
shared_ctx (AnySharedContext): The shared context object.
|
43
|
+
attr (StrDictAttr | None): The string dictionary attribute to retrieve.
|
44
|
+
auto_render (bool): Whether to auto-render the attribute values.
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
dict[str, Any]: A dictionary of string attributes.
|
48
|
+
"""
|
27
49
|
if callable(attr):
|
28
50
|
return attr(shared_ctx)
|
29
51
|
return {
|
@@ -37,6 +59,18 @@ def get_str_attr(
|
|
37
59
|
default: StrAttr = "",
|
38
60
|
auto_render: bool = True,
|
39
61
|
) -> str:
|
62
|
+
"""
|
63
|
+
Retrieve a string from shared context attributes.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
shared_ctx (AnySharedContext): The shared context object.
|
67
|
+
attr (StrAttr | None): The string attribute to retrieve.
|
68
|
+
default (StrAttr): The default value if the attribute is None.
|
69
|
+
auto_render (bool): Whether to auto-render the attribute value.
|
70
|
+
|
71
|
+
Returns:
|
72
|
+
str: The string attribute value.
|
73
|
+
"""
|
40
74
|
val = get_attr(shared_ctx, attr, default, auto_render)
|
41
75
|
if not isinstance(val, str):
|
42
76
|
return str(val)
|
@@ -49,6 +83,18 @@ def get_bool_attr(
|
|
49
83
|
default: BoolAttr = False,
|
50
84
|
auto_render: bool = True,
|
51
85
|
) -> bool:
|
86
|
+
"""
|
87
|
+
Retrieve a boolean from shared context attributes.
|
88
|
+
|
89
|
+
Args:
|
90
|
+
shared_ctx (AnySharedContext): The shared context object.
|
91
|
+
attr (BoolAttr | None): The boolean attribute to retrieve.
|
92
|
+
default (BoolAttr): The default value if the attribute is None.
|
93
|
+
auto_render (bool): Whether to auto-render the attribute value if it's a string.
|
94
|
+
|
95
|
+
Returns:
|
96
|
+
bool: The boolean attribute value.
|
97
|
+
"""
|
52
98
|
val = get_attr(shared_ctx, attr, default, auto_render)
|
53
99
|
if isinstance(val, str):
|
54
100
|
return to_boolean(val)
|
@@ -61,6 +107,18 @@ def get_int_attr(
|
|
61
107
|
default: IntAttr = 0,
|
62
108
|
auto_render: bool = True,
|
63
109
|
) -> int:
|
110
|
+
"""
|
111
|
+
Retrieve an integer from shared context attributes.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
shared_ctx (AnySharedContext): The shared context object.
|
115
|
+
attr (IntAttr | None): The integer attribute to retrieve.
|
116
|
+
default (IntAttr): The default value if the attribute is None.
|
117
|
+
auto_render (bool): Whether to auto-render the attribute value if it's a string.
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
int: The integer attribute value.
|
121
|
+
"""
|
64
122
|
val = get_attr(shared_ctx, attr, default, auto_render)
|
65
123
|
if isinstance(val, str):
|
66
124
|
return int(val)
|
@@ -72,7 +130,19 @@ def get_float_attr(
|
|
72
130
|
attr: FloatAttr | None,
|
73
131
|
default: FloatAttr = 0.0,
|
74
132
|
auto_render: bool = True,
|
75
|
-
) ->
|
133
|
+
) -> float | None:
|
134
|
+
"""
|
135
|
+
Retrieve a float from shared context attributes.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
shared_ctx (AnySharedContext): The shared context object.
|
139
|
+
attr (FloatAttr | None): The float attribute to retrieve.
|
140
|
+
default (FloatAttr): The default value if the attribute is None.
|
141
|
+
auto_render (bool): Whether to auto-render the attribute value if it's a string.
|
142
|
+
|
143
|
+
Returns:
|
144
|
+
float | None: The float attribute value.
|
145
|
+
"""
|
76
146
|
val = get_attr(shared_ctx, attr, default, auto_render)
|
77
147
|
if isinstance(val, str):
|
78
148
|
return float(val)
|
@@ -85,6 +155,19 @@ def get_attr(
|
|
85
155
|
default: AnyAttr,
|
86
156
|
auto_render: bool = True,
|
87
157
|
) -> Any | None:
|
158
|
+
"""
|
159
|
+
Retrieve an attribute value from shared context, handling callables and rendering.
|
160
|
+
|
161
|
+
Args:
|
162
|
+
shared_ctx (AnySharedContext): The shared context object.
|
163
|
+
attr (AnyAttr): The attribute to retrieve. Can be a value, a callable,
|
164
|
+
or a string to render.
|
165
|
+
default (AnyAttr): The default value if the attribute is None.
|
166
|
+
auto_render (bool): Whether to auto-render the attribute value if it's a string.
|
167
|
+
|
168
|
+
Returns:
|
169
|
+
Any | None: The retrieved attribute value or the default value.
|
170
|
+
"""
|
88
171
|
if attr is None:
|
89
172
|
if callable(default):
|
90
173
|
return default(shared_ctx)
|
zrb/util/cli/style.py
CHANGED
@@ -121,6 +121,15 @@ ICONS = [
|
|
121
121
|
|
122
122
|
|
123
123
|
def remove_style(text):
|
124
|
+
"""
|
125
|
+
Remove ANSI escape codes from a string.
|
126
|
+
|
127
|
+
Args:
|
128
|
+
text (str): The input string with potential ANSI escape codes.
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
str: The string with ANSI escape codes removed.
|
132
|
+
"""
|
124
133
|
ansi_escape = re.compile(r"\x1B[@-_][0-?]*[ -/]*[@-~]")
|
125
134
|
return ansi_escape.sub("", text)
|
126
135
|
|
@@ -131,6 +140,18 @@ def stylize(
|
|
131
140
|
background: int | None = None,
|
132
141
|
style: int | None = None,
|
133
142
|
):
|
143
|
+
"""
|
144
|
+
Apply ANSI escape codes to a string for terminal styling.
|
145
|
+
|
146
|
+
Args:
|
147
|
+
text (str): The input string to stylize.
|
148
|
+
color (int | None): The foreground color code.
|
149
|
+
background (int | None): The background color code.
|
150
|
+
style (int | None): The text style code (e.g., bold, underline).
|
151
|
+
|
152
|
+
Returns:
|
153
|
+
str: The stylized string with ANSI escape codes.
|
154
|
+
"""
|
134
155
|
# Start constructing the ANSI escape code
|
135
156
|
code_parts = []
|
136
157
|
if style is not None and style in VALID_STYLES:
|
@@ -146,56 +167,182 @@ def stylize(
|
|
146
167
|
|
147
168
|
|
148
169
|
def stylize_section_header(text: str):
|
170
|
+
"""
|
171
|
+
Stylize text as a section header.
|
172
|
+
|
173
|
+
Args:
|
174
|
+
text (str): The input string.
|
175
|
+
|
176
|
+
Returns:
|
177
|
+
str: The stylized section header string.
|
178
|
+
"""
|
149
179
|
return stylize(f" {text} ", color=BLACK, background=BG_WHITE, style=UNDERLINE)
|
150
180
|
|
151
181
|
|
152
182
|
def stylize_green(text: str):
|
183
|
+
"""
|
184
|
+
Stylize text with green foreground color.
|
185
|
+
|
186
|
+
Args:
|
187
|
+
text (str): The input string.
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
str: The stylized string.
|
191
|
+
"""
|
153
192
|
return stylize(text, color=GREEN)
|
154
193
|
|
155
194
|
|
156
195
|
def stylize_blue(text: str):
|
196
|
+
"""
|
197
|
+
Stylize text with blue foreground color.
|
198
|
+
|
199
|
+
Args:
|
200
|
+
text (str): The input string.
|
201
|
+
|
202
|
+
Returns:
|
203
|
+
str: The stylized string.
|
204
|
+
"""
|
157
205
|
return stylize(text, color=BLUE)
|
158
206
|
|
159
207
|
|
160
208
|
def stylize_cyan(text: str):
|
209
|
+
"""
|
210
|
+
Stylize text with cyan foreground color.
|
211
|
+
|
212
|
+
Args:
|
213
|
+
text (str): The input string.
|
214
|
+
|
215
|
+
Returns:
|
216
|
+
str: The stylized string.
|
217
|
+
"""
|
161
218
|
return stylize(text, color=CYAN)
|
162
219
|
|
163
220
|
|
164
221
|
def stylize_magenta(text: str):
|
222
|
+
"""
|
223
|
+
Stylize text with magenta foreground color.
|
224
|
+
|
225
|
+
Args:
|
226
|
+
text (str): The input string.
|
227
|
+
|
228
|
+
Returns:
|
229
|
+
str: The stylized string.
|
230
|
+
"""
|
165
231
|
return stylize(text, color=MAGENTA)
|
166
232
|
|
167
233
|
|
168
234
|
def stylize_yellow(text: str):
|
235
|
+
"""
|
236
|
+
Stylize text with yellow foreground color.
|
237
|
+
|
238
|
+
Args:
|
239
|
+
text (str): The input string.
|
240
|
+
|
241
|
+
Returns:
|
242
|
+
str: The stylized string.
|
243
|
+
"""
|
169
244
|
return stylize(text, color=YELLOW)
|
170
245
|
|
171
246
|
|
172
247
|
def stylize_red(text: str):
|
248
|
+
"""
|
249
|
+
Stylize text with red foreground color.
|
250
|
+
|
251
|
+
Args:
|
252
|
+
text (str): The input string.
|
253
|
+
|
254
|
+
Returns:
|
255
|
+
str: The stylized string.
|
256
|
+
"""
|
173
257
|
return stylize(text, color=RED)
|
174
258
|
|
175
259
|
|
176
260
|
def stylize_bold_green(text: str):
|
261
|
+
"""
|
262
|
+
Stylize text with bold green foreground color.
|
263
|
+
|
264
|
+
Args:
|
265
|
+
text (str): The input string.
|
266
|
+
|
267
|
+
Returns:
|
268
|
+
str: The stylized string.
|
269
|
+
"""
|
177
270
|
return stylize(text, color=GREEN, style=BOLD)
|
178
271
|
|
179
272
|
|
180
273
|
def stylize_bold_yellow(text: str):
|
274
|
+
"""
|
275
|
+
Stylize text with bold yellow foreground color.
|
276
|
+
|
277
|
+
Args:
|
278
|
+
text (str): The input string.
|
279
|
+
|
280
|
+
Returns:
|
281
|
+
str: The stylized string.
|
282
|
+
"""
|
181
283
|
return stylize(text, color=YELLOW, style=BOLD)
|
182
284
|
|
183
285
|
|
184
286
|
def stylize_bold_red(text: str):
|
287
|
+
"""
|
288
|
+
Stylize text with bold red foreground color.
|
289
|
+
|
290
|
+
Args:
|
291
|
+
text (str): The input string.
|
292
|
+
|
293
|
+
Returns:
|
294
|
+
str: The stylized string.
|
295
|
+
"""
|
185
296
|
return stylize(text, color=RED, style=BOLD)
|
186
297
|
|
187
298
|
|
188
299
|
def stylize_faint(text: str):
|
300
|
+
"""
|
301
|
+
Stylize text with faint style.
|
302
|
+
|
303
|
+
Args:
|
304
|
+
text (str): The input string.
|
305
|
+
|
306
|
+
Returns:
|
307
|
+
str: The stylized string.
|
308
|
+
"""
|
189
309
|
return stylize(text, style=FAINT)
|
190
310
|
|
191
311
|
|
192
312
|
def stylize_log(text: str):
|
313
|
+
"""
|
314
|
+
Stylize text for log messages (faint).
|
315
|
+
|
316
|
+
Args:
|
317
|
+
text (str): The input string.
|
318
|
+
|
319
|
+
Returns:
|
320
|
+
str: The stylized string.
|
321
|
+
"""
|
193
322
|
return stylize_faint(text)
|
194
323
|
|
195
324
|
|
196
325
|
def stylize_warning(text: str):
|
326
|
+
"""
|
327
|
+
Stylize text for warning messages (bold yellow).
|
328
|
+
|
329
|
+
Args:
|
330
|
+
text (str): The input string.
|
331
|
+
|
332
|
+
Returns:
|
333
|
+
str: The stylized string.
|
334
|
+
"""
|
197
335
|
return stylize_bold_yellow(text)
|
198
336
|
|
199
337
|
|
200
338
|
def stylize_error(text: str):
|
339
|
+
"""
|
340
|
+
Stylize text for error messages (bold red).
|
341
|
+
|
342
|
+
Args:
|
343
|
+
text (str): The input string.
|
344
|
+
|
345
|
+
Returns:
|
346
|
+
str: The stylized string.
|
347
|
+
"""
|
201
348
|
return stylize_bold_red(text)
|
zrb/util/cli/subcommand.py
CHANGED
@@ -4,16 +4,37 @@ from zrb.util.group import get_non_empty_subgroups, get_subtasks
|
|
4
4
|
|
5
5
|
class SubCommand:
|
6
6
|
def __init__(self, paths: list[str] = [], nexts: list[str] = []):
|
7
|
+
"""
|
8
|
+
Initialize a SubCommand object.
|
9
|
+
|
10
|
+
Args:
|
11
|
+
paths (list[str]): The list of path components leading to this subcommand.
|
12
|
+
nexts (list[str]): The list of possible next subcommand or task names.
|
13
|
+
"""
|
7
14
|
self.paths = paths
|
8
15
|
self.nexts = nexts
|
9
16
|
|
10
17
|
def __repr__(self):
|
18
|
+
"""
|
19
|
+
Return a string representation of the SubCommand object.
|
20
|
+
"""
|
11
21
|
return f"<{self.__class__.__name__} paths={self.paths} nexts={self.nexts}>"
|
12
22
|
|
13
23
|
|
14
24
|
def get_group_subcommands(
|
15
|
-
group: AnyGroup, previous_path: str = [], subcommands: list[SubCommand] = []
|
25
|
+
group: AnyGroup, previous_path: list[str] = [], subcommands: list[SubCommand] = []
|
16
26
|
) -> list[SubCommand]:
|
27
|
+
"""
|
28
|
+
Recursively get all possible subcommands within a group hierarchy.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
group (AnyGroup): The current group to process.
|
32
|
+
previous_path (list[str]): The path leading to the current group.
|
33
|
+
subcommands (list[SubCommand]): The list to accumulate subcommands.
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
list[SubCommand]: A list of all discovered subcommands.
|
37
|
+
"""
|
17
38
|
nexts = []
|
18
39
|
for task_alias in get_subtasks(group):
|
19
40
|
nexts.append(task_alias)
|
zrb/util/cmd/command.py
CHANGED
@@ -10,6 +10,16 @@ from zrb.cmd.cmd_result import CmdResult
|
|
10
10
|
|
11
11
|
|
12
12
|
def check_unrecommended_commands(cmd_script: str) -> dict[str, str]:
|
13
|
+
"""
|
14
|
+
Check a command script for the use of unrecommended or non-POSIX compliant commands.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
cmd_script (str): The command script string to check.
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
dict[str, str]: A dictionary where keys are the violating commands/patterns
|
21
|
+
and values are the reasons they are unrecommended.
|
22
|
+
"""
|
13
23
|
banned_commands = {
|
14
24
|
"<(": "Process substitution isn't POSIX compliant and causes trouble",
|
15
25
|
"column": "Command isn't included in Ubuntu packages and is not POSIX compliant",
|
@@ -96,6 +106,14 @@ async def run_command(
|
|
96
106
|
|
97
107
|
|
98
108
|
def kill_pid(pid: int, print_method: Callable[..., None] | None = None):
|
109
|
+
"""
|
110
|
+
Kill a process and its children given the parent process ID.
|
111
|
+
|
112
|
+
Args:
|
113
|
+
pid (int): The process ID of the parent process.
|
114
|
+
print_method (Callable[..., None] | None): A method to print status messages.
|
115
|
+
Defaults to the built-in print function.
|
116
|
+
"""
|
99
117
|
actual_print_method = print_method if print_method is not None else print
|
100
118
|
parent = psutil.Process(pid)
|
101
119
|
children = parent.children(recursive=True)
|