zrb 0.17.2__py3-none-any.whl → 0.21.0__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 (59) hide show
  1. zrb/__init__.py +2 -0
  2. zrb/builtin/project/add/app/generator/generator.py +1 -0
  3. zrb/builtin/project/add/app/generator/template/src/kebab-zrb-package-name/src/snake_zrb_package_name/snake_zrb_generator_name/_input.py +7 -4
  4. zrb/builtin/project/add/app/generator/template/src/kebab-zrb-package-name/src/snake_zrb_package_name/snake_zrb_generator_name/snake_zrb_generator_name.py +1 -0
  5. zrb/builtin/project/add/app/python/python.py +1 -0
  6. zrb/builtin/project/add/fastapp/app/_input.py +8 -4
  7. zrb/builtin/project/add/fastapp/app/app.py +1 -0
  8. zrb/builtin/project/add/fastapp/app/template/src/kebab-zrb-app-name/src/component/rpc/messagebus/caller.py +1 -1
  9. zrb/builtin/project/add/fastapp/crud/_input.py +2 -1
  10. zrb/builtin/project/add/fastapp/crud/crud.py +1 -0
  11. zrb/builtin/project/add/fastapp/crud/nodejs/codemod/package-lock.json +33 -24
  12. zrb/builtin/project/add/fastapp/field/field.py +1 -0
  13. zrb/builtin/project/add/plugin/plugin.py +5 -1
  14. zrb/builtin/project/add/task/cmd/add.py +1 -0
  15. zrb/builtin/project/add/task/docker_compose/add.py +1 -0
  16. zrb/builtin/project/add/task/python/add.py +1 -0
  17. zrb/config/config.py +1 -0
  18. zrb/helper/multiline.py +13 -0
  19. zrb/task/any_task.py +10 -3
  20. zrb/task/base_remote_cmd_task.py +66 -8
  21. zrb/task/base_task/base_task.py +10 -12
  22. zrb/task/base_task/component/base_task_model.py +1 -1
  23. zrb/task/base_task/component/common_task_model.py +35 -4
  24. zrb/task/checker.py +1 -1
  25. zrb/task/cmd_task.py +22 -9
  26. zrb/task/decorator.py +1 -1
  27. zrb/task/docker_compose_task.py +1 -1
  28. zrb/task/flow_task.py +45 -34
  29. zrb/task/http_checker.py +1 -1
  30. zrb/task/notifier.py +1 -1
  31. zrb/task/path_checker.py +1 -1
  32. zrb/task/path_watcher.py +1 -1
  33. zrb/task/port_checker.py +1 -1
  34. zrb/task/recurring_task.py +2 -2
  35. zrb/task/remote_cmd_task.py +1 -1
  36. zrb/task/resource_maker.py +1 -1
  37. zrb/task/rsync_task.py +5 -5
  38. zrb/task/server.py +25 -18
  39. zrb/task/task.py +35 -1
  40. zrb/task/time_watcher.py +1 -1
  41. zrb/task/watcher.py +5 -2
  42. zrb/task_env/env.py +4 -3
  43. zrb/task_env/env_file.py +3 -2
  44. zrb/task_group/group.py +1 -1
  45. zrb/task_input/any_input.py +5 -3
  46. zrb/task_input/base_input.py +54 -9
  47. zrb/task_input/bool_input.py +9 -6
  48. zrb/task_input/choice_input.py +8 -5
  49. zrb/task_input/float_input.py +8 -5
  50. zrb/task_input/int_input.py +8 -5
  51. zrb/task_input/multiline_input.py +118 -0
  52. zrb/task_input/password_input.py +9 -6
  53. zrb/task_input/str_input.py +18 -15
  54. zrb/task_input/task_input.py +4 -3
  55. {zrb-0.17.2.dist-info → zrb-0.21.0.dist-info}/METADATA +14 -11
  56. {zrb-0.17.2.dist-info → zrb-0.21.0.dist-info}/RECORD +59 -57
  57. {zrb-0.17.2.dist-info → zrb-0.21.0.dist-info}/LICENSE +0 -0
  58. {zrb-0.17.2.dist-info → zrb-0.21.0.dist-info}/WHEEL +0 -0
  59. {zrb-0.17.2.dist-info → zrb-0.21.0.dist-info}/entry_points.txt +0 -0
@@ -49,7 +49,7 @@ class CommonTaskModel:
49
49
  upstreams: Iterable[AnyTask] = [],
50
50
  fallbacks: Iterable[AnyTask] = [],
51
51
  checkers: Iterable[AnyTask] = [],
52
- checking_interval: Union[float, int] = 0,
52
+ checking_interval: Union[float, int] = 0.05,
53
53
  run: Optional[Callable[..., Any]] = None,
