zrb 1.13.1__py3-none-any.whl → 1.21.17__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.
Files changed (105) hide show
  1. zrb/__init__.py +2 -6
  2. zrb/attr/type.py +8 -8
  3. zrb/builtin/__init__.py +2 -0
  4. zrb/builtin/group.py +31 -15
  5. zrb/builtin/http.py +7 -8
  6. zrb/builtin/llm/attachment.py +40 -0
  7. zrb/builtin/llm/chat_session.py +130 -144
  8. zrb/builtin/llm/chat_session_cmd.py +226 -0
  9. zrb/builtin/llm/chat_trigger.py +73 -0
  10. zrb/builtin/llm/history.py +4 -4
  11. zrb/builtin/llm/llm_ask.py +218 -110
  12. zrb/builtin/llm/tool/api.py +74 -62
  13. zrb/builtin/llm/tool/cli.py +35 -16
  14. zrb/builtin/llm/tool/code.py +49 -47
  15. zrb/builtin/llm/tool/file.py +262 -251
  16. zrb/builtin/llm/tool/note.py +84 -0
  17. zrb/builtin/llm/tool/rag.py +25 -18
  18. zrb/builtin/llm/tool/sub_agent.py +29 -22
  19. zrb/builtin/llm/tool/web.py +135 -143
  20. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/entity/add_entity_util.py +7 -7
  21. zrb/builtin/project/add/fastapp/fastapp_template/my_app_name/_zrb/module/add_module_util.py +5 -5
  22. zrb/builtin/project/add/fastapp/fastapp_util.py +1 -1
  23. zrb/builtin/searxng/config/settings.yml +5671 -0
  24. zrb/builtin/searxng/start.py +21 -0
  25. zrb/builtin/setup/latex/ubuntu.py +1 -0
  26. zrb/builtin/setup/ubuntu.py +1 -1
  27. zrb/builtin/shell/autocomplete/bash.py +4 -3
  28. zrb/builtin/shell/autocomplete/zsh.py +4 -3
  29. zrb/config/config.py +255 -78
  30. zrb/config/default_prompt/file_extractor_system_prompt.md +109 -9
  31. zrb/config/default_prompt/interactive_system_prompt.md +24 -30
  32. zrb/config/default_prompt/persona.md +1 -1
  33. zrb/config/default_prompt/repo_extractor_system_prompt.md +31 -31
  34. zrb/config/default_prompt/repo_summarizer_system_prompt.md +27 -8
  35. zrb/config/default_prompt/summarization_prompt.md +8 -13
  36. zrb/config/default_prompt/system_prompt.md +36 -30
  37. zrb/config/llm_config.py +129 -24
  38. zrb/config/llm_context/config.py +127 -90
  39. zrb/config/llm_context/config_parser.py +1 -7
  40. zrb/config/llm_context/workflow.py +81 -0
  41. zrb/config/llm_rate_limitter.py +89 -45
  42. zrb/context/any_shared_context.py +7 -1
  43. zrb/context/context.py +8 -2
  44. zrb/context/shared_context.py +6 -8
  45. zrb/group/any_group.py +12 -5
  46. zrb/group/group.py +67 -3
  47. zrb/input/any_input.py +5 -1
  48. zrb/input/base_input.py +18 -6
  49. zrb/input/text_input.py +7 -24
  50. zrb/runner/cli.py +21 -20
  51. zrb/runner/common_util.py +24 -19
  52. zrb/runner/web_route/task_input_api_route.py +5 -5
  53. zrb/runner/web_route/task_session_api_route.py +1 -4
  54. zrb/runner/web_util/user.py +7 -3
  55. zrb/session/any_session.py +12 -6
  56. zrb/session/session.py +39 -18
  57. zrb/task/any_task.py +24 -3
  58. zrb/task/base/context.py +17 -9
  59. zrb/task/base/execution.py +15 -8
  60. zrb/task/base/lifecycle.py +8 -4
  61. zrb/task/base/monitoring.py +12 -7
  62. zrb/task/base_task.py +69 -5
  63. zrb/task/base_trigger.py +12 -5
  64. zrb/task/llm/agent.py +138 -52
  65. zrb/task/llm/config.py +45 -13
  66. zrb/task/llm/conversation_history.py +76 -6
  67. zrb/task/llm/conversation_history_model.py +0 -168
  68. zrb/task/llm/default_workflow/coding/workflow.md +41 -0
  69. zrb/task/llm/default_workflow/copywriting/workflow.md +68 -0
  70. zrb/task/llm/default_workflow/git/workflow.md +118 -0
  71. zrb/task/llm/default_workflow/golang/workflow.md +128 -0
  72. zrb/task/llm/default_workflow/html-css/workflow.md +135 -0
  73. zrb/task/llm/default_workflow/java/workflow.md +146 -0
  74. zrb/task/llm/default_workflow/javascript/workflow.md +158 -0
  75. zrb/task/llm/default_workflow/python/workflow.md +160 -0
  76. zrb/task/llm/default_workflow/researching/workflow.md +153 -0
  77. zrb/task/llm/default_workflow/rust/workflow.md +162 -0
  78. zrb/task/llm/default_workflow/shell/workflow.md +299 -0
  79. zrb/task/llm/file_replacement.py +206 -0
  80. zrb/task/llm/file_tool_model.py +57 -0
  81. zrb/task/llm/history_summarization.py +22 -35
  82. zrb/task/llm/history_summarization_tool.py +24 -0
  83. zrb/task/llm/print_node.py +182 -63
  84. zrb/task/llm/prompt.py +213 -153
  85. zrb/task/llm/tool_wrapper.py +210 -53
  86. zrb/task/llm/workflow.py +76 -0
  87. zrb/task/llm_task.py +98 -47
  88. zrb/task/make_task.py +2 -3
  89. zrb/task/rsync_task.py +25 -10
  90. zrb/task/scheduler.py +4 -4
  91. zrb/util/attr.py +50 -40
  92. zrb/util/cli/markdown.py +12 -0
  93. zrb/util/cli/text.py +30 -0
  94. zrb/util/file.py +27 -11
  95. zrb/util/{llm/prompt.py → markdown.py} +2 -3
  96. zrb/util/string/conversion.py +1 -1
  97. zrb/util/truncate.py +23 -0
  98. zrb/util/yaml.py +204 -0
  99. {zrb-1.13.1.dist-info → zrb-1.21.17.dist-info}/METADATA +40 -20
  100. {zrb-1.13.1.dist-info → zrb-1.21.17.dist-info}/RECORD +102 -79
  101. {zrb-1.13.1.dist-info → zrb-1.21.17.dist-info}/WHEEL +1 -1
  102. zrb/task/llm/default_workflow/coding.md +0 -24
  103. zrb/task/llm/default_workflow/copywriting.md +0 -17
  104. zrb/task/llm/default_workflow/researching.md +0 -18
  105. {zrb-1.13.1.dist-info → zrb-1.21.17.dist-info}/entry_points.txt +0 -0
