zrb 1.15.3__py3-none-any.whl → 2.0.0a4__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.
Potentially problematic release.
This version of zrb might be problematic. Click here for more details.
- zrb/__init__.py +118 -133
- zrb/attr/type.py +10 -7
- zrb/builtin/__init__.py +55 -1
- zrb/builtin/git.py +12 -1
- zrb/builtin/group.py +31 -15
- zrb/builtin/llm/chat.py +147 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +7 -7
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +5 -5
- zrb/builtin/project/add/fastapp/fastapp_util.py +1 -1
- zrb/builtin/searxng/config/settings.yml +5671 -0
- zrb/builtin/searxng/start.py +21 -0
- zrb/builtin/shell/autocomplete/bash.py +4 -3
- zrb/builtin/shell/autocomplete/zsh.py +4 -3
- zrb/callback/callback.py +8 -1
- zrb/cmd/cmd_result.py +2 -1
- zrb/config/config.py +555 -169
- zrb/config/helper.py +84 -0
- zrb/config/web_auth_config.py +50 -35
- zrb/context/any_shared_context.py +20 -3
- zrb/context/context.py +39 -5
- zrb/context/print_fn.py +13 -0
- zrb/context/shared_context.py +17 -8
- zrb/group/any_group.py +3 -3
- zrb/group/group.py +3 -3
- zrb/input/any_input.py +5 -1
- zrb/input/base_input.py +18 -6
- zrb/input/option_input.py +41 -1
- zrb/input/text_input.py +7 -24
- zrb/llm/agent/__init__.py +9 -0
- zrb/llm/agent/agent.py +215 -0
- zrb/llm/agent/summarizer.py +20 -0
- zrb/llm/app/__init__.py +10 -0
- zrb/llm/app/completion.py +281 -0
- zrb/llm/app/confirmation/allow_tool.py +66 -0
- zrb/llm/app/confirmation/handler.py +178 -0
- zrb/llm/app/confirmation/replace_confirmation.py +77 -0
- zrb/llm/app/keybinding.py +34 -0
- zrb/llm/app/layout.py +117 -0
- zrb/llm/app/lexer.py +155 -0
- zrb/llm/app/redirection.py +28 -0
- zrb/llm/app/style.py +16 -0
- zrb/llm/app/ui.py +733 -0
- zrb/llm/config/__init__.py +4 -0
- zrb/llm/config/config.py +122 -0
- zrb/llm/config/limiter.py +247 -0
- zrb/llm/history_manager/__init__.py +4 -0
- zrb/llm/history_manager/any_history_manager.py +23 -0
- zrb/llm/history_manager/file_history_manager.py +91 -0
- zrb/llm/history_processor/summarizer.py +108 -0
- zrb/llm/note/__init__.py +3 -0
- zrb/llm/note/manager.py +122 -0
- zrb/llm/prompt/__init__.py +29 -0
- zrb/llm/prompt/claude_compatibility.py +92 -0
- zrb/llm/prompt/compose.py +55 -0
- zrb/llm/prompt/default.py +51 -0
- zrb/llm/prompt/markdown/file_extractor.md +112 -0
- zrb/llm/prompt/markdown/mandate.md +23 -0
- zrb/llm/prompt/markdown/persona.md +3 -0
- zrb/llm/prompt/markdown/repo_extractor.md +112 -0
- zrb/llm/prompt/markdown/repo_summarizer.md +29 -0
- zrb/llm/prompt/markdown/summarizer.md +21 -0
- zrb/llm/prompt/note.py +41 -0
- zrb/llm/prompt/system_context.py +46 -0
- zrb/llm/prompt/zrb.py +41 -0
- zrb/llm/skill/__init__.py +3 -0
- zrb/llm/skill/manager.py +86 -0
- zrb/llm/task/__init__.py +4 -0
- zrb/llm/task/llm_chat_task.py +316 -0
- zrb/llm/task/llm_task.py +245 -0
- zrb/llm/tool/__init__.py +39 -0
- zrb/llm/tool/bash.py +75 -0
- zrb/llm/tool/code.py +266 -0
- zrb/llm/tool/file.py +419 -0
- zrb/llm/tool/note.py +70 -0
- zrb/{builtin/llm → llm}/tool/rag.py +33 -37
- zrb/llm/tool/search/brave.py +53 -0
- zrb/llm/tool/search/searxng.py +47 -0
- zrb/llm/tool/search/serpapi.py +47 -0
- zrb/llm/tool/skill.py +19 -0
- zrb/llm/tool/sub_agent.py +70 -0
- zrb/llm/tool/web.py +97 -0
- zrb/llm/tool/zrb_task.py +66 -0
- zrb/llm/util/attachment.py +101 -0
- zrb/llm/util/prompt.py +104 -0
- zrb/llm/util/stream_response.py +178 -0
- zrb/runner/cli.py +21 -20
- zrb/runner/common_util.py +24 -19
- zrb/runner/web_route/task_input_api_route.py +5 -5
- zrb/runner/web_util/user.py +7 -3
- zrb/session/any_session.py +12 -9
- zrb/session/session.py +38 -17
- zrb/task/any_task.py +24 -3
- zrb/task/base/context.py +42 -22
- zrb/task/base/execution.py +67 -55
- zrb/task/base/lifecycle.py +14 -7
- zrb/task/base/monitoring.py +12 -7
- zrb/task/base_task.py +113 -50
- zrb/task/base_trigger.py +16 -6
- zrb/task/cmd_task.py +6 -0
- zrb/task/http_check.py +11 -5
- zrb/task/make_task.py +5 -3
- zrb/task/rsync_task.py +30 -10
- zrb/task/scaffolder.py +7 -4
- zrb/task/scheduler.py +7 -4
- zrb/task/tcp_check.py +6 -4
- zrb/util/ascii_art/art/bee.txt +17 -0
- zrb/util/ascii_art/art/cat.txt +9 -0
- zrb/util/ascii_art/art/ghost.txt +16 -0
- zrb/util/ascii_art/art/panda.txt +17 -0
- zrb/util/ascii_art/art/rose.txt +14 -0
- zrb/util/ascii_art/art/unicorn.txt +15 -0
- zrb/util/ascii_art/banner.py +92 -0
- zrb/util/attr.py +54 -39
- zrb/util/cli/markdown.py +32 -0
- zrb/util/cli/text.py +30 -0
- zrb/util/cmd/command.py +33 -10
- zrb/util/file.py +61 -33
- zrb/util/git.py +2 -2
- zrb/util/{llm/prompt.py → markdown.py} +2 -3
- zrb/util/match.py +78 -0
- zrb/util/run.py +3 -3
- zrb/util/string/conversion.py +1 -1
- zrb/util/truncate.py +23 -0
- zrb/util/yaml.py +204 -0
- zrb/xcom/xcom.py +10 -0
- {zrb-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/METADATA +41 -27
- {zrb-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/RECORD +129 -131
- {zrb-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/WHEEL +1 -1
- zrb/attr/__init__.py +0 -0
- zrb/builtin/llm/chat_session.py +0 -311
- zrb/builtin/llm/history.py +0 -71
- zrb/builtin/llm/input.py +0 -27
- zrb/builtin/llm/llm_ask.py +0 -187
- zrb/builtin/llm/previous-session.js +0 -21
- zrb/builtin/llm/tool/__init__.py +0 -0
- zrb/builtin/llm/tool/api.py +0 -71
- zrb/builtin/llm/tool/cli.py +0 -38
- zrb/builtin/llm/tool/code.py +0 -254
- zrb/builtin/llm/tool/file.py +0 -626
- zrb/builtin/llm/tool/sub_agent.py +0 -137
- zrb/builtin/llm/tool/web.py +0 -195
- zrb/builtin/project/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/template/app_template/module/my_module/service/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/common/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/permission/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/role/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/module/auth/service/user/__init__.py +0 -0
- zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/schema/__init__.py +0 -0
- zrb/builtin/project/create/__init__.py +0 -0
- zrb/builtin/shell/__init__.py +0 -0
- zrb/builtin/shell/autocomplete/__init__.py +0 -0
- zrb/callback/__init__.py +0 -0
- zrb/cmd/__init__.py +0 -0
- zrb/config/default_prompt/file_extractor_system_prompt.md +0 -12
- zrb/config/default_prompt/interactive_system_prompt.md +0 -35
- zrb/config/default_prompt/persona.md +0 -1
- zrb/config/default_prompt/repo_extractor_system_prompt.md +0 -112
- zrb/config/default_prompt/repo_summarizer_system_prompt.md +0 -10
- zrb/config/default_prompt/summarization_prompt.md +0 -16
- zrb/config/default_prompt/system_prompt.md +0 -32
- zrb/config/llm_config.py +0 -243
- zrb/config/llm_context/config.py +0 -129
- zrb/config/llm_context/config_parser.py +0 -46
- zrb/config/llm_rate_limitter.py +0 -137
- zrb/content_transformer/__init__.py +0 -0
- zrb/context/__init__.py +0 -0
- zrb/dot_dict/__init__.py +0 -0
- zrb/env/__init__.py +0 -0
- zrb/group/__init__.py +0 -0
- zrb/input/__init__.py +0 -0
- zrb/runner/__init__.py +0 -0
- zrb/runner/web_route/__init__.py +0 -0
- zrb/runner/web_route/home_page/__init__.py +0 -0
- zrb/session/__init__.py +0 -0
- zrb/session_state_log/__init__.py +0 -0
- zrb/session_state_logger/__init__.py +0 -0
- zrb/task/__init__.py +0 -0
- zrb/task/base/__init__.py +0 -0
- zrb/task/llm/__init__.py +0 -0
- zrb/task/llm/agent.py +0 -243
- zrb/task/llm/config.py +0 -103
- zrb/task/llm/conversation_history.py +0 -128
- zrb/task/llm/conversation_history_model.py +0 -242
- zrb/task/llm/default_workflow/coding.md +0 -24
- zrb/task/llm/default_workflow/copywriting.md +0 -17
- zrb/task/llm/default_workflow/researching.md +0 -18
- zrb/task/llm/error.py +0 -95
- zrb/task/llm/history_summarization.py +0 -216
- zrb/task/llm/print_node.py +0 -101
- zrb/task/llm/prompt.py +0 -325
- zrb/task/llm/tool_wrapper.py +0 -220
- zrb/task/llm/typing.py +0 -3
- zrb/task/llm_task.py +0 -341
- zrb/task_status/__init__.py +0 -0
- zrb/util/__init__.py +0 -0
- zrb/util/cli/__init__.py +0 -0
- zrb/util/cmd/__init__.py +0 -0
- zrb/util/codemod/__init__.py +0 -0
- zrb/util/string/__init__.py +0 -0
- zrb/xcom/__init__.py +0 -0
- {zrb-1.15.3.dist-info → zrb-2.0.0a4.dist-info}/entry_points.txt +0 -0
zrb/task/base/context.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
2
|
+
from typing import TYPE_CHECKING, Any
|
|
3
3
|
|
|
4
4
|
from zrb.context.any_context import AnyContext
|
|
5
5
|
from zrb.context.any_shared_context import AnySharedContext
|
|
@@ -26,25 +26,33 @@ def build_task_context(task: AnyTask, session: AnySession) -> AnyContext:
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def fill_shared_context_inputs(
|
|
29
|
-
|
|
29
|
+
shared_ctx: AnySharedContext,
|
|
30
|
+
task: AnyTask,
|
|
31
|
+
str_kwargs: dict[str, str] | None = None,
|
|
32
|
+
kwargs: dict[str, Any] | None = None,
|
|
30
33
|
):
|
|
31
34
|
"""
|
|
32
|
-
Populates the shared context with input values provided via
|
|
35
|
+
Populates the shared context with input values provided via str_kwargs.
|
|
33
36
|
"""
|
|
37
|
+
str_kwarg_dict = str_kwargs if str_kwargs is not None else {}
|
|
38
|
+
kwarg_dict = kwargs if kwargs is not None else {}
|
|
34
39
|
for task_input in task.inputs:
|
|
35
|
-
if task_input.name not in
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
if task_input.name not in shared_ctx.input:
|
|
41
|
+
task_input.update_shared_context(
|
|
42
|
+
shared_ctx,
|
|
43
|
+
value=kwarg_dict.get(task_input.name, None),
|
|
44
|
+
str_value=str_kwarg_dict.get(task_input.name, None),
|
|
45
|
+
)
|
|
38
46
|
|
|
39
47
|
|
|
40
|
-
def fill_shared_context_envs(
|
|
48
|
+
def fill_shared_context_envs(shared_ctx: AnySharedContext):
|
|
41
49
|
"""
|
|
42
50
|
Injects OS environment variables into the shared context if they don't already exist.
|
|
43
51
|
"""
|
|
44
52
|
os_env_map = {
|
|
45
|
-
key: val for key, val in os.environ.items() if key not in
|
|
53
|
+
key: val for key, val in os.environ.items() if key not in shared_ctx.env
|
|
46
54
|
}
|
|
47
|
-
|
|
55
|
+
shared_ctx.env.update(os_env_map)
|
|
48
56
|
|
|
49
57
|
|
|
50
58
|
def combine_inputs(
|
|
@@ -71,24 +79,36 @@ def combine_inputs(
|
|
|
71
79
|
input_names.append(task_input.name) # Update names list
|
|
72
80
|
|
|
73
81
|
|
|
82
|
+
def combine_envs(
|
|
83
|
+
existing_envs: list[AnyEnv],
|
|
84
|
+
new_envs: list[AnyEnv | None] | AnyEnv | None,
|
|
85
|
+
):
|
|
86
|
+
"""
|
|
87
|
+
Combines new envs into an existing list.
|
|
88
|
+
Modifies the existing_envs list in place.
|
|
89
|
+
"""
|
|
90
|
+
if isinstance(new_envs, AnyEnv):
|
|
91
|
+
existing_envs.append(new_envs)
|
|
92
|
+
elif new_envs is None:
|
|
93
|
+
pass
|
|
94
|
+
else:
|
|
95
|
+
# new_envs is a list
|
|
96
|
+
for env in new_envs:
|
|
97
|
+
if env is not None:
|
|
98
|
+
existing_envs.append(env)
|
|
99
|
+
|
|
100
|
+
|
|
74
101
|
def get_combined_envs(task: "BaseTask") -> list[AnyEnv]:
|
|
75
102
|
"""
|
|
76
103
|
Aggregates environment variables from the task and its upstreams.
|
|
77
104
|
"""
|
|
78
|
-
envs = []
|
|
105
|
+
envs: list[AnyEnv] = []
|
|
79
106
|
for upstream in task.upstreams:
|
|
80
|
-
envs
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
envs.append(task_envs)
|
|
86
|
-
elif isinstance(task_envs, list):
|
|
87
|
-
# Filter out None while extending
|
|
88
|
-
envs.extend(env for env in task_envs if env is not None)
|
|
89
|
-
|
|
90
|
-
# Filter out None values efficiently from the combined list
|
|
91
|
-
return [env for env in envs if env is not None]
|
|
107
|
+
combine_envs(envs, upstream.envs)
|
|
108
|
+
|
|
109
|
+
combine_envs(envs, task._envs)
|
|
110
|
+
|
|
111
|
+
return envs
|
|
92
112
|
|
|
93
113
|
|
|
94
114
|
def get_combined_inputs(task: "BaseTask") -> list[AnyInput]:
|
zrb/task/base/execution.py
CHANGED
|
@@ -53,7 +53,9 @@ def check_execute_condition(task: "BaseTask", session: AnySession) -> bool:
|
|
|
53
53
|
Evaluates the task's execute_condition attribute.
|
|
54
54
|
"""
|
|
55
55
|
ctx = task.get_ctx(session)
|
|
56
|
-
execute_condition_attr =
|
|
56
|
+
execute_condition_attr = (
|
|
57
|
+
task._execute_condition if task._execute_condition is not None else True
|
|
58
|
+
)
|
|
57
59
|
return get_bool_attr(ctx, execute_condition_attr, True, auto_render=True)
|
|
58
60
|
|
|
59
61
|
|
|
@@ -63,8 +65,12 @@ async def execute_action_until_ready(task: "BaseTask", session: AnySession):
|
|
|
63
65
|
"""
|
|
64
66
|
ctx = task.get_ctx(session)
|
|
65
67
|
readiness_checks = task.readiness_checks
|
|
66
|
-
readiness_check_delay =
|
|
67
|
-
|
|
68
|
+
readiness_check_delay = (
|
|
69
|
+
task._readiness_check_delay if task._readiness_check_delay is not None else 0.5
|
|
70
|
+
)
|
|
71
|
+
monitor_readiness = (
|
|
72
|
+
task._monitor_readiness if task._monitor_readiness is not None else False
|
|
73
|
+
)
|
|
68
74
|
|
|
69
75
|
if not readiness_checks: # Simplified check for empty list
|
|
70
76
|
ctx.log_info("No readiness checks")
|
|
@@ -82,56 +88,61 @@ async def execute_action_until_ready(task: "BaseTask", session: AnySession):
|
|
|
82
88
|
run_async(execute_action_with_retry(task, session))
|
|
83
89
|
)
|
|
84
90
|
|
|
85
|
-
await asyncio.sleep(readiness_check_delay)
|
|
86
|
-
|
|
87
|
-
readiness_check_coros = [
|
|
88
|
-
run_async(check.exec_chain(session)) for check in readiness_checks
|
|
89
|
-
]
|
|
90
|
-
|
|
91
|
-
# Wait primarily for readiness checks to complete
|
|
92
|
-
ctx.log_info("Waiting for readiness checks")
|
|
93
|
-
readiness_passed = False
|
|
94
91
|
try:
|
|
95
|
-
|
|
96
|
-
await asyncio.gather(*readiness_check_coros)
|
|
97
|
-
# Check if all readiness tasks actually completed successfully
|
|
98
|
-
all_readiness_completed = all(
|
|
99
|
-
session.get_task_status(check).is_completed for check in readiness_checks
|
|
100
|
-
)
|
|
101
|
-
if all_readiness_completed:
|
|
102
|
-
ctx.log_info("Readiness checks completed successfully")
|
|
103
|
-
readiness_passed = True
|
|
104
|
-
# Mark task as ready only if checks passed and action didn't fail during checks
|
|
105
|
-
if not session.get_task_status(task).is_failed:
|
|
106
|
-
ctx.log_info("Marked as ready")
|
|
107
|
-
session.get_task_status(task).mark_as_ready()
|
|
108
|
-
else:
|
|
109
|
-
ctx.log_warning(
|
|
110
|
-
"One or more readiness checks did not complete successfully."
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
except Exception as e:
|
|
114
|
-
ctx.log_error(f"Readiness check failed with exception: {e}")
|
|
115
|
-
# If readiness checks fail with an exception, the task is not ready.
|
|
116
|
-
# The action_coro might still be running or have failed.
|
|
117
|
-
# execute_action_with_retry handles marking the main task status.
|
|
92
|
+
await asyncio.sleep(readiness_check_delay)
|
|
118
93
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
# Start monitoring only if readiness passed and monitoring is enabled
|
|
123
|
-
if readiness_passed and monitor_readiness:
|
|
124
|
-
# Import dynamically to avoid circular dependency if monitoring imports execution
|
|
125
|
-
from zrb.task.base.monitoring import monitor_task_readiness
|
|
94
|
+
readiness_check_coros = [
|
|
95
|
+
run_async(check.exec_chain(session)) for check in readiness_checks
|
|
96
|
+
]
|
|
126
97
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
98
|
+
# Wait primarily for readiness checks to complete
|
|
99
|
+
ctx.log_info("Waiting for readiness checks")
|
|
100
|
+
readiness_passed = False
|
|
101
|
+
try:
|
|
102
|
+
# Gather results, but primarily interested in completion/errors
|
|
103
|
+
await asyncio.gather(*readiness_check_coros)
|
|
104
|
+
# Check if all readiness tasks actually completed successfully
|
|
105
|
+
all_readiness_completed = all(
|
|
106
|
+
session.get_task_status(check).is_completed
|
|
107
|
+
for check in readiness_checks
|
|
108
|
+
)
|
|
109
|
+
if all_readiness_completed:
|
|
110
|
+
ctx.log_info("Readiness checks completed successfully")
|
|
111
|
+
readiness_passed = True
|
|
112
|
+
# Mark task as ready only if checks passed and action didn't fail during checks
|
|
113
|
+
if not session.get_task_status(task).is_failed:
|
|
114
|
+
ctx.log_info("Marked as ready")
|
|
115
|
+
session.get_task_status(task).mark_as_ready()
|
|
116
|
+
else:
|
|
117
|
+
ctx.log_warning(
|
|
118
|
+
"One or more readiness checks did not complete successfully."
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
except Exception as e:
|
|
122
|
+
ctx.log_error(f"Readiness check failed with exception: {e}")
|
|
123
|
+
# If readiness checks fail with an exception, the task is not ready.
|
|
124
|
+
# The action_coro might still be running or have failed.
|
|
125
|
+
# execute_action_with_retry handles marking the main task status.
|
|
126
|
+
|
|
127
|
+
# Defer the main action coroutine; it will be awaited later if needed
|
|
128
|
+
session.defer_action(task, action_coro)
|
|
129
|
+
|
|
130
|
+
# Start monitoring only if readiness passed and monitoring is enabled
|
|
131
|
+
if readiness_passed and monitor_readiness:
|
|
132
|
+
# Import dynamically to avoid circular dependency if monitoring imports execution
|
|
133
|
+
from zrb.task.base.monitoring import monitor_task_readiness
|
|
134
|
+
|
|
135
|
+
monitor_coro = asyncio.create_task(
|
|
136
|
+
run_async(monitor_task_readiness(task, session, action_coro))
|
|
137
|
+
)
|
|
138
|
+
session.defer_monitoring(task, monitor_coro)
|
|
131
139
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
# The result here is primarily about readiness check completion.
|
|
141
|
+
# The actual task result is handled by the deferred action_coro.
|
|
142
|
+
return None
|
|
143
|
+
except (asyncio.CancelledError, KeyboardInterrupt, GeneratorExit):
|
|
144
|
+
action_coro.cancel()
|
|
145
|
+
raise
|
|
135
146
|
|
|
136
147
|
|
|
137
148
|
async def execute_action_with_retry(task: "BaseTask", session: AnySession) -> Any:
|
|
@@ -140,8 +151,8 @@ async def execute_action_with_retry(task: "BaseTask", session: AnySession) -> An
|
|
|
140
151
|
handling success (triggering successors) and failure (triggering fallbacks).
|
|
141
152
|
"""
|
|
142
153
|
ctx = task.get_ctx(session)
|
|
143
|
-
retries =
|
|
144
|
-
retry_period =
|
|
154
|
+
retries = task._retries if task._retries is not None else 2
|
|
155
|
+
retry_period = task._retry_period if task._retry_period is not None else 0
|
|
145
156
|
max_attempt = retries + 1
|
|
146
157
|
ctx.set_max_attempt(max_attempt)
|
|
147
158
|
|
|
@@ -163,15 +174,16 @@ async def execute_action_with_retry(task: "BaseTask", session: AnySession) -> An
|
|
|
163
174
|
session.get_task_status(task).mark_as_completed()
|
|
164
175
|
|
|
165
176
|
# Store result in XCom
|
|
166
|
-
task_xcom: Xcom = ctx.xcom.get(task.name)
|
|
167
|
-
task_xcom
|
|
177
|
+
task_xcom: Xcom | None = ctx.xcom.get(task.name)
|
|
178
|
+
if task_xcom is not None:
|
|
179
|
+
task_xcom.push(result)
|
|
168
180
|
|
|
169
181
|
# Skip fallbacks and execute successors on success
|
|
170
182
|
skip_fallbacks(task, session)
|
|
171
183
|
await run_async(execute_successors(task, session))
|
|
172
184
|
return result
|
|
173
185
|
|
|
174
|
-
except (asyncio.CancelledError, KeyboardInterrupt):
|
|
186
|
+
except (asyncio.CancelledError, KeyboardInterrupt, GeneratorExit):
|
|
175
187
|
ctx.log_warning("Task cancelled or interrupted")
|
|
176
188
|
session.get_task_status(task).mark_as_failed() # Mark as failed on cancel
|
|
177
189
|
# Do not trigger fallbacks/successors on cancellation
|
|
@@ -201,7 +213,7 @@ async def run_default_action(task: "BaseTask", ctx: AnyContext) -> Any:
|
|
|
201
213
|
This is the default implementation called by BaseTask._exec_action.
|
|
202
214
|
Subclasses like LLMTask override _exec_action with their own logic.
|
|
203
215
|
"""
|
|
204
|
-
action =
|
|
216
|
+
action = task._action
|
|
205
217
|
if action is None:
|
|
206
218
|
ctx.log_debug("No action defined for this task.")
|
|
207
219
|
return None
|
zrb/task/base/lifecycle.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
|
+
from zrb.context.print_fn import PrintFn
|
|
4
5
|
from zrb.context.shared_context import SharedContext
|
|
5
6
|
from zrb.session.any_session import AnySession
|
|
6
7
|
from zrb.session.session import Session
|
|
@@ -12,7 +13,9 @@ from zrb.util.run import run_async
|
|
|
12
13
|
async def run_and_cleanup(
|
|
13
14
|
task: AnyTask,
|
|
14
15
|
session: AnySession | None = None,
|
|
15
|
-
|
|
16
|
+
print_fn: PrintFn | None = None,
|
|
17
|
+
str_kwargs: dict[str, str] | None = None,
|
|
18
|
+
kwargs: dict[str, Any] | None = None,
|
|
16
19
|
) -> Any:
|
|
17
20
|
"""
|
|
18
21
|
Wrapper for async_run that ensures session termination and cleanup of
|
|
@@ -20,10 +23,12 @@ async def run_and_cleanup(
|
|
|
20
23
|
"""
|
|
21
24
|
# Ensure a session exists
|
|
22
25
|
if session is None:
|
|
23
|
-
session = Session(shared_ctx=SharedContext())
|
|
26
|
+
session = Session(shared_ctx=SharedContext(print_fn=print_fn))
|
|
24
27
|
|
|
25
28
|
# Create the main task execution coroutine
|
|
26
|
-
main_task_coro = asyncio.create_task(
|
|
29
|
+
main_task_coro = asyncio.create_task(
|
|
30
|
+
run_task_async(task, session, print_fn, str_kwargs, kwargs)
|
|
31
|
+
)
|
|
27
32
|
|
|
28
33
|
try:
|
|
29
34
|
result = await main_task_coro
|
|
@@ -67,17 +72,19 @@ async def run_and_cleanup(
|
|
|
67
72
|
async def run_task_async(
|
|
68
73
|
task: AnyTask,
|
|
69
74
|
session: AnySession | None = None,
|
|
70
|
-
|
|
75
|
+
print_fn: PrintFn | None = None,
|
|
76
|
+
str_kwargs: dict[str, str] | None = None,
|
|
77
|
+
kwargs: dict[str, Any] | None = None,
|
|
71
78
|
) -> Any:
|
|
72
79
|
"""
|
|
73
80
|
Asynchronous entry point for running a task (`task.async_run()`).
|
|
74
81
|
Sets up the session and initiates the root task execution chain.
|
|
75
82
|
"""
|
|
76
83
|
if session is None:
|
|
77
|
-
session = Session(shared_ctx=SharedContext())
|
|
84
|
+
session = Session(shared_ctx=SharedContext(print_fn=print_fn))
|
|
78
85
|
|
|
79
86
|
# Populate shared context with inputs and environment variables
|
|
80
|
-
fill_shared_context_inputs(
|
|
87
|
+
fill_shared_context_inputs(session.shared_ctx, task, str_kwargs, kwargs)
|
|
81
88
|
fill_shared_context_envs(session.shared_ctx) # Inject OS env vars
|
|
82
89
|
|
|
83
90
|
# Start the execution chain from the root tasks
|
|
@@ -172,7 +179,7 @@ async def log_session_state(task: AnyTask, session: AnySession):
|
|
|
172
179
|
try:
|
|
173
180
|
while not session.is_terminated:
|
|
174
181
|
session.state_logger.write(session.as_state_log())
|
|
175
|
-
await asyncio.sleep(0
|
|
182
|
+
await asyncio.sleep(0) # Log interval
|
|
176
183
|
# Log one final time after termination signal
|
|
177
184
|
session.state_logger.write(session.as_state_log())
|
|
178
185
|
except asyncio.CancelledError:
|
zrb/task/base/monitoring.py
CHANGED
|
@@ -17,9 +17,13 @@ async def monitor_task_readiness(
|
|
|
17
17
|
"""
|
|
18
18
|
ctx = task.get_ctx(session)
|
|
19
19
|
readiness_checks = task.readiness_checks
|
|
20
|
-
readiness_check_period =
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
readiness_check_period = (
|
|
21
|
+
task._readiness_check_period if task._readiness_check_period else 5.0
|
|
22
|
+
)
|
|
23
|
+
readiness_failure_threshold = (
|
|
24
|
+
task._readiness_failure_threshold if task._readiness_failure_threshold else 1
|
|
25
|
+
)
|
|
26
|
+
readiness_timeout = task._readiness_timeout if task._readiness_timeout else 60
|
|
23
27
|
|
|
24
28
|
if not readiness_checks:
|
|
25
29
|
ctx.log_debug("No readiness checks defined, monitoring is not applicable.")
|
|
@@ -41,8 +45,9 @@ async def monitor_task_readiness(
|
|
|
41
45
|
session.get_task_status(check).reset_history()
|
|
42
46
|
session.get_task_status(check).reset()
|
|
43
47
|
# Clear previous XCom data for the check task if needed
|
|
44
|
-
check_xcom: Xcom = ctx.xcom.get(check.name)
|
|
45
|
-
check_xcom
|
|
48
|
+
check_xcom: Xcom | None = ctx.xcom.get(check.name)
|
|
49
|
+
if check_xcom is not None:
|
|
50
|
+
check_xcom.clear()
|
|
46
51
|
|
|
47
52
|
readiness_check_coros = [
|
|
48
53
|
run_async(check.exec_chain(session)) for check in readiness_checks
|
|
@@ -77,7 +82,7 @@ async def monitor_task_readiness(
|
|
|
77
82
|
)
|
|
78
83
|
# Ensure check tasks are marked as failed on timeout
|
|
79
84
|
for check in readiness_checks:
|
|
80
|
-
if not session.get_task_status(check).
|
|
85
|
+
if not session.get_task_status(check).is_ready:
|
|
81
86
|
session.get_task_status(check).mark_as_failed()
|
|
82
87
|
|
|
83
88
|
except (asyncio.CancelledError, KeyboardInterrupt):
|
|
@@ -92,7 +97,7 @@ async def monitor_task_readiness(
|
|
|
92
97
|
)
|
|
93
98
|
# Mark checks as failed
|
|
94
99
|
for check in readiness_checks:
|
|
95
|
-
if not session.get_task_status(check).
|
|
100
|
+
if not session.get_task_status(check).is_ready:
|
|
96
101
|
session.get_task_status(check).mark_as_failed()
|
|
97
102
|
|
|
98
103
|
# If failure threshold is reached
|
zrb/task/base_task.py
CHANGED
|
@@ -3,8 +3,9 @@ import inspect
|
|
|
3
3
|
from collections.abc import Callable
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
-
from zrb.attr.type import
|
|
6
|
+
from zrb.attr.type import fstring
|
|
7
7
|
from zrb.context.any_context import AnyContext
|
|
8
|
+
from zrb.context.print_fn import PrintFn
|
|
8
9
|
from zrb.env.any_env import AnyEnv
|
|
9
10
|
from zrb.input.any_input import AnyInput
|
|
10
11
|
from zrb.session.any_session import AnySession
|
|
@@ -21,6 +22,7 @@ from zrb.task.base.execution import (
|
|
|
21
22
|
)
|
|
22
23
|
from zrb.task.base.lifecycle import execute_root_tasks, run_and_cleanup, run_task_async
|
|
23
24
|
from zrb.task.base.operators import handle_lshift, handle_rshift
|
|
25
|
+
from zrb.util.string.conversion import to_snake_case
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
class BaseTask(AnyTask):
|
|
@@ -54,7 +56,7 @@ class BaseTask(AnyTask):
|
|
|
54
56
|
input: list[AnyInput | None] | AnyInput | None = None,
|
|
55
57
|
env: list[AnyEnv | None] | AnyEnv | None = None,
|
|
56
58
|
action: fstring | Callable[[AnyContext], Any] | None = None,
|
|
57
|
-
execute_condition:
|
|
59
|
+
execute_condition: bool | str | Callable[[AnyContext], bool] = True,
|
|
58
60
|
retries: int = 2,
|
|
59
61
|
retry_period: float = 0,
|
|
60
62
|
readiness_check: list[AnyTask] | AnyTask | None = None,
|
|
@@ -66,10 +68,20 @@ class BaseTask(AnyTask):
|
|
|
66
68
|
upstream: list[AnyTask] | AnyTask | None = None,
|
|
67
69
|
fallback: list[AnyTask] | AnyTask | None = None,
|
|
68
70
|
successor: list[AnyTask] | AnyTask | None = None,
|
|
71
|
+
print_fn: PrintFn | None = None,
|
|
69
72
|
):
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
# Optimized stack retrieval
|
|
74
|
+
frame = inspect.currentframe()
|
|
75
|
+
if frame is not None:
|
|
76
|
+
caller_frame = frame.f_back
|
|
77
|
+
self.__decl_file = (
|
|
78
|
+
caller_frame.f_code.co_filename if caller_frame else "unknown"
|
|
79
|
+
)
|
|
80
|
+
self.__decl_line = caller_frame.f_lineno if caller_frame else 0
|
|
81
|
+
else:
|
|
82
|
+
self.__decl_file = "unknown"
|
|
83
|
+
self.__decl_line = 0
|
|
84
|
+
|
|
73
85
|
self._name = name
|
|
74
86
|
self._color = color
|
|
75
87
|
self._icon = icon
|
|
@@ -79,10 +91,10 @@ class BaseTask(AnyTask):
|
|
|
79
91
|
self._envs = env
|
|
80
92
|
self._retries = retries
|
|
81
93
|
self._retry_period = retry_period
|
|
82
|
-
self._upstreams = upstream
|
|
83
|
-
self._fallbacks = fallback
|
|
84
|
-
self._successors = successor
|
|
85
|
-
self._readiness_checks = readiness_check
|
|
94
|
+
self._upstreams = self._ensure_task_list(upstream)
|
|
95
|
+
self._fallbacks = self._ensure_task_list(fallback)
|
|
96
|
+
self._successors = self._ensure_task_list(successor)
|
|
97
|
+
self._readiness_checks = self._ensure_task_list(readiness_check)
|
|
86
98
|
self._readiness_check_delay = readiness_check_delay
|
|
87
99
|
self._readiness_check_period = readiness_check_period
|
|
88
100
|
self._readiness_failure_threshold = readiness_failure_threshold
|
|
@@ -90,6 +102,14 @@ class BaseTask(AnyTask):
|
|
|
90
102
|
self._monitor_readiness = monitor_readiness
|
|
91
103
|
self._execute_condition = execute_condition
|
|
92
104
|
self._action = action
|
|
105
|
+
self._print_fn = print_fn
|
|
106
|
+
|
|
107
|
+
def _ensure_task_list(self, tasks: AnyTask | list[AnyTask] | None) -> list[AnyTask]:
|
|
108
|
+
if tasks is None:
|
|
109
|
+
return []
|
|
110
|
+
if isinstance(tasks, list):
|
|
111
|
+
return tasks
|
|
112
|
+
return [tasks]
|
|
93
113
|
|
|
94
114
|
def __repr__(self):
|
|
95
115
|
return f"<{self.__class__.__name__} name={self.name}>"
|
|
@@ -131,18 +151,10 @@ class BaseTask(AnyTask):
|
|
|
131
151
|
@property
|
|
132
152
|
def fallbacks(self) -> list[AnyTask]:
|
|
133
153
|
"""Returns the list of fallback tasks."""
|
|
134
|
-
|
|
135
|
-
return []
|
|
136
|
-
elif isinstance(self._fallbacks, list):
|
|
137
|
-
return self._fallbacks
|
|
138
|
-
return [self._fallbacks] # Assume single task
|
|
154
|
+
return self._fallbacks
|
|
139
155
|
|
|
140
156
|
def append_fallback(self, fallbacks: AnyTask | list[AnyTask]):
|
|
141
157
|
"""Appends fallback tasks, ensuring no duplicates."""
|
|
142
|
-
if self._fallbacks is None:
|
|
143
|
-
self._fallbacks = []
|
|
144
|
-
elif not isinstance(self._fallbacks, list):
|
|
145
|
-
self._fallbacks = [self._fallbacks]
|
|
146
158
|
to_add = fallbacks if isinstance(fallbacks, list) else [fallbacks]
|
|
147
159
|
for fb in to_add:
|
|
148
160
|
if fb not in self._fallbacks:
|
|
@@ -151,18 +163,10 @@ class BaseTask(AnyTask):
|
|
|
151
163
|
@property
|
|
152
164
|
def successors(self) -> list[AnyTask]:
|
|
153
165
|
"""Returns the list of successor tasks."""
|
|
154
|
-
|
|
155
|
-
return []
|
|
156
|
-
elif isinstance(self._successors, list):
|
|
157
|
-
return self._successors
|
|
158
|
-
return [self._successors] # Assume single task
|
|
166
|
+
return self._successors
|
|
159
167
|
|
|
160
168
|
def append_successor(self, successors: AnyTask | list[AnyTask]):
|
|
161
169
|
"""Appends successor tasks, ensuring no duplicates."""
|
|
162
|
-
if self._successors is None:
|
|
163
|
-
self._successors = []
|
|
164
|
-
elif not isinstance(self._successors, list):
|
|
165
|
-
self._successors = [self._successors]
|
|
166
170
|
to_add = successors if isinstance(successors, list) else [successors]
|
|
167
171
|
for succ in to_add:
|
|
168
172
|
if succ not in self._successors:
|
|
@@ -171,18 +175,10 @@ class BaseTask(AnyTask):
|
|
|
171
175
|
@property
|
|
172
176
|
def readiness_checks(self) -> list[AnyTask]:
|
|
173
177
|
"""Returns the list of readiness check tasks."""
|
|
174
|
-
|
|
175
|
-
return []
|
|
176
|
-
elif isinstance(self._readiness_checks, list):
|
|
177
|
-
return self._readiness_checks
|
|
178
|
-
return [self._readiness_checks] # Assume single task
|
|
178
|
+
return self._readiness_checks
|
|
179
179
|
|
|
180
180
|
def append_readiness_check(self, readiness_checks: AnyTask | list[AnyTask]):
|
|
181
181
|
"""Appends readiness check tasks, ensuring no duplicates."""
|
|
182
|
-
if self._readiness_checks is None:
|
|
183
|
-
self._readiness_checks = []
|
|
184
|
-
elif not isinstance(self._readiness_checks, list):
|
|
185
|
-
self._readiness_checks = [self._readiness_checks]
|
|
186
182
|
to_add = (
|
|
187
183
|
readiness_checks
|
|
188
184
|
if isinstance(readiness_checks, list)
|
|
@@ -195,18 +191,10 @@ class BaseTask(AnyTask):
|
|
|
195
191
|
@property
|
|
196
192
|
def upstreams(self) -> list[AnyTask]:
|
|
197
193
|
"""Returns the list of upstream tasks."""
|
|
198
|
-
|
|
199
|
-
return []
|
|
200
|
-
elif isinstance(self._upstreams, list):
|
|
201
|
-
return self._upstreams
|
|
202
|
-
return [self._upstreams] # Assume single task
|
|
194
|
+
return self._upstreams
|
|
203
195
|
|
|
204
196
|
def append_upstream(self, upstreams: AnyTask | list[AnyTask]):
|
|
205
197
|
"""Appends upstream tasks, ensuring no duplicates."""
|
|
206
|
-
if self._upstreams is None:
|
|
207
|
-
self._upstreams = []
|
|
208
|
-
elif not isinstance(self._upstreams, list):
|
|
209
|
-
self._upstreams = [self._upstreams]
|
|
210
198
|
to_add = upstreams if isinstance(upstreams, list) else [upstreams]
|
|
211
199
|
for up in to_add:
|
|
212
200
|
if up not in self._upstreams:
|
|
@@ -216,7 +204,10 @@ class BaseTask(AnyTask):
|
|
|
216
204
|
return build_task_context(self, session)
|
|
217
205
|
|
|
218
206
|
def run(
|
|
219
|
-
self,
|
|
207
|
+
self,
|
|
208
|
+
session: AnySession | None = None,
|
|
209
|
+
str_kwargs: dict[str, str] | None = None,
|
|
210
|
+
kwargs: dict[str, Any] | None = None,
|
|
220
211
|
) -> Any:
|
|
221
212
|
"""
|
|
222
213
|
Synchronously runs the task and its dependencies, handling async setup and cleanup.
|
|
@@ -235,12 +226,29 @@ class BaseTask(AnyTask):
|
|
|
235
226
|
Any: The final result of the main task execution.
|
|
236
227
|
"""
|
|
237
228
|
# Use asyncio.run() to execute the async cleanup wrapper
|
|
238
|
-
return asyncio.run(
|
|
229
|
+
return asyncio.run(
|
|
230
|
+
run_and_cleanup(
|
|
231
|
+
self,
|
|
232
|
+
session=session,
|
|
233
|
+
print_fn=self._print_fn,
|
|
234
|
+
str_kwargs=str_kwargs,
|
|
235
|
+
kwargs=kwargs,
|
|
236
|
+
)
|
|
237
|
+
)
|
|
239
238
|
|
|
240
239
|
async def async_run(
|
|
241
|
-
self,
|
|
240
|
+
self,
|
|
241
|
+
session: AnySession | None = None,
|
|
242
|
+
str_kwargs: dict[str, str] | None = None,
|
|
243
|
+
kwargs: dict[str, Any] | None = None,
|
|
242
244
|
) -> Any:
|
|
243
|
-
return await run_task_async(
|
|
245
|
+
return await run_task_async(
|
|
246
|
+
self,
|
|
247
|
+
session=session,
|
|
248
|
+
print_fn=self._print_fn,
|
|
249
|
+
str_kwargs=str_kwargs,
|
|
250
|
+
kwargs=kwargs,
|
|
251
|
+
)
|
|
244
252
|
|
|
245
253
|
async def exec_root_tasks(self, session: AnySession):
|
|
246
254
|
return await execute_root_tasks(self, session)
|
|
@@ -266,6 +274,8 @@ class BaseTask(AnyTask):
|
|
|
266
274
|
try:
|
|
267
275
|
# Delegate to the helper function for the default behavior
|
|
268
276
|
return await run_default_action(self, ctx)
|
|
277
|
+
except (KeyboardInterrupt, GeneratorExit):
|
|
278
|
+
raise
|
|
269
279
|
except BaseException as e:
|
|
270
280
|
additional_error_note = (
|
|
271
281
|
f"Task: {self.name} ({self.__decl_file}:{self.__decl_line})"
|
|
@@ -276,7 +286,60 @@ class BaseTask(AnyTask):
|
|
|
276
286
|
# Add definition location to the error
|
|
277
287
|
if hasattr(e, "add_note"):
|
|
278
288
|
e.add_note(additional_error_note)
|
|
279
|
-
|
|
289
|
+
elif hasattr(e, "__notes__"):
|
|
280
290
|
# fallback: use the __notes__ attribute directly
|
|
281
291
|
e.__notes__ = getattr(e, "__notes__", []) + [additional_error_note]
|
|
282
292
|
raise e
|
|
293
|
+
|
|
294
|
+
def to_function(self) -> Callable[..., Any]:
|
|
295
|
+
from zrb.context.shared_context import SharedContext
|
|
296
|
+
from zrb.session.session import Session
|
|
297
|
+
|
|
298
|
+
def task_runner_fn(**kwargs) -> Any:
|
|
299
|
+
task_kwargs = self._get_func_kwargs(kwargs)
|
|
300
|
+
shared_ctx = SharedContext(print_fn=self._print_fn)
|
|
301
|
+
session = Session(shared_ctx=shared_ctx)
|
|
302
|
+
return self.run(session=session, kwargs=task_kwargs)
|
|
303
|
+
|
|
304
|
+
task_runner_fn.__doc__ = self._create_fn_docstring()
|
|
305
|
+
task_runner_fn.__signature__ = self._create_fn_signature()
|
|
306
|
+
task_runner_fn.__name__ = self.name
|
|
307
|
+
return task_runner_fn
|
|
308
|
+
|
|
309
|
+
def _get_func_kwargs(self, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
310
|
+
fn_kwargs = {}
|
|
311
|
+
for inp in self.inputs:
|
|
312
|
+
snake_input_name = to_snake_case(inp.name)
|
|
313
|
+
if snake_input_name in kwargs:
|
|
314
|
+
fn_kwargs[inp.name] = kwargs[snake_input_name]
|
|
315
|
+
return fn_kwargs
|
|
316
|
+
|
|
317
|
+
def _create_fn_docstring(self) -> str:
|
|
318
|
+
from zrb.context.shared_context import SharedContext
|
|
319
|
+
|
|
320
|
+
stub_shared_ctx = SharedContext(print_fn=self._print_fn)
|
|
321
|
+
str_input_default_values = {}
|
|
322
|
+
for inp in self.inputs:
|
|
323
|
+
str_input_default_values[inp.name] = inp.get_default_str(stub_shared_ctx)
|
|
324
|
+
# Create docstring
|
|
325
|
+
doc = f"{self.description}\n\n"
|
|
326
|
+
if len(self.inputs) > 0:
|
|
327
|
+
doc += "Args:\n"
|
|
328
|
+
for inp in self.inputs:
|
|
329
|
+
str_input_default = str_input_default_values.get(inp.name, "")
|
|
330
|
+
doc += (
|
|
331
|
+
f" {inp.name}: {inp.description} (default: {str_input_default})"
|
|
332
|
+
)
|
|
333
|
+
doc += "\n"
|
|
334
|
+
return doc
|
|
335
|
+
|
|
336
|
+
def _create_fn_signature(self) -> inspect.Signature:
|
|
337
|
+
params = []
|
|
338
|
+
for inp in self.inputs:
|
|
339
|
+
params.append(
|
|
340
|
+
inspect.Parameter(
|
|
341
|
+
name=to_snake_case(inp.name),
|
|
342
|
+
kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
343
|
+
)
|
|
344
|
+
)
|
|
345
|
+
return inspect.Signature(params)
|