54
54
  on_triggered: Optional[OnTriggered] = None,
55
55
  on_waiting: Optional[OnWaiting] = None,
@@ -65,6 +65,11 @@ class CommonTaskModel:
65
65
  self._group = group
66
66
  if group is not None:
67
67
  group._add_task(self)
68
+ checkers_cp: List[AnyTask] = [checker.copy() for checker in checkers]
69
+ for checker in checkers_cp:
70
+ checker.add_env(*envs)
71
+ checker.add_env_file(*env_files)
72
+ checker.add_input(*inputs)
68
73
  self._description = coalesce_str(description, name)
69
74
  self._inputs = inputs
70
75
  self._envs = envs
@@ -75,7 +80,7 @@ class CommonTaskModel:
75
80
  self._retry_interval = retry_interval
76
81
  self._upstreams = upstreams
77
82
  self._fallbacks = fallbacks
78
- self._checkers = [checker.copy() for checker in checkers]
83
+ self._checkers = checkers_cp
79
84
  self._checking_interval = checking_interval
80
85
  self._run_function: Optional[Callable[..., Any]] = run
81
86
  self._on_triggered = on_triggered
@@ -101,6 +106,9 @@ class CommonTaskModel:
101
106
  self.__has_already_inject_fallbacks: bool = False
102
107
  self.__all_inputs: Optional[List[AnyInput]] = None
103
108
 
109
+ def _lock_checkers(self):
110
+ self.__allow_add_checkers = False
111
+
104
112
  def _lock_upstreams(self):
105
113
  self.__allow_add_upstreams = False
106
114
 
@@ -164,11 +172,15 @@ class CommonTaskModel:
164
172
  if not self.__allow_add_inputs:
165
173
  raise Exception(f"Cannot insert inputs for `{self.get_name()}`")
166
174
  self._inputs = list(inputs) + list(self._inputs)
175
+ for checker in self._get_checkers():
176
+ checker.insert_input(*inputs)
167
177
 
168
178
  def add_input(self, *inputs: AnyInput):
169
179
  if not self.__allow_add_inputs:
170
180
  raise Exception(f"Cannot add inputs for `{self.get_name()}`")
171
181
  self._inputs = list(self._inputs) + list(inputs)
182
+ for checker in self._get_checkers():
183
+ checker.add_input(*inputs)
172
184
 
173
185
  def inject_inputs(self):
174
186
  pass
@@ -219,11 +231,15 @@ class CommonTaskModel:
219
231
  if not self.__allow_add_envs:
220
232
  raise Exception(f"Cannot insert envs to `{self.get_name()}`")
221
233
  self._envs = list(envs) + list(self._envs)
234
+ for checker in self._get_checkers():
235
+ checker.insert_env(*envs)
222
236
 
223
237
  def add_env(self, *envs: Env):
224
238
  if not self.__allow_add_envs:
225
239
  raise Exception(f"Cannot add envs to `{self.get_name()}`")
226
240
  self._envs = list(self._envs) + list(envs)
241
+ for checker in self._get_checkers():
242
+ checker.add_env(*envs)
227
243
 
228
244
  def inject_envs(self):
229
245
  pass
@@ -255,11 +271,15 @@ class CommonTaskModel:
255
271
  if not self.__allow_add_env_files:
256
272
  raise Exception(f"Cannot insert env_files to `{self.get_name()}`")
257
273
  self._env_files = list(env_files) + list(self._env_files)
274
+ for checker in self._get_checkers():
275
+ checker.insert_env_file(*env_files)
258
276
 
259
277
  def add_env_file(self, *env_files: EnvFile):
260
278
  if not self.__allow_add_env_files:
261
279
  raise Exception(f"Cannot add env_files to `{self.get_name()}`")
262
280
  self._env_files = list(self._env_files) + list(env_files)
281
+ for checker in self._get_checkers():
282
+ checker.add_env_file(*env_files)
263
283
 
264
284
  def inject_env_files(self):
265
285
  pass
@@ -317,15 +337,26 @@ class CommonTaskModel:
317
337
  def insert_checker(self, *checkers: AnyTask):
318
338
  if not self.__allow_add_checkers:
319
339
  raise Exception(f"Cannot insert checkers to `{self.get_name()}`")
320
- additional_checkers = [checker.copy() for checker in checkers]
340
+ additional_checkers = self.__complete_new_checkers(checkers)
321
341
  self._checkers = additional_checkers + self._checkers
322
342
 
323
343
  def add_checker(self, *checkers: AnyTask):
324
344
  if not self.__allow_add_checkers:
325
345
  raise Exception(f"Cannot add checkers to `{self.get_name()}`")
326
- additional_checkers = [checker.copy() for checker in checkers]
346
+ additional_checkers = self.__complete_new_checkers(checkers)
327
347
  self._checkers = self._checkers + additional_checkers