zrb/task/llm_task.py CHANGED
@@ -5,7 +5,6 @@ from typing import TYPE_CHECKING, Any
5
5
  from zrb.attr.type import BoolAttr, IntAttr, StrAttr, StrListAttr, fstring
6
6
  from zrb.config.llm_rate_limitter import LLMRateLimiter
7
7
  from zrb.context.any_context import AnyContext
8
- from zrb.context.any_shared_context import AnySharedContext
9
8
  from zrb.env.any_env import AnyEnv
10
9
  from zrb.input.any_input import AnyInput
11
10
  from zrb.task.any_task import AnyTask
@@ -14,30 +13,31 @@ from zrb.task.llm.agent import get_agent, run_agent_iteration
14
13
  from zrb.task.llm.config import (
15
14
  get_model,
16
15
  get_model_settings,
16
+ get_yolo_mode,
17
17
  )
18
18
  from zrb.task.llm.conversation_history import (
19
+ inject_conversation_history_notes,
19
20
  read_conversation_history,
20
21
  write_conversation_history,
21
22
  )
22
23
  from zrb.task.llm.conversation_history_model import ConversationHistory
23
24
  from zrb.task.llm.history_summarization import maybe_summarize_history
24
25
  from zrb.task.llm.prompt import (
26
+ get_attachments,
25
27
  get_summarization_system_prompt,
26
28
  get_system_and_user_prompt,
27
29
  get_user_message,
28
30
  )
31
+ from zrb.task.llm.workflow import load_workflow
29
32
  from zrb.util.cli.style import stylize_faint
30
33
  from zrb.xcom.xcom import Xcom
31
34
 
32
35
  if TYPE_CHECKING:
33
- from pydantic_ai import Agent, Tool
36
+ from pydantic_ai import AbstractToolset, Agent, Tool, UserContent
34
37
  from pydantic_ai.models import Model
35
38
  from pydantic_ai.settings import ModelSettings
36
- from pydantic_ai.toolsets import AbstractToolset
37
39
 
38
40
  ToolOrCallable = Tool | Callable
39
- else:
40
- ToolOrCallable = Any
41
41
 
42
42
 
43
43
  class LLMTask(BaseTask):
@@ -50,47 +50,55 @@ class LLMTask(BaseTask):
50
50
  cli_only: bool = False,
51
51
  input: list[AnyInput | None] | AnyInput | None = None,
52
52
  env: list[AnyEnv | None] | AnyEnv | None = None,
53
- model: (
54
- "Callable[[AnySharedContext], Model | str | fstring] | Model | None"
55
- ) = None,
53
+ model: "Callable[[AnyContext], Model | str | fstring | None] | Model | None" = None,
56
54
  render_model: bool = True,
57
- model_base_url: StrAttr | None = None,
55
+ model_base_url: "Callable[[AnyContext], str | None] | str | None" = None,
58
56
  render_model_base_url: bool = True,
59
- model_api_key: StrAttr | None = None,
57
+ model_api_key: "Callable[[AnyContext], str | None] | str | None" = None,
60
58
  render_model_api_key: bool = True,
