zrb 0.17.0__py3-none-any.whl → 0.17.5__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 (38) hide show
  1. zrb/builtin/devtool/install/helix/helix.py +1 -4
  2. zrb/builtin/project/add/fastapp/app/template/src/kebab-zrb-app-name/src/component/rpc/messagebus/caller.py +1 -1
  3. zrb/task/any_task.py +6 -9
  4. zrb/task/base_remote_cmd_task.py +3 -3
  5. zrb/task/base_task/base_task.py +49 -14
  6. zrb/task/base_task/component/base_task_model.py +1 -1
  7. zrb/task/base_task/component/common_task_model.py +1 -1
  8. zrb/task/base_task/component/trackers.py +4 -3
  9. zrb/task/checker.py +1 -1
  10. zrb/task/cmd_task.py +1 -1
  11. zrb/task/decorator.py +2 -2
  12. zrb/task/docker_compose_task.py +2 -2
  13. zrb/task/flow_task.py +45 -34
  14. zrb/task/http_checker.py +1 -1
  15. zrb/task/looper.py +3 -0
  16. zrb/task/notifier.py +1 -1
  17. zrb/task/path_checker.py +1 -1
  18. zrb/task/path_watcher.py +1 -1
  19. zrb/task/port_checker.py +1 -1
  20. zrb/task/recurring_task.py +2 -2
  21. zrb/task/remote_cmd_task.py +1 -1
  22. zrb/task/resource_maker.py +1 -1
  23. zrb/task/rsync_task.py +1 -1
  24. zrb/task/server.py +24 -16
  25. zrb/task/task.py +35 -1
  26. zrb/task/time_watcher.py +3 -3
  27. zrb/task/watcher.py +5 -2
  28. zrb/task_env/env.py +4 -3
  29. zrb/task_env/env_file.py +3 -2
  30. zrb/task_group/group.py +1 -1
  31. zrb/task_input/any_input.py +5 -3
  32. zrb/task_input/base_input.py +3 -3
  33. zrb/task_input/task_input.py +1 -1
  34. {zrb-0.17.0.dist-info → zrb-0.17.5.dist-info}/METADATA +14 -11
  35. {zrb-0.17.0.dist-info → zrb-0.17.5.dist-info}/RECORD +38 -38
  36. {zrb-0.17.0.dist-info → zrb-0.17.5.dist-info}/LICENSE +0 -0
  37. {zrb-0.17.0.dist-info → zrb-0.17.5.dist-info}/WHEEL +0 -0
  38. {zrb-0.17.0.dist-info → zrb-0.17.5.dist-info}/entry_points.txt +0 -0