328
348
 
349
+ def __complete_new_checkers(self, new_checkers: List[AnyTask]) -> List[AnyTask]:
350
+ """
351
+ For internal use: copy and completing new checkers
352
+ """
353
+ checkers: List[AnyTask] = [checker.copy() for checker in new_checkers]
354
+ for checker in checkers:
355
+ checker.add_input(*self._get_inputs())
356
+ checker.add_env(*self._get_envs())
357
+ checker.add_env_file(*self._get_env_files())
358
+ return checkers
359
+
329
360
  def inject_checkers(self):
330
361
  pass
331
362
 
zrb/task/checker.py CHANGED
@@ -44,7 +44,7 @@ class Checker(BaseTask):
44
44
  on_ready: Optional[OnReady] = None,
45
45
  on_retry: Optional[OnRetry] = None,
46
46
  on_failed: Optional[OnFailed] = None,
47
- checking_interval: Union[int, float] = 0,
47
+ checking_interval: Union[int, float] = 0.05,
48
48
  progress_interval: Union[int, float] = 30,
49
49
  expected_result: bool = True,
50
50
  should_execute: Union[bool, str, Callable[..., bool]] = True,
zrb/task/cmd_task.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import atexit
3
+ import logging
3
4
  import os
4
5
  import pathlib
5
6
  import signal
@@ -7,7 +8,7 @@ import subprocess
7
8
  import sys
8
9
  import time
9
10
 
10
- from zrb.config.config import default_shell
11
+ from zrb.config.config import default_shell, logging_level
11
12
  from zrb.helper.accessories.color import colored
12
13
  from zrb.helper.log import logger
13
14
  from zrb.helper.typecheck import typechecked
@@ -55,6 +56,18 @@ def _reset_stty():
55
56
  _has_stty = False
56
57
 
57
58
 