61
59
  model_settings: (
62
- "ModelSettings | Callable[[AnySharedContext], ModelSettings] | None"
60
+ "ModelSettings | Callable[[AnyContext], ModelSettings] | None"
61
+ ) = None,
62
+ small_model: (
63
+ "Callable[[AnyContext], Model | str | fstring] | Model | None"
63
64
  ) = None,
64
- agent: "Agent | Callable[[AnySharedContext], Agent] | None" = None,
65
- persona: StrAttr | None = None,
65
+ render_small_model: bool = True,
66
+ small_model_base_url: StrAttr | None = None,
67
+ render_small_model_base_url: bool = True,
68
+ small_model_api_key: StrAttr | None = None,
69
+ render_small_model_api_key: bool = True,
70
+ small_model_settings: (
71
+ "ModelSettings | Callable[[AnyContext], ModelSettings] | None"
72
+ ) = None,
73
+ persona: "Callable[[AnyContext], str | None] | str | None" = None,
66
74
  render_persona: bool = False,
67
- system_prompt: StrAttr | None = None,
75
+ system_prompt: "Callable[[AnyContext], str | None] | str | None" = None,
68
76
  render_system_prompt: bool = False,
69
- special_instruction_prompt: StrAttr | None = None,
77
+ special_instruction_prompt: "Callable[[AnyContext], str | None] | str | None" = None,
70
78
  render_special_instruction_prompt: bool = False,
71
- modes: StrListAttr | None = None,
72
- render_modes: bool = True,
79
+ workflows: StrListAttr | None = None,
80
+ render_workflows: bool = True,
73
81
  message: StrAttr | None = None,
82
+ attachment: "UserContent | list[UserContent] | Callable[[AnyContext], UserContent | list[UserContent]] | None" = None, # noqa
74
83
  render_message: bool = True,
75
84
  tools: (
76
- list["ToolOrCallable"]
77
- | Callable[[AnySharedContext], list["ToolOrCallable"]]
85
+ list["ToolOrCallable"] | Callable[[AnyContext], list["ToolOrCallable"]]
78
86
  ) = [],
79
87
  toolsets: (
80
- list["AbstractToolset[Agent]"] | Callable[[AnySharedContext], list["Tool"]]
88
+ list["AbstractToolset[None] | str"]
89
+ | Callable[[AnyContext], list["AbstractToolset[None] | str"]]
81
90
  ) = [],
82
91
  conversation_history: (
83
92
  ConversationHistory
84
- | Callable[[AnySharedContext], ConversationHistory | dict | list]
93
+ | Callable[[AnyContext], ConversationHistory | dict | list]
85
94
  | dict
86
95
  | list
87
96
  ) = ConversationHistory(),
88
97
  conversation_history_reader: (
89
- Callable[[AnySharedContext], ConversationHistory | dict | list | None]
90
- | None
98
+ Callable[[AnyContext], ConversationHistory | dict | list | None] | None
91
99
  ) = None,
92
100
  conversation_history_writer: (
93
- Callable[[AnySharedContext, ConversationHistory], None] | None
101
+ Callable[[AnyContext, ConversationHistory], None] | None
94
102
  ) = None,
95
103
  conversation_history_file: StrAttr | None = None,
96
104
  render_history_file: bool = True,
@@ -101,9 +109,16 @@ class LLMTask(BaseTask):
101
109
  history_summarization_token_threshold: IntAttr | None = None,
102
110
  render_history_summarization_token_threshold: bool = True,
103
111
  rate_limitter: LLMRateLimiter | None = None,
104
- execute_condition: bool | str | Callable[[AnySharedContext], bool] = True,
112
+ execute_condition: bool | str | Callable[[AnyContext], bool] = True,
105
113
  retries: int = 2,
106
114
  retry_period: float = 0,
115
+ yolo_mode: (
116
+ Callable[[AnyContext], list[str] | bool | None]
117
+ | StrListAttr
118
+ | BoolAttr
119
+ | None
120
+ ) = None,
121
+ render_yolo_mode: bool = True,
107
122
  readiness_check: list[AnyTask] | AnyTask | None = None,
108
123
  readiness_check_delay: float = 0.5,
109
124
  readiness_check_period: float = 5,
@@ -115,7 +130,7 @@ class LLMTask(BaseTask):
115
130
  fallback: list[AnyTask] | AnyTask | None = None,
116
131
  successor: list[AnyTask] | AnyTask | None = None,
117
132
  conversation_context: (
118
- dict[str, Any] | Callable[[AnySharedContext], dict[str, Any]] | None
133
+ dict[str, Any] | Callable[[AnyContext], dict[str, Any]] | None
119
134
  ) = None,
120
135
  ):