@@ -29,7 +29,6 @@ install_helix = FlowTask(
29
29
  run=write_config(
30
30
  template_file=os.path.join(
31
31
  _CURRENT_DIR,
32
- "helix",
33
32
  "resource",
34
33
  "themes",
35
34
  "gruvbox_transparent.toml", # noqa
@@ -41,9 +40,7 @@ install_helix = FlowTask(
41
40
  Task(
42
41
  name="configure-helix",
43
42
  run=write_config(
44
- template_file=os.path.join(
45
- _CURRENT_DIR, "helix", "resource", "config.toml"
46
- ),
43
+ template_file=os.path.join(_CURRENT_DIR, "resource", "config.toml"),
47
44
  config_file="~/.config/helix/config.toml",
48
45
  remove_old_config=True,
49
46
  ),
@@ -15,7 +15,7 @@ class MessagebusCaller(Caller):
15
15
  publisher: Publisher,
16
16
  consumer_factory: Callable[[], Consumer],
17
17
  timeout: float = 30,
18
- check_reply_interval: float = 0.1,
18
+ check_reply_interval: float = 0.05,
19
19
  ):
20
20
  self.logger = logger
21
21
  self.admin = admin
zrb/task/any_task.py CHANGED
@@ -25,14 +25,12 @@ TAnyTask = TypeVar("TAnyTask", bound="AnyTask")
25
25
 
26
26
  class AnyTask(ABC):
27
27
  """
28
- Abstract base class for defining tasks in a task management system.
28
+ Abstraction for Zrb Task.
29
29
 
30
- This class acts as a template for creating new task types. To define a new task,
31
- extend this class and implement all its abstract methods. The `AnyTask` class is
32
- considered atomic and is not broken into multiple interfaces.
30
+ This class acts as a template for creating new Task type.
33
31
 
34
- Subclasses should implement the abstract methods to define custom behavior for
35
- task execution, state transitions, and other functionalities.
32
+ To define a new Task type, you should extend this class and implement all its methods.
33
+ The easiest way to do so is by extending `Task`
36
34
  """
37
35
 
38
36
  @abstractmethod
@@ -74,7 +72,7 @@ class AnyTask(ABC):
74
72
  Examples:
75
73
  >>> from zrb import Task
76
74
  >>> class MyTask(Task):
77
- >>> async def run(self, *args: Any, **kwargs: Any) -> int:
75
+ >>> def run(self, *args: Any, **kwargs: Any) -> int:
78
76
  >>> self.print_out('Doing some calculation')
79
77
  >>> return 42
80
78
  """
@@ -88,8 +86,7 @@ class AnyTask(ABC):
88
86
  Any other tasks depends on the current task, will be `started` once the current task is `ready`.
89
87
 
90
88
  This method should be implemented to define the criteria for considering the task
91
- `ready`. The specifics of this completion depend on the task's
92
- nature and the subclass implementation.
89
+ `ready`. The specifics of this completion depend on the task's nature and the subclass implementation.
93
90
 
94
91
  Returns:
95
92
  bool: True if the task is completed, False otherwise.
@@ -91,7 +91,7 @@ class SingleBaseRemoteCmdTask(CmdTask):
91
91
  on_retry: Optional[OnRetry] = None,
92
92
  on_failed: Optional[OnFailed] = None,
93
93
  checkers: Iterable[AnyTask] = [],
94
- checking_interval: Union[float, int] = 0,
94
+ checking_interval: Union[float, int] = 0.05,
95
95
  retry: int = 2,
96
96
  retry_interval: Union[float, int] = 1,
97
97
  max_output_line: int = 1000,
@@ -146,7 +146,7 @@ class SingleBaseRemoteCmdTask(CmdTask):
146
146
  self._remote_config = remote_config
147
147
 
148
148
  def copy(self) -> TSingleBaseRemoteCmdTask:
149
- return copy.deepcopy(self)
149
+ return super().copy()
150
150
 
151
151
  def inject_envs(self):
152
152
  super().inject_envs()
@@ -235,7 +235,7 @@ class BaseRemoteCmdTask(BaseTask):
235
235
  on_retry: Optional[OnRetry] = None,
236
236
  on_failed: Optional[OnFailed] = None,
237
237
  checkers: Iterable[AnyTask] = [],
238
- checking_interval: Union[float, int] = 0,
238
+ checking_interval: Union[float, int] = 0.05,
239
239
  retry: int = 2,
240
240
  retry_interval: Union[float, int] = 1,
241
241
  max_output_line: int = 1000,
@@ -37,8 +37,44 @@ from zrb.task_input.any_input import AnyInput
37
37
  @typechecked
38
38
  class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
39
39
  """
40
- Base class for all tasks.
41
- Every task definition should be extended from this class.
40
+ Base class for all Tasks.
41
+ Every Task definition should be extended from this class.
42
+
43
+ Task is the smallest Zrb automation unit.
44
+
45
+ You can configure a Task by using several interfaces:
46
+ - `inputs`: interfaces to read user input at the beginning of the execution.
47
+ - `envs`: interfaces to read and use OS Environment Variables.
48
+ - `env_files`: interfaces to read and use Environment Files.
49
+
50
+ Moreover, you can define Task dependencies by specifying it's `upstreams` or by using shift-right operator.
51
+
52
+ Every Task has its own life-cycle state:
53
+ - `Triggered`: The Task is triggered.
54
+ - `Waiting`: The Task is waiting for all it's upstreams to be ready.
55
+ - `Skipped`: The Task is not executed and will immediately enter the `Ready` state.
56
+ - `Started`: The Task execution is started.
57
+ - `Failed`: The Task execution is failed. It will enter the `Retry` state if the current attempt is less than the maximum attempt.
58
+ - `Retry`: The task will be restarted.
59
+ - `Ready`: The task is ready.
60
+
61
+ There are several configurations related to Task's life cycle:
62
+ - `retry`: How many retries should be attempted.
63
+ - `retry_interval`: How long to wait before the next retry attempt.
64
+ - `fallbacks`: What to do if the Task is failed and no retry attempt will be performed.
65
+ - `checkers`: How to determine if a Task is `Ready`.
66
+ - `checking_interval`: How long to wait before checking for Task's readiness.
67
+ - `run`: What a Task should do.
68
+ - `on_triggered`: What to do when a Task is `Triggered`.
69
+ - `on_waiting`: What to do when a Task is `Waiting`.
70
+ - `on_skipped`: What to do when a Task is `Skipped`.
71
+ - `on_started`: What to do when a Task is `Started`.
72
+ - `on_ready`: What to do when a Task is `Ready`.
73
+ - `on_retry`: What to do when a Task is `Retry`.
74
+ - `on_failed`: What to do when a Task is `Failed`.
75
+ - `should_execute`: Whether a Task should be `Started` or not (`Skipped`).
76
+
77
+ Finally, you can put related Tasks under the same `group`.
42
78
  """
43
79
 
44
80
  __running_tasks: List[AnyTask] = []
@@ -58,7 +94,7 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
58
94
  upstreams: Iterable[AnyTask] = [],
59
95
  fallbacks: Iterable[AnyTask] = [],
60
96
  checkers: Iterable[AnyTask] = [],
61
- checking_interval: Union[float, int] = 0,
97
+ checking_interval: Union[float, int] = 0.05,
62
98
  run: Optional[Callable[..., Any]] = None,
63
99
  on_triggered: Optional[OnTriggered] = None,
64
100
  on_waiting: Optional[OnWaiting] = None,
@@ -72,10 +108,10 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
72
108
  ):
73
109
  # init properties
74
110
  retry_interval = retry_interval if retry_interval >= 0 else 0
75
- checking_interval = checking_interval if checking_interval > 0 else 0.1
111
+ checking_interval = checking_interval if checking_interval > 0 else 0
76
112
  retry = retry if retry >= 0 else 0
77
113
  # init parent classes
78
- FinishTracker.__init__(self)
114
+ FinishTracker.__init__(self, checking_interval=checking_interval)
79
115
  Renderer.__init__(self)
80
116
  AttemptTracker.__init__(self, retry=retry)
81
117
  BaseTaskModel.__init__(
@@ -233,7 +269,7 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
233
269
  await run_async(self._on_retry, self)
234
270
 
235
271
  async def check(self) -> bool:
236
- return await self._is_done()
272
+ return await run_async(self._is_done)
237
273
 
238
274
  def inject_envs(self):
239
275
  super().inject_envs()
@@ -270,18 +306,17 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
270
306
  new_kwargs = self.get_input_map()
271
307
  # make sure args and kwargs['_args'] are the same
272
308
  self.log_info("Set run args")
273
- new_args = copy.deepcopy(args)
309
+ new_args = list(args)
274
310
  if len(args) == 0 and "_args" in kwargs:
275
311
  new_args = kwargs["_args"]
276
- new_kwargs["_args"] = new_args
277
- # inject self as input_map['_task']
278
- new_kwargs["_task"] = self
279
312
  self._set_args(new_args)
280
313
  self._set_kwargs(new_kwargs)
281
314
  # run the task
282
315
  coroutines = [
283
316
  asyncio.create_task(self._loop_check(show_done_info=show_done_info)),
284
- asyncio.create_task(self._run_all(*new_args, **new_kwargs)),
317
+ asyncio.create_task(
318
+ self._run_all(*new_args, _args=new_args, _task=self, **new_kwargs)
319
+ ),
285
320
  ]
286
321
  results = await asyncio.gather(*coroutines)
287
322
  result = results[-1]
@@ -315,7 +350,7 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
315
350
  if self.__is_check_triggered:
316
351
  self.log_debug("Waiting readiness flag to be set")
317
352
  while not self.__is_ready:
318
- await asyncio.sleep(0.1)
353
+ await asyncio.sleep(self._checking_interval)
319
354
  return True
320
355
  self.__is_check_triggered = True
321
356
  check_result = await self._check()
@@ -336,7 +371,7 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
336
371
  self.log_debug("Waiting execution to be started")
337
372
  while not self.__is_execution_started:
338
373
  # Don't start checking before the execution itself has been started
339
- await asyncio.sleep(0.05)
374
+ await asyncio.sleep(self._checking_interval / 2.0)
340
375
  check_coroutines: Iterable[asyncio.Task] = []
341
376
  for checker_task in self._get_checkers():
342
377
  checker_task._set_execution_id(self.get_execution_id())
@@ -456,7 +491,7 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
456
491
  # set current task local keyval
457
492
  await self._set_local_keyval(kwargs=kwargs, env_prefix=env_prefix)
458
493
  # get new_kwargs for upstream and checkers
459
- new_kwargs = copy.deepcopy(kwargs)
494
+ new_kwargs = {key: kwargs[key] for key in kwargs}
460
495
  new_kwargs.update(self.get_input_map())
461
496
  upstream_coroutines = []
462
497
  # set upstreams keyval
@@ -58,7 +58,7 @@ class BaseTaskModel(CommonTaskModel, PidModel, TimeTracker):
58
58
  upstreams: Iterable[AnyTask] = [],
59
59
  fallbacks: Iterable[AnyTask] = [],
60
60
  checkers: Iterable[AnyTask] = [],
61
- checking_interval: Union[int, float] = 0,
61
+ checking_interval: Union[int, float] = 0.05,
62
62
  run: Optional[Callable[..., Any]] = None,
63
63
  on_triggered: Optional[OnTriggered] = None,
64
64
  on_waiting: Optional[OnWaiting] = None,
@@ -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,
@@ -2,7 +2,7 @@ import asyncio
2
2
  import time
3
3
 
4
4
  from zrb.helper.typecheck import typechecked
5
- from zrb.helper.typing import Optional
5
+ from zrb.helper.typing import Optional, Union
6
6
 
7
7
  LOG_NAME_LENGTH = 20
8
8
 
@@ -51,9 +51,10 @@ class AttemptTracker:
51
51
 
52
52
  @typechecked
53
53
  class FinishTracker:
54
- def __init__(self):
54
+ def __init__(self, checking_interval: Union[float, int]):
55
55
  self.__execution_queue: Optional[asyncio.Queue] = None
56
56
  self.__counter = 0
57
+ self.__checking_interval = checking_interval
57
58
 
58
59
  async def _mark_awaited(self):
59
60
  if self.__execution_queue is None:
@@ -69,5 +70,5 @@ class FinishTracker:
69
70
 
70
71
  async def _is_done(self) -> bool:
71
72
  while self.__execution_queue is None:
72
- await asyncio.sleep(0.05)
73
+ await asyncio.sleep(self.__checking_interval / 2.0)
73
74
  return await self.__execution_queue.get()
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.1,
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
@@ -125,7 +125,7 @@ class CmdTask(BaseTask):
125
125
  on_retry: Optional[OnRetry] = None,
126
126
  on_failed: Optional[OnFailed] = None,
127
127
  checkers: Iterable[AnyTask] = [],
128
- checking_interval: Union[float, int] = 0,
128
+ checking_interval: Union[float, int] = 0.05,
129
129
  retry: int = 2,
130
130
  retry_interval: Union[float, int] = 1,
131
131
  max_output_line: int = 1000,
zrb/task/decorator.py CHANGED
@@ -43,9 +43,9 @@ 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: float = 0.1,
46
+ checking_interval: Union[float, int] = 0.05,
47
47
  retry: int = 2,
48
- retry_interval: float = 1,
48
+ retry_interval: Union[float, int] = 1,
49
49
  should_execute: Union[bool, str, Callable[..., bool]] = True,
50
50
  return_upstream_result: bool = False,
51
51
  runner: Optional[Runner] = None,
@@ -115,9 +115,9 @@ 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: float = 0.1,
118
+ checking_interval: Union[float, int] = 0.05,
119
119
  retry: int = 2,
120
- retry_interval: float = 1,
120
+ retry_interval: Union[float, int] = 1,
121
121
  max_output_line: int = 1000,
122
122
  max_error_line: int = 1000,
123
123
  preexec_fn: Optional[Callable[[], Any]] = os.setsid,
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.1,
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/looper.py CHANGED
@@ -32,8 +32,11 @@ class Looper:
32
32
  if result is not None:
33
33
  if not result:
34
34
  continue
35
+ while len(self._queue[identifier]) > 1000:
36
+ self._queue[identifier].pop(0)
35
37
  self._queue[identifier].append(result)
36
38
  except KeyboardInterrupt:
39
+ self.stop()
37
40
  break
38
41
 
39
42
 
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.1,
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.1,
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.1,
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
@@ -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,
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,39 +59,45 @@ 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]:
86
87
  task = self._get_task()
87
88
 
88
89
  async def fn() -> Any:
90
+ task.print_out_dark(f"Starting controller: {self._name}")
89
91
  task_fn = task.to_function(is_async=True)
90
92
  return await task_fn(*self._args, **self._kwargs)
91
93
 
92
94
  return fn
93
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
+
94
101
  def _get_task(self) -> AnyTask:
95
102
  actions = [action.copy() for action in self._actions]
96
103
  actions.insert(0, self._get_remonitor_task())
@@ -140,17 +147,18 @@ class Server(BaseTask):
140
147
  on_retry: Optional[OnRetry] = None,
141
148
  on_failed: Optional[OnFailed] = None,
142
149
  checkers: Iterable[AnyTask] = [],
143
- checking_interval: float = 0,
150
+ checking_interval: Union[int, float] = 0.05,
144
151
  retry: int = 0,
145
- retry_interval: float = 1,
152
+ retry_interval: Union[int, float] = 1,
146
153
  should_execute: Union[bool, str, Callable[..., bool]] = True,
147
154
  return_upstream_result: bool = False,
148
155
  ):
149
156
  inputs, envs, env_files = list(inputs), list(envs), list(env_files)
150
157
  for controller in controllers:
151
- inputs += controller.get_sub_inputs()
152
- envs += controller.get_sub_envs()
153
- 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()
154
162
  BaseTask.__init__(
155
163
  self,
156
164
  name=name,
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 Task has its own life-cycle state:
22
+ - `Triggered`: The Task is triggered.
23
+ - `Waiting`: The Task is waiting for all it's upstreams to be ready.
24
+ - `Skipped`: The Task is not executed and will immediately enter the `Ready` state.
25
+ - `Started`: The Task execution is started.
26
+ - `Failed`: The Task execution is failed. It will enter the `Retry` state if the current attempt is less than the maximum attempt.
27
+ - `Retry`: The task will be restarted.
28
+ - `Ready`: The task is ready.
29
+
30
+ There are several configurations related to Task's life cycle:
31
+ - `retry`: How many retries should be attempted.
32
+ - `retry_interval`: How long to wait before the next retry attempt.
33
+ - `fallbacks`: What to do if the Task is failed and no retry attempt will be performed.
34
+ - `checkers`: How to determine if a Task is `Ready`.
35
+ - `checking_interval`: How long to wait before checking for Task's readiness.
36
+ - `run`: What a Task should do.
37
+ - `on_triggered`: What to do when a Task is `Triggered`.
38
+ - `on_waiting`: What to do when a Task is `Waiting`.
39
+ - `on_skipped`: What to do when a Task is `Skipped`.
40
+ - `on_started`: What to do when a Task is `Started`.
41
+ - `on_ready`: What to do when a Task is `Ready`.
42
+ - `on_retry`: What to do when a Task is `Retry`.
43
+ - `on_failed`: What to do when a Task is `Failed`.
44
+ - `should_execute`: Whether a Task should be `Started` or not (`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] = 1,
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
  ):
@@ -128,7 +128,7 @@ class TimeWatcher(Watcher):
128
128
 
129
129
  def create_loop_inspector(self) -> Callable[..., Optional[bool]]:
130
130
  async def loop_inspect() -> bool:
131
- await asyncio.sleep(0.1)
131
+ await asyncio.sleep(self._checking_interval)
132
132
  label = f"Watching {self._rendered_schedule}"
133
133
  identifier = self.get_identifier()
134
134
  scheduled_time = self.__scheduled_times[identifier]
@@ -148,7 +148,7 @@ class TimeWatcher(Watcher):
148
148
  return cron.get_next(datetime.datetime)
149
149
 
150
150
  def _get_cron(self) -> Any:
151
- margin = datetime.timedelta(seconds=0.001)
151
+ margin = datetime.timedelta(seconds=self._checking_interval / 2.0)
152
152
  slightly_before_now = datetime.datetime.now() - margin
153
153
  cron = croniter.croniter(self._rendered_schedule, slightly_before_now)
154
154
  return cron
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.1,
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
 
zrb/task_env/env.py CHANGED
@@ -15,8 +15,9 @@ logger.debug(colored("Loading zrb.task_env.env", attrs=["dark"]))
15
15
  @typechecked
16
16
  class Env:
17
17
  """
18
- Env Represents an environment configuration for a task, encapsulating details such as environment name, OS-specific
19
- environment name, default values, and rendering behavior.
18
+ Env is an interface for a Task to read and use OS Environment Variable.
19
+
20
+ Env encapsulating details such as environment name, OS environment name, default values, and rendering behavior.
20
21
 
21
22
  Attributes:
22
23
  name (str): Environment name as recognized by the task.
@@ -77,7 +78,7 @@ class Env:
77
78
 
78
79
  def should_render(self) -> bool:
79
80
  """
80
- Determines whether the environment value should be rendered.
81
+ Retrieves whether the environment value should be rendered.
81
82
 
82
83
  Returns:
83
84
  bool: True if the environment value should be rendered, False otherwise.
zrb/task_env/env_file.py CHANGED
@@ -16,8 +16,9 @@ logger.debug(colored("Loading zrb.task_env.env_file", attrs=["dark"]))
16
16
  @typechecked
17
17
  class EnvFile:
18
18
  """
19
- Represents a handler for an environment file, facilitating the creation and management of environment variables
20
- (Env objects) based on the contents of the specified environment file.
19
+ Env is an interface for a Task to read and use OS Environment File.
20
+
21
+ Under the hood, EnvFile creates list of Env based on its `path` property.
21
22
 
22
23
  Attributes:
23
24
  path (str): The path to the environment file.
zrb/task_group/group.py CHANGED
@@ -15,7 +15,7 @@ TGroup = TypeVar("TGroup", bound="Group")
15
15
  @typechecked
16
16
  class Group:
17
17
  """
18
- Represents a group of tasks and subgroups, facilitating organization and hierarchy.
18
+ Group is an umbrella of several related Tasks or sub-groups, facilitating organization and hierarchy.
19
19
 
20
20
  This class allows the creation of a hierarchical structure by grouping tasks and
21
21
  other task groups together. It provides methods to add tasks, retrieve tasks,
@@ -11,10 +11,12 @@ logger.debug(colored("Loading zrb.task_input.any_input", attrs=["dark"]))
11
11
 
12
12
  class AnyInput(ABC):
13
13
  """
14
- Abstract base class representing a generalized input specification.
15
- This class serves as a template for creating various input types,
16
- providing a standardized interface for input handling and processing.
14
+ Abstraction for Zrb Input.
17
15
 
16
+ This class acts as a template for creating new Inputs.
17
+
18
+ To define a new Input type, you should extend this class and implement all its methods.
19
+ The easiest way to do so is by extending `Input`
18
20
  """
19
21
 
20
22
  @abstractmethod
@@ -14,9 +14,9 @@ logger.debug(colored("Loading zrb.task_input.base_input", attrs=["dark"]))
14
14
  @typechecked
15
15
  class BaseInput(AnyInput):
16
16
  """
17
- A concrete implementation of the AnyInput abstract base class, representing a specific type of task input.
18
- This class allows for the creation of interactive and configurable inputs for tasks, with various attributes
19
- to customize its behavior and appearance.
17
+ Base class for all Input.
18
+
19
+ Input is an interface for a Task to read user input at the beginning of the execution.
20
20
 
21
21
  Attributes:
22
22
  name (str): The name of the input, used as a unique identifier.
@@ -9,7 +9,7 @@ logger.debug(colored("Loading zrb.task_input.task_input", attrs=["dark"]))
9
9
 
10
10
  class Input(BaseInput):
11
11
  """
12
- Alias for BaseInput
12
+ Input is an interface for a Task to read user input at the beginning of the execution.
13
13
 
14
14
  Attributes:
15
15
  name (str): The name of the input, used as a unique identifier.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zrb
3
- Version: 0.17.0
3
+ Version: 0.17.5
4
4
  Summary: A Framework to Enhance Your Workflow
5
5
  Home-page: https://github.com/state-alchemists/zrb
6
6
  License: AGPL-3.0-or-later
@@ -33,7 +33,7 @@ Description-Content-Type: text/markdown
33
33
  ![](https://raw.githubusercontent.com/state-alchemists/zrb/main/_images/zrb/android-chrome-192x192.png)
34
34
 
35
35
 
36
- [🫰 Installation](https://github.com/state-alchemists/zrb/blob/main/docs/installation.md) | [📖 Documentation](https://github.com/state-alchemists/zrb/blob/main/docs/README.md) | [🏁 Getting Started](https://github.com/state-alchemists/zrb/blob/main/docs/getting-started.md) | [💃 Common Mistakes](https://github.com/state-alchemists/zrb/blob/main/docs/oops-i-did-it-again/README.md) | [❓ FAQ](https://github.com/state-alchemists/zrb/blob/main/docs/faq/README.md)
36
+ [🫰 Installation](https://github.com/state-alchemists/zrb/blob/main/docs/installation.md) | [📖 Documentation](https://github.com/state-alchemists/zrb/blob/main/docs/README.md) | [🏁 Getting Started](https://github.com/state-alchemists/zrb/blob/main/docs/getting-started.md) | [💃 Common Mistakes](https://github.com/state-alchemists/zrb/blob/main/docs/common-mistakes/README.md) | [❓ FAQ](https://github.com/state-alchemists/zrb/blob/main/docs/faq/README.md)
37
37
 
38
38
 
39
39
  # 🤖 Zrb: A Framework to Enhance Your Workflow
@@ -43,11 +43,12 @@ Zrb is a [CLI-based](https://en.wikipedia.org/wiki/Command-line_interface) autom
43
43
  - __Automate__ day-to-day tasks.
44
44
  - __Generate__ projects or applications.
45
45
  - __Prepare__, __run__, and __deploy__ your applications with a single command.
46
- - Etc.
46
+ - Deliver __faster__ with __fewer human errors__.
47
47
 
48
- You can also write custom task definitions in [Python](https://www.python.org/), enhancing Zrb's base capabilities. Defining your tasks in Zrb gives you several advantages because:
48
+ Zrb allows you to write custom task definitions in [Python](https://www.python.org/), further enhancing Zrb's capabilities. Defining your tasks in Zrb gives you several advantages because:
49
49
 
50
- - Every task has a __retry mechanism__.
50
+ - Every Zrb Task has a __retry mechanism__.
51
+ - Every Zrb Task is __configurable__ via __environment variables__ or __user inputs__.
51
52
  - Zrb handles your __task dependencies__ automatically.
52
53
  - Zrb runs your task dependencies __concurrently__.
53
54
 
@@ -85,7 +86,9 @@ Download Datasets ──┘
85
86
  ⬇️
86
87
  ```
87
88
 
88
- You can create a file named `zrb_init.py` and define the tasks as follows:
89
+ Zrb Task is the smallest automation unit. You can configure a Zrb Task using user input (`inputs`) or environment variables (`envs` or `env_files`). Every Zrb Task has a configurable `retry` mechanism. Moreover, you can also define Zrb Task dependencies using the shift right operator `>>` or `upstreams` parameter.
90
+
91
+ You can create a file named `zrb_init.py` and define your Zrb Tasks as follows:
89
92
 
90
93
  ```python
91
94
  # File name: zrb_init.py
@@ -129,7 +132,7 @@ def show_stats(*args, **kwargs):
129
132
  # Define dependencies: `show_stat` depends on both, `download_dataset` and `install_pandas`
130
133
  Parallel(download_dataset, install_pandas) >> show_stats
131
134
 
132
- # Register the tasks so that they are accessbie from the CLI
135
+ # Register the tasks so that they are accessble from the CLI
133
136
  runner.register(install_pandas, download_dataset, show_stats)
134
137
  ```
135
138
 
@@ -286,15 +289,15 @@ Visit [our tutorials](https://github.com/state-alchemists/zrb/blob/main/docs/tut
286
289
  - [🫰 Installation](https://github.com/state-alchemists/zrb/blob/main/docs/installation.md)
287
290
  - [🏁 Getting Started](https://github.com/state-alchemists/zrb/blob/main/docs/getting-started.md)
288
291
  - [📖 Documentation](https://github.com/state-alchemists/zrb/blob/main/docs/README.md)
289
- - [💃 Common Mistakes](https://github.com/state-alchemists/zrb/blob/main/docs/oops-i-did-it-again/README.md)
292
+ - [💃 Common Mistakes](https://github.com/state-alchemists/zrb/blob/main/docs/common-mistakes/README.md)
290
293
  - [❓ FAQ](https://github.com/state-alchemists/zrb/blob/main/docs/faq/README.md)
291
294
 
292
295
  # 🐞 Bug Report + Feature Request
293
296
 
294
- You can submit bug report and feature request by creating a new [issue](https://github.com/state-alchemists/zrb/issues) on Zrb Github Repositories. When reporting a bug or requesting a feature, please be sure to:
297
+ You can submit bug reports and feature requests by creating a new [issue](https://github.com/state-alchemists/zrb/issues) on Zrb's GitHub Repositories. When reporting a bug or requesting a feature, please be sure to:
295
298
 
296
299
  - Include the version of Zrb you are using (i.e., `zrb version`)
297
- - Tell us what you have try
300
+ - Tell us what you have tried
298
301
  - Tell us what you expect
299
302
  - Tell us what you get
300
303
 
@@ -326,7 +329,7 @@ We are thankful for the following libraries and services. They accelerate Zrb de
326
329
  - [Jsons](https://pypi.org.project/jsons/): Parse JSON. This package should be part of the standard library.
327
330
  - [Libcst](https://pypi.org/project/libcst/): Turn Python code into a Concrete Syntax Tree.
328
331
  - [Croniter](https://pypi.org/project/croniter/): Parse cron pattern.
329
- - [Flit](https://pypi.org/project/flit), [Twine](https://pypi.org/project/twine/), and many more. See the complete list of Zrb's requirements.txt
332
+ - [Poetry](https://pypi.org/project/poetry), [flake8](https://pypi.org/project/flake8/), [black](https://pypi.org/project/black), [isort](https://pypi.org/project/isort), and many more. See the complete list of Zrb's [pyproject.toml](https://github.com/state-alchemists/zrb/blob/main/pyproject.toml)
330
333
  - Services
331
334
  - [asciiflow.com](https://asciiflow.com/): Creates beautiful ASCII-based diagrams.
332
335
  - [emojipedia.org](https://emojipedia.org/): Find emoji.
@@ -27,7 +27,7 @@ zrb/builtin/devtool/install/gvm/download.sh,sha256=Z4IDsTS4gOeWiezhI-TrRv2nFgMsl
27
27
  zrb/builtin/devtool/install/gvm/finalize.sh,sha256=Han_IDq5XwxNsxyAVc99PoW3fdjTnYtv6rsr5KRhtEE,538
28
28
  zrb/builtin/devtool/install/gvm/gvm.py,sha256=jD5HzHA4eXHwFeSzKUVAp8PMXiibPNF_Rwq01NaEZIo,1693
29
29
  zrb/builtin/devtool/install/gvm/resource/config.sh,sha256=M_r6XjtoYZjx8rJaT3FIwVl3HUd7lJF5_KqUSEJQeo4,253
30
- zrb/builtin/devtool/install/helix/helix.py,sha256=W0Xm_eq0wIHcoq0CzG9WrQIH9tPByT-AZ-zi4Z72-U0,2098
30
+ zrb/builtin/devtool/install/helix/helix.py,sha256=XQzTbokyAXy9-UMBPp8woIOPNFF0vxWcrg0fr4mYj4E,2010
31
31
  zrb/builtin/devtool/install/helix/install-language-server.sh,sha256=ZkV_ARwhTnLjjbAhJe8Pvp1hyRYVn176DYwg7ObkQ1w,1040
32
32
  zrb/builtin/devtool/install/helix/install.sh,sha256=Dsg65aEnpU8YnlvHwiKoxRpj8Jo8j3mejB4bTi2eeKo,1375
33
33
  zrb/builtin/devtool/install/helix/resource/config.toml,sha256=35IwzDzXGfSnUH3O3nyd2IzDVOWyKqj6Kb3QuympXCE,305
@@ -965,7 +965,7 @@ zrb/builtin/project/add/fastapp/app/template/src/kebab-zrb-app-name/src/componen
965
965
  zrb/builtin/project/add/fastapp/app/template/src/kebab-zrb-app-name/src/component/repo/search_filter.py,sha256=lYquQLR4_qr43AlKGNBBNzcGOEOASQHteuLdiewOBoE,147
966
966
  zrb/builtin/project/add/fastapp/app/template/src/kebab-zrb-app-name/src/component/rpc/__init__.py,sha256=i5QOPDiwAwp0EskPSVwJDuVH6pq_DWRi3XN1hMFFw4A,244
967
967
  zrb/builtin/project/add/fastapp/app/template/src/kebab-zrb-app-name/src/component/rpc/messagebus/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
968
- zrb/builtin/project/add/fastapp/app/template/src/kebab-zrb-app-name/src/component/rpc/messagebus/caller.py,sha256=4C6bDNUm-cL-G_5R-GdaSCXht_bJM5q8lelnVftCdaQ,3201
968
+ zrb/builtin/project/add/fastapp/app/template/src/kebab-zrb-app-name/src/component/rpc/messagebus/caller.py,sha256=P90X3d6et457EwD4NohOsPNIcz9lFQmzqTbZubmR9aA,3202
969
969
  zrb/builtin/project/add/fastapp/app/template/src/kebab-zrb-app-name/src/component/rpc/messagebus/server.py,sha256=KFVErPpkRDetDokMAR16thaKtOTqk_xRp4lmM1iQNFo,2197
970
970
  zrb/builtin/project/add/fastapp/app/template/src/kebab-zrb-app-name/src/component/rpc/rpc.py,sha256=obJ8ksA9Io5nRpGoTQE506hj82dPwRZIUWTq2JEOLRo,1625
971
971
  zrb/builtin/project/add/fastapp/app/template/src/kebab-zrb-app-name/src/component/schema/__init__.py,sha256=g1CrqtQukcTOnNaKFLj6j-iVaNQ_cvpmdzROtbeteck,127
@@ -1355,46 +1355,46 @@ zrb/shell-scripts/notify.ps1,sha256=6_xPoIwuxARpYljcjVV-iRJS3gJqGfx-B6kj719cJ9o,
1355
1355
  zrb/shell-scripts/rsync-util.sh,sha256=QzdhSBvUNMxB4U2B4m0Dxg9czGckRjB7Vk4A1ObG0-k,353
1356
1356
  zrb/shell-scripts/ssh-util.sh,sha256=9lXDzw6oO8HuA4vdbfps_uQMMwKyNYX9fZkZgpK52g8,401
1357
1357
  zrb/task/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1358
- zrb/task/any_task.py,sha256=1jc0qVZs1yIMfGuInktgH7EqDbI8IqmsaSlH5VijThc,39435
1358
+ zrb/task/any_task.py,sha256=hrgsYikSe_C6JDSWeHNGCCvefNrkVioJWUunrqSFGLE,39199
1359
1359
  zrb/task/any_task_event_handler.py,sha256=AjTC6lIcprutRusNBGl83EifQe4TbZzxdlVIR4ndWN4,524
1360
- zrb/task/base_remote_cmd_task.py,sha256=tZi3jODMXfTkDAmWiFR2YdA-b4-TDTP1uLtO0ulmM34,10101
1360
+ zrb/task/base_remote_cmd_task.py,sha256=P-F4y8_tq4d6UFPy8lXoRq8DPFjRr4FQQcTaImaqa3I,10102
1361
1361
  zrb/task/base_task/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1362
- zrb/task/base_task/base_task.py,sha256=qnQSG-nAppmm38JY11wC03-0mZSOjjk-XJ62leco2rw,20354
1362
+ zrb/task/base_task/base_task.py,sha256=BsJ6iqi05Y-3YPuGdpGcogPN9wqScSxiWrCGB_LtWGM,22325
1363
1363
  zrb/task/base_task/component/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1364
- zrb/task/base_task/component/base_task_model.py,sha256=YlEuvYSzlrK83Kds07bu2drKSw3rKyl7qIR1qwIPNZk,10376
1365
- zrb/task/base_task/component/common_task_model.py,sha256=JoAAf8EvuPx31fCK_TBxX_TBeHZrZyFDjLeK8ts0w3A,12285
1364
+ zrb/task/base_task/component/base_task_model.py,sha256=i6TrtTusZ71ZnOnx8yM0aJl8uF6R1hKdAf62bEdpdCs,10379
1365
+ zrb/task/base_task/component/common_task_model.py,sha256=h81BGqplsaWJCQl0Zigl24LLkCUKPEfJikSbITREQnM,12288
1366
1366
  zrb/task/base_task/component/pid_model.py,sha256=RjJIqOpavucDssnd3q3gT4q8QnP8I9SUdlv1b9pR7kU,292
1367
1367
  zrb/task/base_task/component/renderer.py,sha256=9wP2IW811Ta81IoPWmeQ7yVc7eG-uaSnOVbEyeaOIuk,4439
1368
- zrb/task/base_task/component/trackers.py,sha256=gM9eOukMh6kvNJnRsHscQ_JN--Haa2YA4bIufqh8upE,1950
1369
- zrb/task/checker.py,sha256=fn6gmcCUHsar458NQ4Af4-kt2skcyibd0ewsPTRd5-w,3383
1370
- zrb/task/cmd_task.py,sha256=_wjF9MbKGA0EAr_df47AMC0lROCiaJTUyxT-AFQKKJo,14181
1371
- zrb/task/decorator.py,sha256=j62l7ITIRZtk_qE97d4naVl9gbbMoi2eEXOPOmYdF8M,3039
1372
- zrb/task/docker_compose_task.py,sha256=fnFEOAS9yEi2ve7pSzN9guXOVeYt8HYTdQvtjk-yoRQ,14886
1373
- zrb/task/flow_task.py,sha256=QIIZgq9C7e-kvTRJ0Y1Slb5AQyy15N9H4NZxdFR3FI8,4867
1374
- zrb/task/http_checker.py,sha256=DRIwXjC8StucHo2nP1duM3Ime3zzw1BQH8ib2bFZVmA,5692
1375
- zrb/task/looper.py,sha256=aU7hwXLlfZRhH4rlusIwsusz5fHEhaYfwEpRUVcHx4o,1284
1376
- zrb/task/notifier.py,sha256=19E4EcFgFZ0thU9p2P1dGUYR04721pa0K3lqsj6a4Xc,6217
1368
+ zrb/task/base_task/component/trackers.py,sha256=c5xhZ6agICxKPI5Va1sn66_9OqC92ebF5CNhcwVUNUE,2074
1369
+ zrb/task/checker.py,sha256=raYNBHgeyEqkyfBRsPPgSV7ukEfMlJOCUn97WQNl6mU,3384
1370
+ zrb/task/cmd_task.py,sha256=Bfu29x5Cy10gCazIItCEOzrafU7r7z5WlWS4_wD7Znk,14184
1371
+ zrb/task/decorator.py,sha256=stxrl6aXbuUDK83lVf8m8uni3Ii6egLl0TCR0vxslUQ,3064
1372
+ zrb/task/docker_compose_task.py,sha256=hUKF7W3GwxFuEWmlPPFxa7h8npEnig2sm7KjlidHFBI,14911
1373
+ zrb/task/flow_task.py,sha256=QBOoyIrqc6ToSf3RF8xu8h4yxCWCerUAu2Ba0GxAqgg,5147
1374
+ zrb/task/http_checker.py,sha256=y0cWa2t4YtGQr6FWno5sZ6Ej9gQiLDF-Z1kLU1rijRw,5693
1375
+ zrb/task/looper.py,sha256=0eM3wEIC_RbThg60MRbK4Az16vt81O5p12cORAYTfnI,1430
1376
+ zrb/task/notifier.py,sha256=GbvnndQ-3-1vfGpOIIQ0IMS1ddqDxzgTSYThs_zg1xU,6220
1377
1377
  zrb/task/parallel.py,sha256=-coMuiFlS29GpBgW6plPVaCLesgzzD0bYib29OvhXFg,1193
1378
- zrb/task/path_checker.py,sha256=yvMgGlmMQhlaX3Wlq5yI7-nzEpyCFoXHEOFOdlfI6-o,4673
1379
- zrb/task/path_watcher.py,sha256=UGaGYzWExoVQDK6smXEKsd0leO3FZOQYHnpHgZ9hiZU,7420
1380
- zrb/task/port_checker.py,sha256=IoVIP0QjxKz2SLgnK2GIaxn4WASk6ZKf4cQlKog0Fw8,4592
1381
- zrb/task/recurring_task.py,sha256=FNxV7n4h9AzUCU8oKXwAS_A9j1newS-esWjmMsC33vE,7430
1382
- zrb/task/remote_cmd_task.py,sha256=rmLB5uCcbbfZBy8-nAZI8mgnNd-J2d4SBemLEDwSlV4,3951
1383
- zrb/task/resource_maker.py,sha256=jQSO7PVIuTZi__JcrpRC4Ni_xmuJszJiMAxH_qfJPhs,7644
1384
- zrb/task/rsync_task.py,sha256=bgCeZQTG-4isvjZGGs_05oOEkkwGc930NTyuUYUm_cg,4187
1385
- zrb/task/server.py,sha256=CdzBkdWZvX1PTvloDrOXtCbAbMeK4KK75C33N_Wz3Sc,6408
1386
- zrb/task/task.py,sha256=dHv4cmnd0QFPT9PwrfmHpxTaXj86mm8xf7_jAj_engI,329
1387
- zrb/task/time_watcher.py,sha256=xx82w8ygpL-6pUbeuWjsxSVLSZhkWVWHsAoZUXPV7Jk,5075
1388
- zrb/task/watcher.py,sha256=m72YhUKtQsE4mZSm1y2MKiSdfj6HZo3rj9f3nQLU7Oo,3252
1378
+ zrb/task/path_checker.py,sha256=SHpwtlS5YbO1jFGNnP7SuswkuDNHMOy52Fr4IzdIG98,4674
1379
+ zrb/task/path_watcher.py,sha256=x2SdgdgaZm2wiAyfBJoMVa625ufHtf8oHKmKhhEqAQk,7421
1380
+ zrb/task/port_checker.py,sha256=Za02E0xzR7xvHEujxuPszaydJCpwl_hJa1S9U4j25XQ,4593
1381
+ zrb/task/recurring_task.py,sha256=6JN7YkAY-NQ_zU2EAzqaQAxvfkGssbxvzNtL_foNA3Q,7434
1382
+ zrb/task/remote_cmd_task.py,sha256=DzAt5Vq8YekqAfu9PflGP0XcdLWX_gLKI64dGVgtL6s,3954
1383
+ zrb/task/resource_maker.py,sha256=wvq8Gv11epA_OYeLY071hIfFYkJ0rMcmcnXUAbd3TRg,7645
1384
+ zrb/task/rsync_task.py,sha256=kusVfttM1H0OEpNpLuU1I25MqChYaJeZDNZiXWtuVbA,4190
1385
+ zrb/task/server.py,sha256=q0RS-H1FWZaeT-mHLsbRx_D2bU-CZP0W3VVIx2KkaL8,6721
1386
+ zrb/task/task.py,sha256=DOomim37qnVVE4neWCJhHvAIkAN_h3QGFzvH-BrGTEI,2240
1387
+ zrb/task/time_watcher.py,sha256=D3_rr5Dlaw8xOg9iZMAGzPiEC2VDvIIvNTluQsZeBHo,5122
1388
+ zrb/task/watcher.py,sha256=7yDAS7kxST4Gd_PLyg_7XU6uXed5qbojE-B5X5z9EY4,3344
1389
1389
  zrb/task/wiki_task.py,sha256=Mcugk_6Pd7pzubi2ZP4eegJs8e9niYKh-9mCrNHXE_g,4330
1390
1390
  zrb/task_env/constant.py,sha256=ySdHv2dawWE-UoeBHl8FEOmrBl4vfkRI67TIBdkK6l8,220
1391
- zrb/task_env/env.py,sha256=C9IzavEtWlpap4C92XONi1ID-RK9gDDLQKl5rYGBsyc,5195
1392
- zrb/task_env/env_file.py,sha256=NkPOjxi8lrvBjB13kc9LU3ODvjGsdw3sSGOyC9-GnzE,3087
1391
+ zrb/task_env/env.py,sha256=WoxPlUgD33p6-y2EJU0oXAXBAnPL65k55hAbv6kO-Zs,5206
1392
+ zrb/task_env/env_file.py,sha256=D5cHJBY1DFcBa3KI3ujTe7Gxk7y_WB1zXCzkZNlDexE,3047
1393
1393
  zrb/task_group/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1394
- zrb/task_group/group.py,sha256=RmYpnh1UGiCi0vKYoqLwlCmJzC5-VKkMgDrVR2y3wsk,6091
1394
+ zrb/task_group/group.py,sha256=uF3tyLABTyyBNyE9FUCfJeYSDnaLFQCis3tuEn5FYho,6109
1395
1395
  zrb/task_input/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1396
- zrb/task_input/any_input.py,sha256=cZ2v8qjJ3B-L-jZgAz5EhpZii5sMoepwan5jAbJE0cM,2067
1397
- zrb/task_input/base_input.py,sha256=qNifwst3ArwAbQ9XBDoyo6whMeS2IP-VZ3Pw0vf8efE,5643
1396
+ zrb/task_input/any_input.py,sha256=-uOq1ONXdhW20FRkC3pzqX081tbfmWjvCc_-d-gCNGY,2087
1397
+ zrb/task_input/base_input.py,sha256=kxt2184s8ppC2dsa69J2uLUOUqsdCmXEJiNUIJxKcuQ,5495
1398
1398
  zrb/task_input/bool_input.py,sha256=xlWjqPvHi52TLiFP13wB1SayJlbzmxv31AS-sZJ0jw4,4040
1399
1399
  zrb/task_input/choice_input.py,sha256=GU28ATSayZsmqCW-euAPoWyA-Kh6d5Wf6FwWLXffk5w,4290
1400
1400
  zrb/task_input/constant.py,sha256=VEsnrI0BDdCJ1Z58EJgxXUhZBe5CA8TfURo0cNu5CaQ,200
@@ -1402,9 +1402,9 @@ zrb/task_input/float_input.py,sha256=rtaowHp8RpiQS8a24ULgtydRu6wkYT1Q7Q_IRcCPD7s
1402
1402
  zrb/task_input/int_input.py,sha256=d2fXcm5fCo09472eMAm6PdzLQD82ZBV9ARq5CjKepAo,4198
1403
1403
  zrb/task_input/password_input.py,sha256=g_g8ZWAzDaHx4h2EHY3UCGvTigC6esAUBzXU0T9nDUk,4192
1404
1404
  zrb/task_input/str_input.py,sha256=BNflOhrJvST9bWK0rGdCi7C7y-QDvHj9ISQMRmujIWU,4200
1405
- zrb/task_input/task_input.py,sha256=x1sGHsoSYAYMdQBrCLmcvZa_ZmGggMPj3goAQzewUKI,2181
1406
- zrb-0.17.0.dist-info/LICENSE,sha256=WfnGCl8G60EYOPAEkuc8C9m9pdXWDe08NsKj3TBbxsM,728
1407
- zrb-0.17.0.dist-info/METADATA,sha256=GEA_jDoWaQ2X5kQMiLZFHMRqnYczd1HK-S5g8QwPYfI,16460
1408
- zrb-0.17.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
1409
- zrb-0.17.0.dist-info/entry_points.txt,sha256=xTgXc1kBKYhJHEujdaSPHUcJT3-hbyP1mLgwkv-5sSk,40
1410
- zrb-0.17.0.dist-info/RECORD,,
1405
+ zrb/task_input/task_input.py,sha256=DcHgKie5Oo1sUxj41t1ZQjCIK1aAfTgGzaKr7_ap7ZI,2248
1406
+ zrb-0.17.5.dist-info/LICENSE,sha256=WfnGCl8G60EYOPAEkuc8C9m9pdXWDe08NsKj3TBbxsM,728
1407
+ zrb-0.17.5.dist-info/METADATA,sha256=nYeD36gTHtcdrptMrQY6-nc2E7Nd0IRIe90cNhtNcmg,17076
1408
+ zrb-0.17.5.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
1409
+ zrb-0.17.5.dist-info/entry_points.txt,sha256=xTgXc1kBKYhJHEujdaSPHUcJT3-hbyP1mLgwkv-5sSk,40
1410
+ zrb-0.17.5.dist-info/RECORD,,
File without changes
File without changes