59
+ def _log_error(message: Any):
60
+ if logging_level > logging.ERROR:
61
+ return
62
+ colored_message = colored(f"{message}", color="red", attrs=["bold"])
63
+ logger.error(colored_message, exc_info=True)
64
+
65
+
66
+ def _print_out_dark(message: Any):
67
+ message_str = f"{message}"
68
+ print(colored(message_str, attrs=["dark"]), file=sys.stderr)
69
+
70
+
58
71
  CmdVal = Union[
59
72
  JinjaTemplate,
60
73
  Iterable[JinjaTemplate],
@@ -125,7 +138,7 @@ class CmdTask(BaseTask):
125
138
  on_retry: Optional[OnRetry] = None,
126
139
  on_failed: Optional[OnFailed] = None,
127
140
  checkers: Iterable[AnyTask] = [],
128
- checking_interval: Union[float, int] = 0,
141
+ checking_interval: Union[float, int] = 0.05,
129
142
  retry: int = 2,
130
143
  retry_interval: Union[float, int] = 1,
131
144
  max_output_line: int = 1000,
@@ -272,7 +285,7 @@ class CmdTask(BaseTask):
272
285
  def __on_kill(self, signum: Any, frame: Any):
273
286
  self._global_state.no_more_attempt = True
274
287
  self._global_state.is_killed_by_signal = True
275
- self.print_out_dark(f"Getting signal {signum}")
288
+ _print_out_dark(f"Getting signal {signum}")
276
289
  for pid in self._pids:
277
290
  self.__kill_by_pid(pid)
278
291
  tasks = asyncio.all_tasks()
@@ -282,7 +295,7 @@ class CmdTask(BaseTask):
282
295
  except Exception as e:
283
296
  self.print_err(e)
284
297
  time.sleep(0.3)
285
- self.print_out_dark(f"Exiting with signal {signum}")
298
+ _print_out_dark(f"Exiting with signal {signum}")
286
299
  sys.exit(signum)
287
300
 
288
301
  def __on_exit(self):
@@ -297,20 +310,20 @@ class CmdTask(BaseTask):
297
310
  process_ever_exists = False
298
311
  if self.__is_process_exist(pid):
299
312
  process_ever_exists = True
300
- self.print_out_dark(f"Send SIGTERM to process {pid}")
313
+ _print_out_dark(f"Send SIGTERM to process {pid}")
301
314
  os.killpg(os.getpgid(pid), signal.SIGTERM)
302
315
  time.sleep(0.3)
303
316
  if self.__is_process_exist(pid):
304
- self.print_out_dark(f"Send SIGINT to process {pid}")
317
+ _print_out_dark(f"Send SIGINT to process {pid}")
305
318
  os.killpg(os.getpgid(pid), signal.SIGINT)
306
319
  time.sleep(0.3)
307
320
  if self.__is_process_exist(pid):
308
- self.print_out_dark(f"Send SIGKILL to process {pid}")
321
+ _print_out_dark(f"Send SIGKILL to process {pid}")
309
322
  os.killpg(os.getpgid(pid), signal.SIGKILL)
310
323
  if process_ever_exists:
311
- self.print_out_dark(f"Process {pid} is killed successfully")
324
+ _print_out_dark(f"Process {pid} is killed successfully")
312
325
  except Exception:
313
- self.log_error(f"Cannot kill process {pid}")
326
+ _log_error(f"Cannot kill process {pid}")
314
327
 
315
328
  def __is_process_exist(self, pid: int) -> bool:
316
329
  try:
zrb/task/decorator.py CHANGED
@@ -43,7 +43,7 @@ def python_task(
43
43
  on_retry: Optional[OnRetry] = None,
44
44
  on_failed: Optional[OnFailed] = None,
45
45
  checkers: Iterable[AnyTask] = [],
46
- checking_interval: Union[float, int] = 0,
46
+ checking_interval: Union[float, int] = 0.05,
47
47
  retry: int = 2,
48
48
  retry_interval: Union[float, int] = 1,
49
49
  should_execute: Union[bool, str, Callable[..., bool]] = True,
@@ -115,7 +115,7 @@ class DockerComposeTask(CmdTask):
115
115
  on_retry: Optional[OnRetry] = None,
116
116
  on_failed: Optional[OnFailed] = None,
117
117
  checkers: Iterable[AnyTask] = [],
118
- checking_interval: Union[float, int] = 0,
118
+ checking_interval: Union[float, int] = 0.05,
119
119
  retry: int = 2,
120
120
  retry_interval: Union[float, int] = 1,
121
121
  max_output_line: int = 1000,
zrb/task/flow_task.py CHANGED
@@ -45,27 +45,13 @@ class FlowTask(BaseTask):
45
45
  on_retry: Optional[OnRetry] = None,
46
46
  on_failed: Optional[OnFailed] = None,
47
47
  checkers: Iterable[AnyTask] = [],
48
- checking_interval: float = 0,
48
+ checking_interval: Union[float, int] = 0.05,
49
49
  retry: int = 2,
50
- retry_interval: float = 1,
50
+ retry_interval: Union[float, int] = 1,
51
51
  steps: List[Union[AnyTask, List[AnyTask]]] = [],
52
52
  should_execute: Union[bool, str, Callable[..., bool]] = True,
53
53
  return_upstream_result: bool = False,
54
54
  ):
55
- final_upstreams: List[AnyTask] = list(upstreams)
56
- inputs: List[AnyInput] = list(inputs)
57
- envs: List[Env] = list(envs)
58
- env_files: List[EnvFile] = list(env_files)
59
- for step in steps:
60
- tasks = self._step_to_tasks(step)
61
- new_upstreams = self._get_embeded_tasks(
62
- tasks=tasks,
63
- upstreams=final_upstreams,
64
- inputs=inputs,
65
- envs=envs,
66
- env_files=env_files,
67
- )
68
- final_upstreams = new_upstreams
69
55
  BaseTask.__init__(
70
56
  self,
71
57
  name=name,
@@ -76,7 +62,13 @@ class FlowTask(BaseTask):
76
62
  icon=icon,
77
63
  color=color,
78
64
  description=description,
79
- upstreams=final_upstreams,
65
+ upstreams=self._create_flow_upstreams(
66
+ steps=steps,
67
+ upstreams=list(upstreams),
68
+ inputs=list(inputs),
69
+ envs=list(envs),
70
+ env_files=list(env_files),
71
+ ),
80
72
  fallbacks=fallbacks,
81
73
  on_triggered=on_triggered,
82
74
  on_waiting=on_waiting,
@@ -97,12 +89,33 @@ class FlowTask(BaseTask):
97
89
  def copy(self) -> TFlowTask:
98
90
  return super().copy()
99
91
 
100
- def _step_to_tasks(self, node: Union[AnyTask, List[AnyTask]]) -> List[AnyTask]:
101
- if isinstance(node, AnyTask):
102
- return [node]
103
- return node
92
+ def _create_flow_upstreams(
93
+ self,
94
+ steps: List[Union[AnyTask, List[AnyTask]]],
95
+ upstreams: List[AnyTask],
96
+ inputs: List[AnyInput],
97
+ envs: List[Env],
98
+ env_files: List[EnvFile],
99
+ ) -> List[AnyTask]:
100
+ flow_upstreams = upstreams
101
+ for step in steps:
102
+ tasks = [task.copy() for task in self._step_to_tasks(step)]
103
+ new_upstreams = self._create_embeded_tasks(
104
+ tasks=tasks,
105
+ upstreams=flow_upstreams,
106
+ inputs=inputs,
107
+ envs=envs,
108
+ env_files=env_files,
109
+ )
110
+ flow_upstreams = new_upstreams
111
+ return flow_upstreams
112
+
113
+ def _step_to_tasks(self, step: Union[AnyTask, List[AnyTask]]) -> List[AnyTask]:
114
+ if isinstance(step, AnyTask):
115
+ return [step]
116
+ return step
104
117
 
105
- def _get_embeded_tasks(
118
+ def _create_embeded_tasks(
106
119
  self,
107
120
  tasks: List[AnyTask],
108
121
  upstreams: List[AnyTask],
@@ -111,28 +124,26 @@ class FlowTask(BaseTask):
111
124
  env_files: List[EnvFile],
112
125
  ) -> List[AnyTask]:
113
126
  embeded_tasks: List[AnyTask] = []
114
- for task in tasks:
115
- embeded_task = task.copy()
116
- embeded_task_root_upstreams = self._get_root_upstreams(tasks=[embeded_task])
117
- for embeded_task_root_upstream in embeded_task_root_upstreams:
118
- embeded_task_root_upstream.add_upstream(*upstreams)
119
- # embeded_task.add_upstream(*upstreams)
127
+ for embeded_task in tasks:
128
+ embeded_task_upstreams = self._get_all_upstreams(tasks=[embeded_task])
129
+ for embeded_task_upstream in embeded_task_upstreams:
130
+ embeded_task_upstream.add_upstream(*upstreams)
120
131
  embeded_task.add_env(*envs)
121
132
  embeded_task.add_env_file(*env_files)
122
133
  embeded_task.add_input(*inputs)
123
134
  embeded_tasks.append(embeded_task)
124
135
  return embeded_tasks
125
136
 
126
- def _get_root_upstreams(self, tasks: List[AnyTask]):
127
- root_upstreams = []
137
+ def _get_all_upstreams(self, tasks: List[AnyTask]):
138
+ all_upstreams = []
128
139
  for task in tasks:
129
140
  upstreams = task._get_upstreams()
130
141
  if len(upstreams) == 0:
131
- root_upstreams.append(task)
142
+ all_upstreams.append(task)
132
143
  continue
133
144
  for upstream in upstreams:
134
145
  if len(upstream._get_upstreams()) == 0:
135
- root_upstreams.append(upstream)
146
+ all_upstreams.append(upstream)
136
147
  continue
137
- root_upstreams += self._get_root_upstreams([upstream])
138
- return root_upstreams
148
+ all_upstreams += self._get_all_upstreams([upstream])
149
+ return all_upstreams
zrb/task/http_checker.py CHANGED
@@ -87,7 +87,7 @@ class HTTPChecker(Checker):
87
87
  on_ready: Optional[OnReady] = None,
88
88
  on_retry: Optional[OnRetry] = None,
89
89
  on_failed: Optional[OnFailed] = None,
90
- checking_interval: Union[int, float] = 0,
90
+ checking_interval: Union[int, float] = 0.05,
91
91
  progress_interval: Union[int, float] = 5,
92
92
  expected_result: bool = True,
93
93
  should_execute: Union[bool, JinjaTemplate, Callable[..., bool]] = True,
zrb/task/notifier.py CHANGED
@@ -58,7 +58,7 @@ class Notifier(BaseTask):
58
58
  on_ready: Optional[OnReady] = None,
59
59
  on_retry: Optional[OnRetry] = None,
60
60
  on_failed: Optional[OnFailed] = None,
61
- checking_interval: Union[int, float] = 0,
61
+ checking_interval: Union[int, float] = 0.05,
62
62
  retry: int = 2,
63
63
  retry_interval: Union[float, int] = 1,
64
64
  should_execute: Union[bool, str, Callable[..., bool]] = True,
zrb/task/path_checker.py CHANGED
@@ -56,7 +56,7 @@ class PathChecker(Checker):
56
56
  on_failed: Optional[OnFailed] = None,
57
57
  path: JinjaTemplate = "",
58
58
  ignored_path: Union[JinjaTemplate, Iterable[JinjaTemplate]] = [],
59
- checking_interval: Union[int, float] = 0,
59
+ checking_interval: Union[int, float] = 0.05,
60
60
  progress_interval: Union[int, float] = 5,
61
61
  expected_result: bool = True,
62
62
  should_execute: Union[bool, JinjaTemplate, Callable[..., bool]] = True,
zrb/task/path_watcher.py CHANGED
@@ -72,7 +72,7 @@ class PathWatcher(Watcher):
72
72
  on_failed: Optional[OnFailed] = None,
73
73
  path: JinjaTemplate = "",
74
74
  ignored_path: Union[JinjaTemplate, Iterable[JinjaTemplate]] = [],
75
- checking_interval: Union[int, float] = 0,
75
+ checking_interval: Union[int, float] = 0.05,
76
76
  progress_interval: Union[int, float] = 30,
77
77
  watch_new_files: bool = True,
78
78
  watch_modified_files: bool = True,
zrb/task/port_checker.py CHANGED
@@ -66,7 +66,7 @@ class PortChecker(Checker):
66
66
  on_ready: Optional[OnReady] = None,
67
67
  on_retry: Optional[OnRetry] = None,
68
68
  on_failed: Optional[OnFailed] = None,
69
- checking_interval: Union[int, float] = 0,
69
+ checking_interval: Union[int, float] = 0.05,
70
70
  progress_interval: Union[int, float] = 5,
71
71
  expected_result: bool = True,
72
72
  should_execute: Union[bool, str, Callable[..., bool]] = True,
@@ -76,7 +76,7 @@ class RecurringTask(BaseTask):
76
76
  on_retry: Optional[OnRetry] = None,
77
77
  on_failed: Optional[OnFailed] = None,
78
78
  checkers: Iterable[AnyTask] = [],
79
- checking_interval: float = 0,
79
+ checking_interval: float = 0.05,
80
80
  retry: int = 0,
81
81
  retry_interval: float = 1,
82
82
  should_execute: Union[bool, str, Callable[..., bool]] = True,
@@ -185,7 +185,7 @@ class RecurringTask(BaseTask):
185
185
  async def __run_from_queue(self):
186
186
  while True:
187
187
  if len(self._run_configs) == 0:
188
- await asyncio.sleep(0.1)
188
+ await asyncio.sleep(0.05)
189
189
  continue
190
190
  if self._single_execution:
191
191
  # Drain the queue, leave only the latest task
@@ -66,7 +66,7 @@ class RemoteCmdTask(BaseRemoteCmdTask):
66
66
  on_retry: Optional[OnRetry] = None,
67
67
  on_failed: Optional[OnFailed] = None,
68
68
  checkers: Iterable[AnyTask] = [],
69
- checking_interval: Union[float, int] = 0,
69
+ checking_interval: Union[float, int] = 0.05,
70
70
  retry: int = 2,
71
71
  retry_interval: Union[float, int] = 1,
72
72
  max_output_line: int = 1000,
@@ -123,7 +123,7 @@ class ResourceMaker(BaseTask):
123
123
  on_retry=on_retry,
124
124
  on_failed=on_failed,
125
125
  checkers=[],
126
- checking_interval=0.1,
126
+ checking_interval=0.05,
127
127
  retry=0,
128
128
  retry_interval=0,
129
129
  should_execute=should_execute,
zrb/task/rsync_task.py CHANGED
@@ -48,8 +48,8 @@ class RsyncTask(BaseRemoteCmdTask):
48
48
  remote_configs: Iterable[RemoteConfig],
49
49
  src: JinjaTemplate,
50
50
  dst: JinjaTemplate,
51
- is_remote_src: bool = False,
52
- is_remote_dst: bool = True,
51
+ src_is_remote: bool = False,
52
+ dst_is_remote: bool = True,
53
53
  group: Optional[Group] = None,
54
54
  inputs: Iterable[AnyInput] = [],
55
55
  envs: Iterable[Env] = [],
@@ -69,7 +69,7 @@ class RsyncTask(BaseRemoteCmdTask):
69
69
  on_retry: Optional[OnRetry] = None,
70
70
  on_failed: Optional[OnFailed] = None,
71
71
  checkers: Iterable[AnyTask] = [],
72
- checking_interval: Union[float, int] = 0,
72
+ checking_interval: Union[float, int] = 0.05,
73
73
  retry: int = 2,
74
74
  retry_interval: Union[float, int] = 1,
75
75
  max_output_line: int = 1000,
@@ -77,8 +77,8 @@ class RsyncTask(BaseRemoteCmdTask):
77
77
  preexec_fn: Optional[Callable[[], Any]] = os.setsid,
78
78
  should_execute: Union[bool, str, Callable[..., bool]] = True,
79
79
  ):
80
- parsed_src = self._get_parsed_path(is_remote_src, src)
81
- parsed_dst = self._get_parsed_path(is_remote_dst, dst)
80
+ parsed_src = self._get_parsed_path(src_is_remote, src)
81
+ parsed_dst = self._get_parsed_path(dst_is_remote, dst)
82
82
  cmd = f'auth_rsync "{parsed_src}" "{parsed_dst}"'
83
83
  BaseRemoteCmdTask.__init__(
84
84
  self,
zrb/task/server.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import copy
2
3
 
3
4
  from zrb.helper.accessories.color import colored
4
5
  from zrb.helper.accessories.name import get_random_name
@@ -35,8 +36,8 @@ class Controller:
35
36
  name: Optional[str] = None,
36
37
  ):
37
38
  self._name = get_random_name() if name is None else name
38
- self._triggers = [trigger] if isinstance(trigger, AnyTask) else trigger
39
- self._actions = [action] if isinstance(action, AnyTask) else action
39
+ self._triggers = self._to_task_list(trigger)
40
+ self._actions = self._to_task_list(action)
40
41
  self._args: List[Any] = []
41
42
  self._kwargs: Mapping[str, Any] = {}
42
43
  self._inputs: List[AnyInput] = []
@@ -58,28 +59,28 @@ class Controller:
58
59
  def set_env_files(self, env_files: List[EnvFile]):
59
60
  self._env_files = env_files
60
61
 
61
- def get_sub_env_files(self) -> Iterable[EnvFile]:
62
+ def get_original_env_files(self) -> Iterable[EnvFile]:
62
63
  env_files = []
63
64
  for trigger in self._triggers:
64
- env_files += trigger.copy()._get_env_files()
65
+ env_files += trigger._get_env_files()
65
66
  for action in self._actions:
66
- env_files += action.copy()._get_env_files()
67
+ env_files += action._get_env_files()
67
68
  return env_files
68
69
 
69
- def get_sub_envs(self) -> Iterable[Env]:
70
+ def get_original_envs(self) -> Iterable[Env]:
70
71
  envs = []
71
72
  for trigger in self._triggers:
72
- envs += trigger.copy()._get_envs()
73
+ envs += trigger._get_envs()
73
74
  for action in self._actions:
74
- envs += action.copy()._get_envs()
75
+ envs += action._get_envs()
75
76
  return envs
76
77
 
77
- def get_sub_inputs(self) -> Iterable[AnyInput]:
78
+ def get_original_inputs(self) -> Iterable[AnyInput]:
78
79
  inputs = []
79
80
  for trigger in self._triggers:
80
- inputs += trigger.copy()._get_combined_inputs()
81
+ inputs += trigger._get_combined_inputs()
81
82
  for action in self._actions:
82
- inputs += action.copy()._get_combined_inputs()
83
+ inputs += action._get_combined_inputs()
83
84
  return inputs
84
85
 
85
86
  def to_function(self) -> Callable[..., Any]:
@@ -92,6 +93,11 @@ class Controller:
92
93
 
93
94
  return fn
94
95
 
96
+ def _to_task_list(self, tasks: Union[AnyTask, List[AnyTask]]) -> List[AnyTask]:
97
+ if isinstance(tasks, AnyTask):
98
+ return [tasks.copy()]
99
+ return [task.copy() for task in tasks]
100
+
95
101
  def _get_task(self) -> AnyTask:
96
102
  actions = [action.copy() for action in self._actions]
97
103
  actions.insert(0, self._get_remonitor_task())
@@ -123,7 +129,7 @@ class Server(BaseTask):
123
129
  def __init__(
124
130
  self,
125
131
  name: str,
126
- controllers: List[Controller],
132
+ controllers: Iterable[Controller],
127
133
  group: Optional[Group] = None,
128
134
  inputs: Iterable[AnyInput] = [],
129
135
  envs: Iterable[Env] = [],
@@ -141,17 +147,18 @@ class Server(BaseTask):
141
147
  on_retry: Optional[OnRetry] = None,
142
148
  on_failed: Optional[OnFailed] = None,
143
149
  checkers: Iterable[AnyTask] = [],
144
- checking_interval: float = 0,
150
+ checking_interval: Union[int, float] = 0.05,
145
151
  retry: int = 0,
146
- retry_interval: float = 1,
152
+ retry_interval: Union[int, float] = 1,
147
153
  should_execute: Union[bool, str, Callable[..., bool]] = True,
148
154
  return_upstream_result: bool = False,
149
155
  ):
150
156
  inputs, envs, env_files = list(inputs), list(envs), list(env_files)
151
157
  for controller in controllers:
152
- inputs += controller.get_sub_inputs()
153
- envs += controller.get_sub_envs()
154
- env_files += controller.get_sub_env_files()
158
+ controller_cp = copy.deepcopy(controller)
159
+ inputs += controller_cp.get_original_inputs()
160
+ envs += controller_cp.get_original_envs()
161
+ env_files += controller_cp.get_original_env_files()
155
162
  BaseTask.__init__(
156
163
  self,
157
164
  name=name,
@@ -178,7 +185,7 @@ class Server(BaseTask):
178
185
  should_execute=should_execute,
179
186
  return_upstream_result=return_upstream_result,
180
187
  )
181
- self._controllers = controllers
188
+ self._controllers = list(controllers)
182
189
 
183
190
  async def run(self, *args: Any, **kwargs: Any):
184
191
  for controller in self._controllers:
zrb/task/task.py CHANGED
@@ -9,7 +9,41 @@ logger.debug(colored("Loading zrb.task.task", attrs=["dark"]))
9
9
  @typechecked
10
10
  class Task(BaseTask):
11
11
  """
12
- Alias for BaseTask.
12
+ Task is the smallest Zrb automation unit.
13
+
14
+ You can configure a Task by using several interfaces:
15
+ - `inputs`: interfaces to read user input at the beginning of the execution.
16
+ - `envs`: interfaces to read and use OS Environment Variables.
17
+ - `env_files`: interfaces to read and use Environment Files.
18
+
19
+ Moreover, you can define Task dependencies by specifying its `upstreams` or by using shift-right operator.
20
+
21
+ Every Zrb Task has its life-cycle state:
22
+ - `Triggered`: The Task is triggered (either by the user or by the other Task).
23
+ - `Waiting`: Zrb has already triggered the Task. The Task is now waiting for all its upstreams to be ready.
24
+ - `Skipped`: Task upstreams are ready, but the Task is not executed and will immediately enter the `Ready` state.
25
+ - `Started`: The upstreams are ready, and Zrb is now starting the Task execution.
26
+ - `Failed`: Zrb failed to execute the Task. It will enter the `Retry` state if the current attempt does not exceed the maximum attempt.
27
+ - `Retry`: The task has already entered the `Failed` state. Now, Zrb will try to start the Task execution.
28
+ - `Ready`: The task is ready.
29
+
30
+ There are several configurations related to Task's life cycle:
31
+ - `retry`: Maximum retry attempt.
32
+ - `retry_interval`: The duration is to wait before Zrb starts the next attempt.
33
+ - `fallbacks`: Action to take if the Task has failed for good.
34
+ - `checkers`: How to determine if a Task is `Ready`.
35
+ - `checking_interval`: The duration to wait before Zrb checks for the Task's readiness.
36
+ - `run`: Action to do when Zrb executes the Task.
37
+ - `on_triggered`: Action to do when a Task is `Triggered`.
38
+ - `on_waiting`: Action to do when a Task is `Waiting`.
39
+ - `on_skipped`: Action to do when a Task is `Skipped`.
40
+ - `on_started`: Action to do when a Task is `Started`.
41
+ - `on_ready`: Action to do when a Task is `Ready`.
42
+ - `on_retry`: Action to do when a Task is `Retry`.
43
+ - `on_failed`: Action to do when a Task is `Failed`.
44
+ - `should_execute`: Condition to determine whether a Task should be `Started` or `Skipped`.
45
+
46
+ Finally, you can put related Tasks under the same `group`.
13
47
  """
14
48
 
15
49
  pass
zrb/task/time_watcher.py CHANGED
@@ -68,7 +68,7 @@ class TimeWatcher(Watcher):
68
68
  on_retry: Optional[OnRetry] = None,
69
69
  on_failed: Optional[OnFailed] = None,
70
70
  schedule: JinjaTemplate = "",
71
- checking_interval: Union[int, float] = 0,
71
+ checking_interval: Union[int, float] = 0.05,
72
72
  progress_interval: Union[int, float] = 30,
73
73
  should_execute: Union[bool, JinjaTemplate, Callable[..., bool]] = True,
74
74
  ):
zrb/task/watcher.py CHANGED
@@ -2,6 +2,7 @@ import asyncio
2
2
 
3
3
  from zrb.helper.accessories.color import colored
4
4
  from zrb.helper.accessories.name import get_random_name
5
+ from zrb.helper.callable import run_async
5
6
  from zrb.helper.log import logger
6
7
  from zrb.helper.typecheck import typechecked
7
8
  from zrb.helper.typing import Any, Callable, Iterable, Optional, Union
@@ -48,7 +49,7 @@ class Watcher(Checker):
48
49
  on_ready: Optional[OnReady] = None,
49
50
  on_retry: Optional[OnRetry] = None,
50
51
  on_failed: Optional[OnFailed] = None,
51
- checking_interval: Union[int, float] = 0,
52
+ checking_interval: Union[int, float] = 0.05,
52
53
  progress_interval: Union[int, float] = 30,
53
54
  expected_result: bool = True,
54
55
  should_execute: Union[bool, str, Callable[..., bool]] = True,
@@ -85,7 +86,9 @@ class Watcher(Checker):
85
86
  async def run(self, *args: Any, **kwargs: Any) -> bool:
86
87
  if not looper.is_registered(self._identifier):
87
88
  asyncio.create_task(
88
- looper.register(self._identifier, self.create_loop_inspector())
89
+ run_async(
90
+ looper.register, self._identifier, self.create_loop_inspector()
91
+ )
89
92
  )
90
93
  return await super().run(*args, **kwargs)
91
94