121
136
  super().__init__(
@@ -146,24 +161,30 @@ class LLMTask(BaseTask):
146
161
  self._model_api_key = model_api_key
147
162
  self._render_model_api_key = render_model_api_key
148
163
  self._model_settings = model_settings
149
- self._agent = agent
164
+ self._small_model = small_model
165
+ self._render_small_model = render_small_model
166
+ self._small_model_base_url = small_model_base_url
167
+ self._render_small_model_base_url = render_small_model_base_url
168
+ self._small_model_api_key = small_model_api_key
169
+ self._render_small_model_api_key = render_small_model_api_key
170
+ self._small_model_settings = small_model_settings
150
171
  self._persona = persona
151
172
  self._render_persona = render_persona
152
173
  self._system_prompt = system_prompt
153
174
  self._render_system_prompt = render_system_prompt
154
175
  self._special_instruction_prompt = special_instruction_prompt
155
176
  self._render_special_instruction_prompt = render_special_instruction_prompt
156
- self._modes = modes
157
- self._render_modes = render_modes
177
+ self._workflows = workflows
178
+ self._render_workflows = render_workflows
158
179
  self._message = message
159
180
  self._render_message = render_message
160
181
  self._summarization_prompt = summarization_prompt
161
182
  self._render_summarization_prompt = render_summarization_prompt
162
183
  self._tools = tools
163
184
  self._rate_limitter = rate_limitter
164
- self._additional_tools: list["ToolOrCallable"] = []
185
+ self._additional_tools: list["ToolOrCallable"] = [load_workflow]
165
186
  self._toolsets = toolsets
166
- self._additional_toolsets: list["AbstractToolset[Agent]"] = []
187
+ self._additional_toolsets: list["AbstractToolset[None] | str"] = []
167
188
  self._conversation_history = conversation_history
168
189
  self._conversation_history_reader = conversation_history_reader
169
190
  self._conversation_history_writer = conversation_history_writer
@@ -179,18 +200,21 @@ class LLMTask(BaseTask):
179
200
  )
180
201
  self._max_call_iteration = max_call_iteration
181
202
  self._conversation_context = conversation_context
203
+ self._yolo_mode = yolo_mode
204
+ self._render_yolo_mode = render_yolo_mode
205
+ self._attachment = attachment
182
206
 
183
- def add_tool(self, *tool: ToolOrCallable):
207
+ def add_tool(self, *tool: "ToolOrCallable"):
184
208
  self.append_tool(*tool)
185
209
 
186
- def append_tool(self, *tool: ToolOrCallable):
210
+ def append_tool(self, *tool: "ToolOrCallable"):
187
211
  for single_tool in tool:
188
212
  self._additional_tools.append(single_tool)
189
213
 
190
- def add_toolset(self, *toolset: "AbstractToolset[Agent]"):
214
+ def add_toolset(self, *toolset: "AbstractToolset[None] | str"):
191
215
  self.append_toolset(*toolset)
192
216
 
193
- def append_toolset(self, *toolset: "AbstractToolset[Agent]"):
217
+ def append_toolset(self, *toolset: "AbstractToolset[None] | str"):
194
218
  for single_toolset in toolset:
195
219
  self._additional_toolsets.append(single_toolset)
196
220
 
@@ -202,6 +226,12 @@ class LLMTask(BaseTask):
202
226
  ):
203
227
  self._history_summarization_token_threshold = summarization_token_threshold
204
228
 
229
+ def set_workflows(self, workflows: StrListAttr):
230
+ self._workflows = workflows
231
+
232
+ def set_yolo_mode(self, yolo_mode: StrListAttr | BoolAttr):
233
+ self._yolo_mode = yolo_mode
234
+
205
235
  async def _exec_action(self, ctx: AnyContext) -> Any:
206
236
  # Get dependent configurations first
207
237
  model_settings = get_model_settings(ctx, self._model_settings)
@@ -214,12 +244,18 @@ class LLMTask(BaseTask):
214
244
  model_api_key_attr=self._model_api_key,
215
245
  render_model_api_key=self._render_model_api_key,
216
246
  )
247
+ yolo_mode = get_yolo_mode(
248
+ ctx=ctx,
249
+ yolo_mode_attr=self._yolo_mode,
250
+ render_yolo_mode=self._render_yolo_mode,
251
+ )
217
252
  summarization_prompt = get_summarization_system_prompt(
218
253
  ctx=ctx,
219
254
  summarization_prompt_attr=self._summarization_prompt,
220
255
  render_summarization_prompt=self._render_summarization_prompt,
221
256
  )
222
257
  user_message = get_user_message(ctx, self._message, self._render_message)
258
+ attachments = get_attachments(ctx, self._attachment)
223
259
  # 1. Prepare initial state (read history from previous session)
224
260
  conversation_history = await read_conversation_history(
225
261
  ctx=ctx,
@@ -228,9 +264,9 @@ class LLMTask(BaseTask):
228
264
  render_history_file=self._render_history_file,
229
265
  conversation_history_attr=self._conversation_history,
230
266
  )
231
- conversation_history.fetch_newest_notes()
267
+ inject_conversation_history_notes(conversation_history)
232
268
  # 2. Get system prompt and user prompt
233
- system_prompt, user_message = get_system_and_user_prompt(
269
+ system_prompt, user_prompt = get_system_and_user_prompt(
234
270
  ctx=ctx,
235
271
  user_message=user_message,
236
272
  persona_attr=self._persona,
@@ -239,14 +275,15 @@ class LLMTask(BaseTask):
239
275
  render_system_prompt=self._render_system_prompt,
240
276
  special_instruction_prompt_attr=self._special_instruction_prompt,
241
277
  render_special_instruction_prompt=self._render_special_instruction_prompt,
242
- modes_attr=self._modes,
243
- render_modes=self._render_modes,
278
+ workflows_attr=self._workflows,
279
+ render_workflows=self._render_workflows,
244
280
  conversation_history=conversation_history,
245
281
  )
282
+ ctx.log_debug(f"SYSTEM PROMPT:\n{system_prompt}")
283
+ ctx.log_debug(f"USER PROMPT:\n{user_prompt}")
246
284
  # 3. Get the agent instance
247
285
  agent = get_agent(
248
286
  ctx=ctx,
249
- agent_attr=self._agent,
250
287
  model=model,
251
288
  system_prompt=system_prompt,
252
289
  model_settings=model_settings,
@@ -254,15 +291,27 @@ class LLMTask(BaseTask):
254
291
  additional_tools=self._additional_tools,
255
292
  toolsets_attr=self._toolsets,
256
293
  additional_toolsets=self._additional_toolsets,
294
+ yolo_mode=yolo_mode,
257
295
  )
258
296
  # 4. Run the agent iteration and save the results/history
259
297
  result = await self._execute_agent(
260
- ctx,
261
- agent,
262
- user_message,
263
- conversation_history,
298
+ ctx=ctx,
299
+ agent=agent,
300
+ user_prompt=user_prompt,
301
+ attachments=attachments,
302
+ conversation_history=conversation_history,
264
303
  )
265
304
  # 5. Summarize
305
+ small_model = get_model(
306
+ ctx=ctx,
307
+ model_attr=self._small_model,
308
+ render_model=self._render_small_model,
309
+ model_base_url_attr=self._small_model_base_url,
310
+ render_model_base_url=self._render_small_model_base_url,
311
+ model_api_key_attr=self._small_model_api_key,
312
+ render_model_api_key=self._render_small_model_api_key,
313
+ )
314
+ small_model_settings = get_model_settings(ctx, self._small_model_settings)
266
315
  conversation_history = await maybe_summarize_history(
267
316
  ctx=ctx,
268
317
  conversation_history=conversation_history,
@@ -274,8 +323,8 @@ class LLMTask(BaseTask):
274
323
  render_history_summarization_token_threshold=(
275
324
  self._render_history_summarization_token_threshold
276
325
  ),
277
- model=model,
278
- model_settings=model_settings,
326
+ model=small_model,
327
+ model_settings=small_model_settings,
279
328
  summarization_prompt=summarization_prompt,
280
329
  rate_limitter=self._rate_limitter,
281
330
  )
@@ -294,6 +343,7 @@ class LLMTask(BaseTask):
294
343
  ctx: AnyContext,
295
344
  agent: "Agent",
296
345
  user_prompt: str,
346
+ attachments: "list[UserContent]",
297
347
  conversation_history: ConversationHistory,
298
348
  ) -> Any:
299
349
  """Executes the agent, processes results, and saves history."""
@@ -302,6 +352,7 @@ class LLMTask(BaseTask):
302
352
  ctx=ctx,
303
353
  agent=agent,
304
354
  user_prompt=user_prompt,
355
+ attachments=attachments,
305
356
  history_list=conversation_history.history,
306
357
  rate_limitter=self._rate_limitter,
307
358
  )
@@ -313,7 +364,7 @@ class LLMTask(BaseTask):
313
364
  ctx.xcom[xcom_usage_key] = Xcom([])
314
365
  usage = agent_run.result.usage()
315
366
  ctx.xcom[xcom_usage_key].push(usage)
316
- ctx.print(stylize_faint(f"💸 Token: {usage}"), plain=True)
367
+ ctx.print(stylize_faint(f" 💸 Token: {usage}"), plain=True)
317
368
  return agent_run.result.output
318
369
  else:
319
370
  ctx.log_warning("Agent run did not produce a result.")
zrb/task/make_task.py CHANGED
@@ -2,7 +2,6 @@ from collections.abc import Callable
2
2
  from typing import Any
3
3
 
4
4
  from zrb.context.any_context import AnyContext
5
- from zrb.context.any_shared_context import AnySharedContext
6
5
  from zrb.env.any_env import AnyEnv
7
6
  from zrb.group.any_group import AnyGroup
8
7
  from zrb.input.any_input import AnyInput
@@ -18,7 +17,7 @@ def make_task(
18
17
  cli_only: bool = False,
19
18
  input: list[AnyInput | None] | AnyInput | None = None,
20
19
  env: list[AnyEnv | None] | AnyEnv | None = None,
21
- execute_condition: bool | str | Callable[[AnySharedContext], bool] = True,
20
+ execute_condition: bool | str | Callable[[AnyContext], bool] = True,
22
21
  retries: int = 2,
23
22
  retry_period: float = 0,
24
23
  readiness_check: list[AnyTask] | AnyTask | None = None,
@@ -33,7 +32,7 @@ def make_task(
33
32
  group: AnyGroup | None = None,
34
33
  alias: str | None = None,
35
34
  ) -> Callable[[Callable[[AnyContext], Any]], AnyTask]:
36
- def _make_task(fn: Callable[[AnyContext], Any]) -> BaseTask:
35
+ def _make_task(fn: Callable[[AnyContext], Any]) -> AnyTask:
37
36
  task = BaseTask(
38
37
  name=name,
39
38
  color=color,
zrb/task/rsync_task.py CHANGED
@@ -1,6 +1,4 @@
1
- from collections.abc import Callable
2
-
3
- from zrb.attr.type import IntAttr, StrAttr
1
+ from zrb.attr.type import BoolAttr, IntAttr, StrAttr
4
2
  from zrb.context.any_context import AnyContext
5
3
  from zrb.env.any_env import AnyEnv
6
4
  from zrb.input.any_input import AnyInput
@@ -17,8 +15,8 @@ class RsyncTask(CmdTask):
17
15
  icon: str | None = None,
18
16
  description: str | None = None,
19
17
  cli_only: bool = False,
20
- input: list[AnyInput] | AnyInput | None = None,
21
- env: list[AnyEnv] | AnyEnv | None = None,
18
+ input: list[AnyInput | None] | AnyInput | None = None,
19
+ env: list[AnyEnv | None] | AnyEnv | None = None,
22
20
  shell: StrAttr | None = None,
23
21
  auto_render_shell: bool = True,
24
22
  remote_host: StrAttr | None = None,
@@ -39,12 +37,14 @@ class RsyncTask(CmdTask):
39
37
  render_local_source_path: bool = True,
40
38
  local_destination_path: StrAttr | None = None,
41
39
  render_local_destination_path: bool = True,
40
+ exclude_from: StrAttr | None = None,
41
+ render_exclude_from: bool = True,
42
42
  cwd: str | None = None,
43
43
  render_cwd: bool = True,
44
44
  plain_print: bool = False,
45
45
  max_output_line: int = 1000,
46
46
  max_error_line: int = 1000,
47
- execute_condition: bool | str | Callable[[AnyContext], bool] = True,
47
+ execute_condition: BoolAttr = True,
48
48
  retries: int = 2,
49
49
  retry_period: float = 0,
50
50
  readiness_check: list[AnyTask] | AnyTask | None = None,
@@ -93,6 +93,8 @@ class RsyncTask(CmdTask):
93
93
  self._render_local_source_path = render_local_source_path
94
94
  self._local_destination_path = local_destination_path
95
95
  self._render_local_destination_path = render_local_destination_path
96
+ self._exclude_from = exclude_from
97
+ self._render_exclude_from = render_exclude_from
96
98
 
97
99
  def _get_source_path(self, ctx: AnyContext) -> str:
98
100
  local_source_path = self._get_local_source_path(ctx)
@@ -144,16 +146,29 @@ class RsyncTask(CmdTask):
144
146
  auto_render=self._render_local_destination_path,
145
147
  )
146
148
 
149
+ def _get_exclude_from_param(self, ctx: AnyContext) -> str:
150
+ exclude_from = get_str_attr(
151
+ ctx,
152
+ self._exclude_from,
153
+ "",
154
+ auto_render=self._render_exclude_from,
155
+ ).strip()
156
+ if exclude_from == "":
157
+ return ""
158
+ return f"--exclude-from='{exclude_from}'"
159
+
147
160
  def _get_cmd_script(self, ctx: AnyContext) -> str:
148
161
  port = self._get_remote_port(ctx)
149
162
  password = self._get_remote_password(ctx)
150
163
  key = self._get_remote_ssh_key(ctx)
151
164
  src = self._get_source_path(ctx)
152
165
  dst = self._get_destination_path(ctx)
166
+ exclude_from = self._get_exclude_from_param(ctx)
167
+ exclude_from_with_space = f"{exclude_from} " if exclude_from != "" else ""
153
168
  if key != "" and password != "":
154
- return f'sshpass -p "$_ZRB_SSH_PASSWORD" rsync --mkpath -avz -e "ssh -i {key} -p {port}" {src} {dst}' # noqa
169
+ return f'sshpass -p "$_ZRB_SSH_PASSWORD" rsync --mkpath -avz -e "ssh -i {key} -p {port}" {exclude_from_with_space}{src} {dst}' # noqa
155
170
  if key != "":
156
- return f'rsync --mkpath -avz -e "ssh -i {key} -p {port}" {src} {dst}'
171
+ return f'rsync --mkpath -avz -e "ssh -i {key} -p {port}" {exclude_from_with_space}{src} {dst}' # noqa
157
172
  if password != "":
158
- return f'sshpass -p "$_ZRB_SSH_PASSWORD" rsync --mkpath -avz -e "ssh -p {port}" {src} {dst}' # noqa
159
- return f'rsync --mkpath -avz -e "ssh -p {port}" {src} {dst}'
173
+ return f'sshpass -p "$_ZRB_SSH_PASSWORD" rsync --mkpath -avz -e "ssh -p {port}" {exclude_from_with_space}{src} {dst}' # noqa
174
+ return f'rsync --mkpath -avz -e "ssh -p {port}" {exclude_from_with_space}{src} {dst}'
zrb/task/scheduler.py CHANGED
@@ -24,8 +24,8 @@ class Scheduler(BaseTrigger):
24
24
  cli_only: bool = False,
25
25
  input: list[AnyInput | None] | AnyInput | None = None,
26
26
  env: list[AnyEnv | None] | AnyEnv | None = None,
27
- schedule: StrAttr = None,
28
- execute_condition: bool | str | Callable[[AnySharedContext], bool] = True,
27
+ schedule: StrAttr | None = None,
28
+ execute_condition: bool | str | Callable[[AnyContext], bool] = True,
29
29
  queue_name: fstring | None = None,
30
30
  callback: list[AnyCallback] | AnyCallback = [],
31
31
  retries: int = 2,
@@ -76,6 +76,6 @@ class Scheduler(BaseTrigger):
76
76
  ctx.print(f"Current time: {now}")
77
77
  if match_cron(cron_pattern, now):
78
78
  ctx.print(f"Matching {now} with pattern: {cron_pattern}")
79
- xcom = self.get_exchange_xcom(ctx.session)
80
- xcom.push(now)
79
+ if ctx.session is not None:
80
+ self.push_exchange_xcom(ctx.session, now)
81
81
  await asyncio.sleep(60)
zrb/util/attr.py CHANGED
@@ -9,52 +9,54 @@ from zrb.attr.type import (
9
9
  StrDictAttr,
10
10
  StrListAttr,
11
11
  )
12
- from zrb.context.any_shared_context import AnySharedContext
12
+ from zrb.context.any_context import AnyContext
13
13
  from zrb.util.string.conversion import to_boolean
14
14
 
15
15
 
16
16
  def get_str_list_attr(
17
- shared_ctx: AnySharedContext, attr: StrListAttr | None, auto_render: bool = True
17
+ ctx: AnyContext, attr: StrListAttr | None, auto_render: bool = True
18
18
  ) -> list[str]:
19
19
  """
20
20
  Retrieve a list of strings from shared context attributes.
21
21
 
22
22
  Args:
23
- shared_ctx (AnySharedContext): The shared context object.
23
+ ctx (AnyContext): The shared context object.
24
24
  attr (StrListAttr | None): The string list attribute to retrieve.
25
25
  auto_render (bool): Whether to auto-render the attribute values.
26
26
 
27
27
  Returns:
28
28
  list[str]: A list of string attributes.
29
29
  """
30
+ if attr is None:
31
+ return []
30
32
  if callable(attr):
31
- return attr(shared_ctx)
32
- return {get_str_attr(shared_ctx, val, "", auto_render) for val in attr}
33
+ return attr(ctx)
34
+ return [get_str_attr(ctx, val, "", auto_render) for val in attr]
33
35
 
34
36
 
35
37
  def get_str_dict_attr(
36
- shared_ctx: AnySharedContext, attr: StrDictAttr | None, auto_render: bool = True
38
+ ctx: AnyContext, attr: StrDictAttr | None, auto_render: bool = True
37
39
  ) -> dict[str, Any]:
38
40
  """
39
41
  Retrieve a dictionary of strings from shared context attributes.
40
42
 
41
43
  Args:
42
- shared_ctx (AnySharedContext): The shared context object.
44
+ ctx (AnyContext): The shared context object.
43
45
  attr (StrDictAttr | None): The string dictionary attribute to retrieve.
44
46
  auto_render (bool): Whether to auto-render the attribute values.
45
47
 
46
48
  Returns:
47
49
  dict[str, Any]: A dictionary of string attributes.
48
50
  """
51
+ if attr is None:
52
+ return {}
49
53
  if callable(attr):
50
- return attr(shared_ctx)
51
- return {
52
- key: get_str_attr(shared_ctx, val, "", auto_render) for key, val in attr.items()
53
- }
54
+ return attr(ctx)
55
+ return {key: get_str_attr(ctx, val, "", auto_render) for key, val in attr.items()}
54
56
 
55
57
 
56
58
  def get_str_attr(
57
- shared_ctx: AnySharedContext,
59
+ ctx: AnyContext,
58
60
  attr: StrAttr | None,
59
61
  default: StrAttr = "",
60
62
  auto_render: bool = True,
@@ -63,7 +65,7 @@ def get_str_attr(
63
65
  Retrieve a string from shared context attributes.
64
66
 
65
67
  Args:
66
- shared_ctx (AnySharedContext): The shared context object.
68
+ ctx (AnyContext): The shared context object.
67
69
  attr (StrAttr | None): The string attribute to retrieve.
68
70
  default (StrAttr): The default value if the attribute is None.
69
71
  auto_render (bool): Whether to auto-render the attribute value.
@@ -71,14 +73,16 @@ def get_str_attr(
71
73
  Returns:
72
74
  str: The string attribute value.
73
75
  """
74
- val = get_attr(shared_ctx, attr, default, auto_render)
75
- if not isinstance(val, str):
76
- return str(val)
77
- return val
76
+ val = get_attr(ctx, attr, default, auto_render)
77
+ if isinstance(val, str):
78
+ return val
79
+ if val is None:
80
+ return ""
81
+ return str(val)
78
82
 
79
83
 
80
84
  def get_bool_attr(
81
- shared_ctx: AnySharedContext,
85
+ ctx: AnyContext,
82
86
  attr: BoolAttr | None,
83
87
  default: BoolAttr = False,
84
88
  auto_render: bool = True,
@@ -87,7 +91,7 @@ def get_bool_attr(
87
91
  Retrieve a boolean from shared context attributes.
88
92
 
89
93
  Args:
90
- shared_ctx (AnySharedContext): The shared context object.
94
+ ctx (AnyContext): The shared context object.
91
95
  attr (BoolAttr | None): The boolean attribute to retrieve.
92
96
  default (BoolAttr): The default value if the attribute is None.
93
97
  auto_render (bool): Whether to auto-render the attribute value if it's a string.
@@ -95,14 +99,16 @@ def get_bool_attr(
95
99
  Returns:
96
100
  bool: The boolean attribute value.
97
101
  """
98
- val = get_attr(shared_ctx, attr, default, auto_render)
99
- if isinstance(val, str):
100
- return to_boolean(val)
101
- return val
102
+ val = get_attr(ctx, attr, default, auto_render)
103
+ if isinstance(val, bool):
104
+ return val
105
+ if val is None:
106
+ return False
107
+ return to_boolean(val)
102
108
 
103
109
 
104
110
  def get_int_attr(
105
- shared_ctx: AnySharedContext,
111
+ ctx: AnyContext,
106
112
  attr: IntAttr | None,
107
113
  default: IntAttr = 0,
108
114
  auto_render: bool = True,
@@ -111,7 +117,7 @@ def get_int_attr(
111
117
  Retrieve an integer from shared context attributes.
112
118
 
113
119
  Args:
114
- shared_ctx (AnySharedContext): The shared context object.
120
+ ctx (AnyContext): The shared context object.
115
121
  attr (IntAttr | None): The integer attribute to retrieve.
116
122
  default (IntAttr): The default value if the attribute is None.
117
123
  auto_render (bool): Whether to auto-render the attribute value if it's a string.
@@ -119,14 +125,16 @@ def get_int_attr(
119
125
  Returns:
120
126
  int: The integer attribute value.
121
127
  """
122
- val = get_attr(shared_ctx, attr, default, auto_render)
123
- if isinstance(val, str):
124
- return int(val)
125
- return val
128
+ val = get_attr(ctx, attr, default, auto_render)
129
+ if isinstance(val, int):
130
+ return val
131
+ if val is None:
132
+ return 0
133
+ return int(val)
126
134
 
127
135
 
128
136
  def get_float_attr(
129
- shared_ctx: AnySharedContext,
137
+ ctx: AnyContext,
130
138
  attr: FloatAttr | None,
131
139
  default: FloatAttr = 0.0,
132
140
  auto_render: bool = True,
@@ -135,7 +143,7 @@ def get_float_attr(
135
143
  Retrieve a float from shared context attributes.
136
144
 
137
145
  Args:
138
- shared_ctx (AnySharedContext): The shared context object.
146
+ ctx (AnyContext): The shared context object.
139
147
  attr (FloatAttr | None): The float attribute to retrieve.
140
148
  default (FloatAttr): The default value if the attribute is None.
141
149
  auto_render (bool): Whether to auto-render the attribute value if it's a string.
@@ -143,14 +151,16 @@ def get_float_attr(
143
151
  Returns:
144
152
  float | None: The float attribute value.
145
153
  """
146
- val = get_attr(shared_ctx, attr, default, auto_render)
147
- if isinstance(val, str):
148
- return float(val)
149
- return val
154
+ val = get_attr(ctx, attr, default, auto_render)
155
+ if isinstance(val, (int, float)):
156
+ return val
157
+ if val is None:
158
+ return 0.0
159
+ return float(val)
150
160
 
151
161
 
152
162
  def get_attr(
153
- shared_ctx: AnySharedContext,
163
+ ctx: AnyContext,
154
164
  attr: AnyAttr,
155
165
  default: AnyAttr,
156
166
  auto_render: bool = True,
@@ -159,7 +169,7 @@ def get_attr(
159
169
  Retrieve an attribute value from shared context, handling callables and rendering.
160
170
 
161
171
  Args:
162
- shared_ctx (AnySharedContext): The shared context object.
172
+ ctx (AnyContext): The shared context object.
163
173
  attr (AnyAttr): The attribute to retrieve. Can be a value, a callable,
164
174
  or a string to render.
165
175
  default (AnyAttr): The default value if the attribute is None.
@@ -170,10 +180,10 @@ def get_attr(
170
180
  """
171
181
  if attr is None:
172
182
  if callable(default):
173
- return default(shared_ctx)
183
+ return default(ctx)
174
184
  return default
175
185
  if callable(attr):
176
- return attr(shared_ctx)
186
+ return attr(ctx)
177
187
  if isinstance(attr, str) and auto_render:
178
- return shared_ctx.render(attr)
188
+ return ctx.render(attr)
179
189
  return attr