zrb 0.15.1__py3-none-any.whl → 0.17.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.
zrb/__init__.py CHANGED
@@ -32,8 +32,10 @@ from zrb.task.resource_maker import (
32
32
  get_default_resource_skip_parsing,
33
33
  )
34
34
  from zrb.task.rsync_task import RsyncTask
35
+ from zrb.task.server import Controller, Server
35
36
  from zrb.task.task import Task
36
37
  from zrb.task.time_watcher import TimeWatcher
38
+ from zrb.task.watcher import Watcher
37
39
  from zrb.task.wiki_task import create_wiki_tasks
38
40
  from zrb.task_env.env import Env
39
41
  from zrb.task_env.env_file import EnvFile
@@ -74,6 +76,9 @@ assert HTTPChecker
74
76
  assert PortChecker
75
77
  assert PathChecker
76
78
  assert PathWatcher
79
+ assert Controller
80
+ assert Server
81
+ assert Watcher
77
82
  assert TimeWatcher
78
83
  assert ResourceMaker
79
84
  assert FlowTask
zrb/action/runner.py CHANGED
@@ -110,7 +110,12 @@ class Runner:
110
110
 
111
111
  def __get_wrapped_task_function(self, task: AnyTask) -> Callable[..., Any]:
112
112
  def wrapped_function(*args: Any, **kwargs: Any) -> Any:
113
- function = task.to_function(env_prefix=self.__env_prefix, raise_error=True)
113
+ function = task.to_function(
114
+ env_prefix=self.__env_prefix,
115
+ raise_error=True,
116
+ should_clear_xcom=True,
117
+ should_stop_looper=True,
118
+ )
114
119
  try:
115
120
  function(*args, **kwargs)
116
121
  except Exception:
@@ -1,6 +1,4 @@
1
- from zrb.helper.typing import Any
2
1
  from zrb.task.cmd_task import CmdTask
3
- from zrb.task.decorator import python_task
4
2
  from zrb.task.notifier import Notifier
5
3
  from zrb.task.task import Task
6
4
  from zrb.task_input.str_input import StrInput
@@ -8,8 +6,6 @@ from zrb.task_input.str_input import StrInput
8
6
 
9
7
  def create_recurring_action(
10
8
  notif_title: str,
11
- trigger_caption: str,
12
- trigger_xcom_key: str,
13
9
  default_message: str = "👋",
14
10
  ) -> Task:
15
11
  # define inputs
@@ -23,16 +19,11 @@ def create_recurring_action(
23
19
  default="",
24
20
  prompt="Command to be executed",
25
21
  )
26
- # define tasks
27
- show_trigger_info = _create_show_trigger_info(
28
- trigger_caption=trigger_caption, trigger_xcom_key=trigger_xcom_key
29
- )
30
22
  run_command = CmdTask(
31
23
  name="run-command",
32
24
  icon="⚙️",
33
25
  color="blue",
34
26
  inputs=[command_input],
35
- upstreams=[show_trigger_info],
36
27
  should_execute='{{ input.command != "" }}',
37
28
  cmd="{{ input.command }}",
38
29
  )
@@ -43,7 +34,6 @@ def create_recurring_action(
43
34
  inputs=[message_input],
44
35
  title=notif_title,
45
36
  message="{{ input.message }}",
46
- upstreams=[show_trigger_info],
47
37
  should_execute='{{ input.message != "" }}',
48
38
  )
49
39
  # return aggregator task
@@ -53,16 +43,3 @@ def create_recurring_action(
53
43
  upstreams=[run_command, notify],
54
44
  retry=0,
55
45
  )
56
-
57
-
58
- def _create_show_trigger_info(trigger_caption: str, trigger_xcom_key: str) -> Task:
59
- @python_task(
60
- name="show-trigger-info",
61
- icon="🔍",
62
- color="magenta",
63
- )
64
- def show_trigger_info(*args: Any, **kwargs: Any):
65
- task: Task = kwargs.get("_task")
66
- task.print_out(f"{trigger_caption}: {task.get_xcom(trigger_xcom_key)}")
67
-
68
- return show_trigger_info
@@ -3,6 +3,7 @@ theme = "gruvbox_transparent"
3
3
  [editor]
4
4
  line-number = "relative"
5
5
  mouse = true
6
+ bufferline = "always"
6
7
 
7
8
  [editor.cursor-shape]
8
9
  insert = "bar"
@@ -16,4 +17,4 @@ enable = true
16
17
  hidden = false
17
18
  git-ignore = false
18
19
  git-global = false
19
- git-exclude = false
20
+ git-exclude = false
@@ -16,7 +16,7 @@ get_process_pid_by_name = CmdTask(
16
16
  prompt="Process name to be checked",
17
17
  )
18
18
  ],
19
- cmd="pgrep {{ input.name }}",
19
+ cmd='pgrep "{{ input.name }}" || exit 0',
20
20
  checking_interval=3,
21
21
  )
22
22
  runner.register(get_process_pid_by_name)
zrb/builtin/schedule.py CHANGED
@@ -1,10 +1,10 @@
1
1
  from zrb.builtin._helper.reccuring_action import create_recurring_action
2
2
  from zrb.runner import runner
3
- from zrb.task.recurring_task import RecurringTask
3
+ from zrb.task.server import Controller, Server
4
4
  from zrb.task.time_watcher import TimeWatcher
5
5
  from zrb.task_input.str_input import StrInput
6
6
 
7
- schedule = RecurringTask(
7
+ schedule = Server(
8
8
  name="schedule",
9
9
  icon="📅",
10
10
  color="yellow",
@@ -17,15 +17,19 @@ schedule = RecurringTask(
17
17
  description="Schedule cron pattern to show the message",
18
18
  ),
19
19
  ],
20
- triggers=[
21
- TimeWatcher(
22
- name="watch-time", color="cyan", icon="⏰", schedule="{{input.schedule}}"
20
+ controllers=[
21
+ Controller(
22
+ name="periodic",
23
+ trigger=TimeWatcher(
24
+ name="watch-time",
25
+ color="cyan",
26
+ icon="⏰",
27
+ schedule="{{input.schedule}}",
28
+ ),
29
+ action=create_recurring_action(
30
+ notif_title="Schedule",
31
+ ),
23
32
  )
24
33
  ],
25
- task=create_recurring_action(
26
- notif_title="Schedule",
27
- trigger_caption="Schedule",
28
- trigger_xcom_key="watch-time.scheduled-time",
29
- ),
30
34
  )
31
35
  runner.register(schedule)
@@ -1,10 +1,10 @@
1
1
  from zrb.builtin._helper.reccuring_action import create_recurring_action
2
2
  from zrb.runner import runner
3
3
  from zrb.task.path_watcher import PathWatcher
4
- from zrb.task.recurring_task import RecurringTask
4
+ from zrb.task.server import Controller, Server
5
5
  from zrb.task_input.str_input import StrInput
6
6
 
7
- watch_changes = RecurringTask(
7
+ watch_changes = Server(
8
8
  name="watch-changes",
9
9
  icon="🕵️",
10
10
  color="yellow",
@@ -23,19 +23,20 @@ watch_changes = RecurringTask(
23
23
  description="Ignored file pattern",
24
24
  ),
25
25
  ],
26
- triggers=[
27
- PathWatcher(
28
- name="watch-path",
29
- color="cyan",
30
- icon="👀",
31
- path="{{input.pattern}}",
32
- ignored_path="{{input.ignored_pattern}}",
26
+ controllers=[
27
+ Controller(
28
+ name="watch",
29
+ trigger=PathWatcher(
30
+ name="watch-path",
31
+ color="cyan",
32
+ icon="👀",
33
+ path="{{input.pattern}}",
34
+ ignored_path="{{input.ignored_pattern}}",
35
+ ),
36
+ action=create_recurring_action(
37
+ notif_title="Watch",
38
+ ),
33
39
  )
34
40
  ],
35
- task=create_recurring_action(
36
- notif_title="Watch",
37
- trigger_caption="File changes",
38
- trigger_xcom_key="watch-path.file",
39
- ),
40
41
  )
41
42
  runner.register(watch_changes)
zrb/helper/callable.py CHANGED
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import inspect
2
3
  from typing import Any, Callable
3
4
 
@@ -10,4 +11,6 @@ logger.debug(colored("Loading zrb.helper.callable", attrs=["dark"]))
10
11
  async def run_async(fn: Callable, *args: Any, **kwargs: Any) -> Any:
11
12
  if inspect.iscoroutinefunction(fn):
12
13
  return await fn(*args, **kwargs)
13
- return fn(*args, **kwargs)
14
+ coro = asyncio.to_thread(fn, *args, **kwargs)
15
+ task = asyncio.create_task(coro)
16
+ return await task
zrb/task/any_task.py CHANGED
@@ -308,6 +308,7 @@ class AnyTask(ABC):
308
308
  is_async: bool = False,
309
309
  show_done_info: bool = True,
310
310
  should_clear_xcom: bool = False,
311
+ should_stop_looper: bool = False,
311
312
  ) -> Callable[..., Any]:
312
313
  """
313
314
  Converts the current task into a callable function.
@@ -26,6 +26,7 @@ from zrb.task.any_task_event_handler import (
26
26
  from zrb.task.base_task.component.base_task_model import BaseTaskModel
27
27
  from zrb.task.base_task.component.renderer import Renderer
28
28
  from zrb.task.base_task.component.trackers import AttemptTracker, FinishTracker
29
+ from zrb.task.looper import looper
29
30
  from zrb.task.parallel import AnyParallel
30
31
  from zrb.task_env.env import Env, PrivateEnv
31
32
  from zrb.task_env.env_file import EnvFile
@@ -140,7 +141,7 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
140
141
  xcom_dir = self.__get_xcom_dir()
141
142
  xcom_file = os.path.join(xcom_dir, key)
142
143
  with open(xcom_file, "w") as f:
143
- f.write(value)
144
+ f.write(f"{value}")
144
145
 
145
146
  def get_xcom(self, key: str) -> str:
146
147
  self.__ensure_xcom_dir_exists()
@@ -164,6 +165,7 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
164
165
  is_async: bool = False,
165
166
  show_done_info: bool = True,
166
167
  should_clear_xcom: bool = False,
168
+ should_stop_looper: bool = False,
167
169
  ) -> Callable[..., Any]:
168
170
  async def function(*args: Any, **kwargs: Any) -> Any:
169
171
  self.log_info("Copy task")
@@ -175,6 +177,8 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
175
177
  kwargs=kwargs,
176
178
  show_done_info=show_done_info,
177
179
  )
180
+ if should_stop_looper:
181
+ looper.stop()
178
182
  if should_clear_xcom:
179
183
  self_cp.clear_xcom()
180
184
  return result
zrb/task/checker.py CHANGED
@@ -83,15 +83,15 @@ class Checker(BaseTask):
83
83
  while True:
84
84
  self._should_show_progress = wait_time >= self._progress_interval
85
85
  inspect_result = await self.inspect(*args, **kwargs)
86
- if inspect_result == self._expected_result:
86
+ if inspect_result is not None and inspect_result == self._expected_result:
87
87
  return True
88
88
  if wait_time >= self._progress_interval:
89
89
  wait_time = 0
90
90
  await asyncio.sleep(self._checking_interval)
91
91
  wait_time += self._checking_interval
92
92
 
93
- async def inspect(self, *args: Any, **kwargs: Any) -> bool:
94
- return False
93
+ async def inspect(self, *args: Any, **kwargs: Any) -> Optional[bool]:
94
+ return None
95
95
 
96
96
  def show_progress(self, message: str):
97
97
  if self._should_show_progress:
zrb/task/cmd_task.py CHANGED
@@ -199,8 +199,17 @@ class CmdTask(BaseTask):
199
199
  raise_error: bool = True,
200
200
  is_async: bool = False,
201
201
  show_done_info: bool = True,
202
+ should_clear_xcom: bool = False,
203
+ should_stop_looper: bool = False,
202
204
  ) -> Callable[..., CmdResult]:
203
- return super().to_function(env_prefix, raise_error, is_async, show_done_info)
205
+ return super().to_function(
206
+ env_prefix=env_prefix,
207
+ raise_error=raise_error,
208
+ is_async=is_async,
209
+ show_done_info=show_done_info,
210
+ should_clear_xcom=should_clear_xcom,
211
+ should_stop_looper=should_stop_looper,
212
+ )
204
213
 
205
214
  def print_result(self, result: CmdResult):
206
215
  if not self._should_print_cmd_result or result.output == "":
zrb/task/http_checker.py CHANGED
@@ -133,8 +133,17 @@ class HTTPChecker(Checker):
133
133
  raise_error: bool = True,
134
134
  is_async: bool = False,
135
135
  show_done_info: bool = True,
136
+ should_clear_xcom: bool = False,
137
+ should_stop_looper: bool = False,
136
138
  ) -> Callable[..., bool]:
137
- return super().to_function(env_prefix, raise_error, is_async, show_done_info)
139
+ return super().to_function(
140
+ env_prefix=env_prefix,
141
+ raise_error=raise_error,
142
+ is_async=is_async,
143
+ show_done_info=show_done_info,
144
+ should_clear_xcom=should_clear_xcom,
145
+ should_stop_looper=should_stop_looper,
146
+ )
138
147
 
139
148
  async def run(self, *args: Any, **kwargs: Any) -> bool:
140
149
  self._config = HttpConnectionConfig(
zrb/task/looper.py ADDED
@@ -0,0 +1,40 @@
1
+ from zrb.helper.accessories.color import colored
2
+ from zrb.helper.callable import run_async
3
+ from zrb.helper.log import logger
4
+ from zrb.helper.typing import Callable, List, Mapping, Optional
5
+
6
+ logger.debug(colored("Loading zrb.task.looper", attrs=["dark"]))
7
+
8
+
9
+ class Looper:
10
+ def __init__(self):
11
+ self._queue: Mapping[str, List[Optional[bool]]] = {}
12
+ self._should_stop = False
13
+
14
+ async def pop(self, identifier: str) -> Optional[bool]:
15
+ if identifier not in self._queue or len(self._queue[identifier]) == 0:
16
+ return None
17
+ return self._queue[identifier].pop(0)
18
+
19
+ def stop(self):
20
+ self._should_stop = True
21
+
22
+ def is_registered(self, identifier: str) -> bool:
23
+ return identifier in self._queue
24
+
25
+ async def register(self, identifier: str, function: Callable[..., Optional[bool]]):
26
+ if identifier in self._queue:
27
+ return
28
+ self._queue[identifier] = []
29
+ while not self._should_stop:
30
+ try:
31
+ result = await run_async(function)
32
+ if result is not None:
33
+ if not result:
34
+ continue
35
+ self._queue[identifier].append(result)
36
+ except KeyboardInterrupt:
37
+ break
38
+
39
+
40
+ looper = Looper()
zrb/task/path_checker.py CHANGED
@@ -99,8 +99,17 @@ class PathChecker(Checker):
99
99
  raise_error: bool = True,
100
100
  is_async: bool = False,
101
101
  show_done_info: bool = True,
102
+ should_clear_xcom: bool = False,
103
+ should_stop_looper: bool = False,
102
104
  ) -> Callable[..., bool]:
103
- return super().to_function(env_prefix, raise_error, is_async, show_done_info)
105
+ return super().to_function(
106
+ env_prefix=env_prefix,
107
+ raise_error=raise_error,
108
+ is_async=is_async,
109
+ show_done_info=show_done_info,
110
+ should_clear_xcom=should_clear_xcom,
111
+ should_stop_looper=should_stop_looper,
112
+ )
104
113
 
105
114
  async def run(self, *args: Any, **kwargs: Any) -> bool:
106
115
  self._rendered_path = self.render_str(self._path)
zrb/task/path_watcher.py CHANGED
@@ -25,7 +25,7 @@ from zrb.task.any_task_event_handler import (
25
25
  OnTriggered,
26
26
  OnWaiting,
27
27
  )
28
- from zrb.task.checker import Checker
28
+ from zrb.task.watcher import Watcher
29
29
  from zrb.task_env.env import Env
30
30
  from zrb.task_env.env_file import EnvFile
31
31
  from zrb.task_group.group import Group
@@ -37,7 +37,7 @@ TPathWatcher = TypeVar("TPathWatcher", bound="PathWatcher")
37
37
 
38
38
 
39
39
  @typechecked
40
- class PathWatcher(Checker):
40
+ class PathWatcher(Watcher):
41
41
  """
42
42
  PathWatcher will wait for any changes specified on path.
43
43
 
@@ -49,6 +49,8 @@ class PathWatcher(Checker):
49
49
  - <task-name>.deleted-file
50
50
  """
51
51
 
52
+ __init_times: Mapping[str, Mapping[str, float]] = {}
53
+
52
54
  def __init__(
53
55
  self,
54
56
  name: str = "watch-path",
@@ -77,7 +79,7 @@ class PathWatcher(Checker):
77
79
  watch_deleted_files: bool = True,
78
80
  should_execute: Union[bool, JinjaTemplate, Callable[..., bool]] = True,
79
81
  ):
80
- Checker.__init__(
82
+ Watcher.__init__(
81
83
  self,
82
84
  name=name,
83
85
  group=group,
@@ -107,7 +109,6 @@ class PathWatcher(Checker):
107
109
  self._watch_deleted_files = watch_deleted_files
108
110
  self._rendered_path: str = ""
109
111
  self._rendered_ignored_paths: List[str] = []
110
- self._init_times: Mapping[str, float] = {}
111
112
 
112
113
  def copy(self) -> TPathWatcher:
113
114
  return super().copy()
@@ -118,8 +119,17 @@ class PathWatcher(Checker):
118
119
  raise_error: bool = True,
119
120
  is_async: bool = False,
120
121
  show_done_info: bool = True,
122
+ should_clear_xcom: bool = False,
123
+ should_stop_looper: bool = False,
121
124
  ) -> Callable[..., bool]:
122
- return super().to_function(env_prefix, raise_error, is_async, show_done_info)
125
+ return super().to_function(
126
+ env_prefix=env_prefix,
127
+ raise_error=raise_error,
128
+ is_async=is_async,
129
+ show_done_info=show_done_info,
130
+ should_clear_xcom=should_clear_xcom,
131
+ should_stop_looper=should_stop_looper,
132
+ )
123
133
 
124
134
  async def run(self, *args: Any, **kwargs: Any) -> bool:
125
135
  self._rendered_path = self.render_str(self._path)
@@ -128,7 +138,9 @@ class PathWatcher(Checker):
128
138
  for ignored_path in self._get_rendered_ignored_paths()
129
139
  if ignored_path != ""
130
140
  ]
131
- self._init_times = self._get_mod_times()
141
+ identifier = self.get_identifier()
142
+ if identifier not in self.__init_times:
143
+ self.__init_times[identifier] = self._get_mod_times()
132
144
  return await super().run(*args, **kwargs)
133
145
 
134
146
  def _get_rendered_ignored_paths(self) -> List[str]:
@@ -136,41 +148,49 @@ class PathWatcher(Checker):
136
148
  return [self.render_str(self._ignored_path)]
137
149
  return [self.render_str(ignored_path) for ignored_path in self._ignored_path]
138
150
 
139
- async def inspect(self, *args: Any, **kwargs: Any) -> bool:
140
- label = f"Watching {self._rendered_path}"
141
- try:
142
- mod_times = self._get_mod_times()
143
- except Exception as e:
144
- self.show_progress(f"{label} Cannot inspect")
145
- raise e
146
- # watch changes
147
- if self._watch_new_files:
148
- new_files = mod_times.keys() - self._init_times.keys()
149
- for file in new_files:
150
- self.print_out_dark(f"{label} [+] New file detected: {file}")
151
- self.set_task_xcom("new-file", file)
152
- self.set_task_xcom("file", file)
153
- return True
154
- if self._watch_deleted_files:
155
- deleted_files = self._init_times.keys() - mod_times.keys()
156
- for file in deleted_files:
157
- self.print_out_dark(f"{label} [-] File deleted: {file}")
158
- self.set_task_xcom("deleted-file", file)
159
- self.set_task_xcom("file", file)
160
- return True
161
- if self._watch_modified_files:
162
- modified_files = {
163
- file
164
- for file, mod_time in mod_times.items()
165
- if file in mod_times and self._init_times[file] != mod_time
166
- }
167
- for file in modified_files:
168
- self.print_out_dark(f"{label} [/] File modified: {file}")
169
- self.set_task_xcom("modified-file", file)
170
- self.set_task_xcom("file", file)
171
- return True
172
- self.show_progress(f"{label} (Nothing changed)")
173
- return False
151
+ def create_loop_inspector(self) -> Callable[..., Optional[bool]]:
152
+ def loop_inspect() -> bool:
153
+ label = f"Watching {self._rendered_path}"
154
+ identifier = self.get_identifier()
155
+ try:
156
+ init_times = self.__init_times[identifier]
157
+ mod_times = self._get_mod_times()
158
+ except Exception as e:
159
+ self.show_progress(f"{label} Cannot inspect")
160
+ raise e
161
+ # watch changes
162
+ if self._watch_new_files:
163
+ new_files = mod_times.keys() - init_times.keys()
164
+ for file in new_files:
165
+ self.print_out_dark(f"{label} [+] New file detected: {file}")
166
+ self.set_task_xcom("new-file", file)
167
+ self.set_task_xcom("file", file)
168
+ self.__init_times[identifier] = self._get_mod_times()
169
+ return True
170
+ if self._watch_deleted_files:
171
+ deleted_files = init_times.keys() - mod_times.keys()
172
+ for file in deleted_files:
173
+ self.print_out_dark(f"{label} [-] File deleted: {file}")
174
+ self.set_task_xcom("deleted-file", file)
175
+ self.set_task_xcom("file", file)
176
+ self.__init_times[identifier] = self._get_mod_times()
177
+ return True
178
+ if self._watch_modified_files:
179
+ modified_files = {
180
+ file
181
+ for file, mod_time in mod_times.items()
182
+ if file in mod_times and init_times[file] != mod_time
183
+ }
184
+ for file in modified_files:
185
+ self.print_out_dark(f"{label} [/] File modified: {file}")
186
+ self.set_task_xcom("modified-file", file)
187
+ self.set_task_xcom("file", file)
188
+ self.__init_times[identifier] = self._get_mod_times()
189
+ return True
190
+ self.show_progress(f"{label} (Nothing changed)")
191
+ return False
192
+
193
+ return loop_inspect
174
194
 
175
195
  def _get_mod_times(self) -> Mapping[str, float]:
176
196
  matches = get_file_names(
zrb/task/port_checker.py CHANGED
@@ -109,8 +109,17 @@ class PortChecker(Checker):
109
109
  raise_error: bool = True,
110
110
  is_async: bool = False,
111
111
  show_done_info: bool = True,
112
+ should_clear_xcom: bool = False,
113
+ should_stop_looper: bool = False,
112
114
  ) -> Callable[..., bool]:
113
- return super().to_function(env_prefix, raise_error, is_async, show_done_info)
115
+ return super().to_function(
116
+ env_prefix=env_prefix,
117
+ raise_error=raise_error,
118
+ is_async=is_async,
119
+ show_done_info=show_done_info,
120
+ should_clear_xcom=should_clear_xcom,
121
+ should_stop_looper=should_stop_looper,
122
+ )
114
123
 
115
124
  async def run(self, *args: Any, **kwargs: Any) -> bool:
116
125
  self._config = PortConfig(
@@ -115,6 +115,7 @@ class RecurringTask(BaseTask):
115
115
  self._triggers: List[AnyTask] = [trigger.copy() for trigger in triggers]
116
116
  self._run_configs: List[RunConfig] = []
117
117
  self._single_execution = single_execution
118
+ self.print_err("Deprecated, please use Server instead")
118
119
 
119
120
  async def _set_keyval(self, kwargs: Mapping[str, Any], env_prefix: str):
120
121
  await super()._set_keyval(kwargs=kwargs, env_prefix=env_prefix)
@@ -144,8 +144,17 @@ class ResourceMaker(BaseTask):
144
144
  raise_error: bool = True,
145
145
  is_async: bool = False,
146
146
  show_done_info: bool = True,
147
+ should_clear_xcom: bool = False,
148
+ should_stop_looper: bool = False,
147
149
  ) -> Callable[..., bool]:
148
- return super().to_function(env_prefix, raise_error, is_async, show_done_info)
150
+ return super().to_function(
151
+ env_prefix=env_prefix,
152
+ raise_error=raise_error,
153
+ is_async=is_async,
154
+ show_done_info=show_done_info,
155
+ should_clear_xcom=should_clear_xcom,
156
+ should_stop_looper=should_stop_looper,
157
+ )
149
158
 
150
159
  async def run(self, *args: Any, **kwargs: Any) -> bool:
151
160
  # render parameters
zrb/task/server.py ADDED
@@ -0,0 +1,190 @@
1
+ import asyncio
2
+
3
+ from zrb.helper.accessories.color import colored
4
+ from zrb.helper.accessories.name import get_random_name
5
+ from zrb.helper.log import logger
6
+ from zrb.helper.typecheck import typechecked
7
+ from zrb.helper.typing import Any, Callable, Iterable, List, Mapping, Optional, Union
8
+ from zrb.helper.util import to_kebab_case
9
+ from zrb.task.any_task import AnyTask
10
+ from zrb.task.any_task_event_handler import (
11
+ OnFailed,
12
+ OnReady,
13
+ OnRetry,
14
+ OnSkipped,
15
+ OnStarted,
16
+ OnTriggered,
17
+ OnWaiting,
18
+ )
19
+ from zrb.task.base_task.base_task import BaseTask
20
+ from zrb.task.flow_task import FlowTask
21
+ from zrb.task_env.env import Env
22
+ from zrb.task_env.env_file import EnvFile
23
+ from zrb.task_group.group import Group
24
+ from zrb.task_input.any_input import AnyInput
25
+
26
+ logger.debug(colored("Loading zrb.task.server", attrs=["dark"]))
27
+
28
+
29
+ @typechecked
30
+ class Controller:
31
+ def __init__(
32
+ self,
33
+ trigger: Union[AnyTask, List[AnyTask]],
34
+ action: Union[AnyTask, List[AnyTask]],
35
+ name: Optional[str] = None,
36
+ ):
37
+ 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
40
+ self._args: List[Any] = []
41
+ self._kwargs: Mapping[str, Any] = {}
42
+ self._inputs: List[AnyInput] = []
43
+ self._envs: List[Env] = []
44
+ self._env_files: List[EnvFile] = []
45
+
46
+ def set_args(self, args: List[Any]):
47
+ self._args = args
48
+
49
+ def set_kwargs(self, kwargs: Mapping[str, Any]):
50
+ self._kwargs = kwargs
51
+
52
+ def set_inputs(self, inputs: List[AnyInput]):
53
+ self._inputs = inputs
54
+
55
+ def set_envs(self, envs: List[Env]):
56
+ self._envs = envs
57
+
58
+ def set_env_files(self, env_files: List[EnvFile]):
59
+ self._env_files = env_files
60
+
61
+ def get_sub_env_files(self) -> Iterable[EnvFile]:
62
+ env_files = []
63
+ for trigger in self._triggers:
64
+ env_files += trigger.copy()._get_env_files()
65
+ for action in self._actions:
66
+ env_files += action.copy()._get_env_files()
67
+ return env_files
68
+
69
+ def get_sub_envs(self) -> Iterable[Env]:
70
+ envs = []
71
+ for trigger in self._triggers:
72
+ envs += trigger.copy()._get_envs()
73
+ for action in self._actions:
74
+ envs += action.copy()._get_envs()
75
+ return envs
76
+
77
+ def get_sub_inputs(self) -> Iterable[AnyInput]:
78
+ inputs = []
79
+ for trigger in self._triggers:
80
+ inputs += trigger.copy()._get_combined_inputs()
81
+ for action in self._actions:
82
+ inputs += action.copy()._get_combined_inputs()
83
+ return inputs
84
+
85
+ def to_function(self) -> Callable[..., Any]:
86
+ task = self._get_task()
87
+
88
+ async def fn() -> Any:
89
+ task_fn = task.to_function(is_async=True)
90
+ return await task_fn(*self._args, **self._kwargs)
91
+
92
+ return fn
93
+
94
+ def _get_task(self) -> AnyTask:
95
+ actions = [action.copy() for action in self._actions]
96
+ actions.insert(0, self._get_remonitor_task())
97
+ triggers = [trigger.copy() for trigger in self._triggers]
98
+ task: AnyTask = FlowTask(
99
+ name=to_kebab_case(self._name),
100
+ inputs=self._inputs,
101
+ envs=self._envs,
102
+ env_files=self._env_files,
103
+ steps=[triggers, actions],
104
+ )
105
+ return task
106
+
107
+ def _get_remonitor_task(self) -> AnyTask:
108
+ async def on_ready(task: AnyTask):
109
+ task = self._get_task()
110
+ fn = task.to_function(is_async=True)
111
+ await fn()
112
+
113
+ return BaseTask(
114
+ name=f"monitor-{to_kebab_case(self._name)}",
115
+ on_ready=on_ready,
116
+ )
117
+
118
+
119
+ @typechecked
120
+ class Server(BaseTask):
121
+
122
+ def __init__(
123
+ self,
124
+ name: str,
125
+ controllers: List[Controller],
126
+ group: Optional[Group] = None,
127
+ inputs: Iterable[AnyInput] = [],
128
+ envs: Iterable[Env] = [],
129
+ env_files: Iterable[EnvFile] = [],
130
+ icon: Optional[str] = None,
131
+ color: Optional[str] = None,
132
+ description: str = "",
133
+ upstreams: Iterable[AnyTask] = [],
134
+ fallbacks: Iterable[AnyTask] = [],
135
+ on_triggered: Optional[OnTriggered] = None,
136
+ on_waiting: Optional[OnWaiting] = None,
137
+ on_skipped: Optional[OnSkipped] = None,
138
+ on_started: Optional[OnStarted] = None,
139
+ on_ready: Optional[OnReady] = None,
140
+ on_retry: Optional[OnRetry] = None,
141
+ on_failed: Optional[OnFailed] = None,
142
+ checkers: Iterable[AnyTask] = [],
143
+ checking_interval: float = 0,
144
+ retry: int = 0,
145
+ retry_interval: float = 1,
146
+ should_execute: Union[bool, str, Callable[..., bool]] = True,
147
+ return_upstream_result: bool = False,
148
+ ):
149
+ inputs, envs, env_files = list(inputs), list(envs), list(env_files)
150
+ for controller in controllers:
151
+ inputs += controller.get_sub_inputs()
152
+ envs += controller.get_sub_envs()
153
+ env_files += controller.get_sub_env_files()
154
+ BaseTask.__init__(
155
+ self,
156
+ name=name,
157
+ group=group,
158
+ inputs=inputs,
159
+ envs=envs,
160
+ env_files=env_files,
161
+ icon=icon,
162
+ color=color,
163
+ description=description,
164
+ upstreams=upstreams,
165
+ fallbacks=fallbacks,
166
+ on_triggered=on_triggered,
167
+ on_waiting=on_waiting,
168
+ on_skipped=on_skipped,
169
+ on_started=on_started,
170
+ on_ready=on_ready,
171
+ on_retry=on_retry,
172
+ on_failed=on_failed,
173
+ checkers=checkers,
174
+ checking_interval=checking_interval,
175
+ retry=retry,
176
+ retry_interval=retry_interval,
177
+ should_execute=should_execute,
178
+ return_upstream_result=return_upstream_result,
179
+ )
180
+ self._controllers = controllers
181
+
182
+ async def run(self, *args: Any, **kwargs: Any):
183
+ for controller in self._controllers:
184
+ controller.set_envs(self._get_envs())
185
+ controller.set_env_files(self._get_env_files())
186
+ controller.set_inputs(self._get_inputs())
187
+ controller.set_args(args)
188
+ controller.set_kwargs(kwargs)
189
+ functions = [controller.to_function() for controller in self._controllers]
190
+ await asyncio.gather(*[fn() for fn in functions])
zrb/task/time_watcher.py CHANGED
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import datetime
2
3
 
3
4
  import croniter
@@ -10,6 +11,7 @@ from zrb.helper.typing import (
10
11
  Callable,
11
12
  Iterable,
12
13
  JinjaTemplate,
14
+ Mapping,
13
15
  Optional,
14
16
  TypeVar,
15
17
  Union,
@@ -24,7 +26,7 @@ from zrb.task.any_task_event_handler import (
24
26
  OnTriggered,
25
27
  OnWaiting,
26
28
  )
27
- from zrb.task.checker import Checker
29
+ from zrb.task.watcher import Watcher
28
30
  from zrb.task_env.env import Env
29
31
  from zrb.task_env.env_file import EnvFile
30
32
  from zrb.task_group.group import Group
@@ -36,7 +38,7 @@ TTimeWatcher = TypeVar("TTimeWatcher", bound="TimeWatcher")
36
38
 
37
39
 
38
40
  @typechecked
39
- class TimeWatcher(Checker):
41
+ class TimeWatcher(Watcher):
40
42
  """
41
43
  TimeWatcher will wait for any changes specified on path.
42
44
 
@@ -44,6 +46,8 @@ class TimeWatcher(Checker):
44
46
  and <task-name>.scheduled-time xcom will be set.
45
47
  """
46
48
 
49
+ __scheduled_times: Mapping[str, Mapping[str, datetime.datetime]] = {}
50
+
47
51
  def __init__(
48
52
  self,
49
53
  name: str = "watch-path",
@@ -68,7 +72,7 @@ class TimeWatcher(Checker):
68
72
  progress_interval: Union[int, float] = 30,
69
73
  should_execute: Union[bool, JinjaTemplate, Callable[..., bool]] = True,
70
74
  ):
71
- Checker.__init__(
75
+ Watcher.__init__(
72
76
  self,
73
77
  name=name,
74
78
  group=group,
@@ -92,7 +96,6 @@ class TimeWatcher(Checker):
92
96
  should_execute=should_execute,
93
97
  )
94
98
  self._schedule = schedule
95
- self._scheduled_time: Optional[datetime.datetime] = None
96
99
  self._rendered_schedule: str = ""
97
100
 
98
101
  def copy(self) -> TTimeWatcher:
@@ -104,23 +107,48 @@ class TimeWatcher(Checker):
104
107
  raise_error: bool = True,
105
108
  is_async: bool = False,
106
109
  show_done_info: bool = True,
110
+ should_clear_xcom: bool = False,
111
+ should_stop_looper: bool = False,
107
112
  ) -> Callable[..., bool]:
108
- return super().to_function(env_prefix, raise_error, is_async, show_done_info)
113
+ return super().to_function(
114
+ env_prefix=env_prefix,
115
+ raise_error=raise_error,
116
+ is_async=is_async,
117
+ show_done_info=show_done_info,
118
+ should_clear_xcom=should_clear_xcom,
119
+ should_stop_looper=should_stop_looper,
120
+ )
109
121
 
110
122
  async def run(self, *args: Any, **kwargs: Any) -> bool:
111
123
  self._rendered_schedule = self.render_str(self._schedule)
112
- margin = datetime.timedelta(seconds=0.001)
113
- slightly_before_check_time = datetime.datetime.now() - margin
114
- cron = croniter.croniter(self._rendered_schedule, slightly_before_check_time)
115
- self._scheduled_time = cron.get_next(datetime.datetime)
116
- self.set_task_xcom(key="scheduled-time", value=self._scheduled_time)
124
+ identifier = self.get_identifier()
125
+ if identifier not in self.__scheduled_times:
126
+ self.__scheduled_times[identifier] = self._get_next_schedule_time()
117
127
  return await super().run(*args, **kwargs)
118
128
 
119
- async def inspect(self, *args: Any, **kwargs: Any) -> bool:
120
- label = f"Watching {self._rendered_schedule}"
121
- now = datetime.datetime.now()
122
- if now > self._scheduled_time:
123
- self.print_out_dark(f"{label} (Meet {self._scheduled_time})")
124
- return True
125
- self.show_progress(f"{label} (Waiting for {self._scheduled_time})")
126
- return False
129
+ def create_loop_inspector(self) -> Callable[..., Optional[bool]]:
130
+ async def loop_inspect() -> bool:
131
+ await asyncio.sleep(0.1)
132
+ label = f"Watching {self._rendered_schedule}"
133
+ identifier = self.get_identifier()
134
+ scheduled_time = self.__scheduled_times[identifier]
135
+ self.set_task_xcom(key="scheduled-time", value=scheduled_time)
136
+ now = datetime.datetime.now()
137
+ if now > scheduled_time:
138
+ self.print_out_dark(f"{label} (Meet {scheduled_time})")
139
+ self.__scheduled_times[identifier] = self._get_next_schedule_time()
140
+ return True
141
+ self.show_progress(f"{label} (Waiting for {scheduled_time})")
142
+ return False
143
+
144
+ return loop_inspect
145
+
146
+ def _get_next_schedule_time(self) -> datetime.datetime:
147
+ cron = self._get_cron()
148
+ return cron.get_next(datetime.datetime)
149
+
150
+ def _get_cron(self) -> Any:
151
+ margin = datetime.timedelta(seconds=0.001)
152
+ slightly_before_now = datetime.datetime.now() - margin
153
+ cron = croniter.croniter(self._rendered_schedule, slightly_before_now)
154
+ return cron
zrb/task/watcher.py ADDED
@@ -0,0 +1,100 @@
1
+ import asyncio
2
+
3
+ from zrb.helper.accessories.color import colored
4
+ from zrb.helper.accessories.name import get_random_name
5
+ from zrb.helper.log import logger
6
+ from zrb.helper.typecheck import typechecked
7
+ from zrb.helper.typing import Any, Callable, Iterable, Optional, Union
8
+ from zrb.task.any_task import AnyTask
9
+ from zrb.task.any_task_event_handler import (
10
+ OnFailed,
11
+ OnReady,
12
+ OnRetry,
13
+ OnSkipped,
14
+ OnStarted,
15
+ OnTriggered,
16
+ OnWaiting,
17
+ )
18
+ from zrb.task.checker import Checker
19
+ from zrb.task.looper import looper
20
+ from zrb.task_env.env import Env
21
+ from zrb.task_env.env_file import EnvFile
22
+ from zrb.task_group.group import Group
23
+ from zrb.task_input.any_input import AnyInput
24
+
25
+ logger.debug(colored("Loading zrb.task.watcher", attrs=["dark"]))
26
+
27
+
28
+ @typechecked
29
+ class Watcher(Checker):
30
+ __looper = looper
31
+
32
+ def __init__(
33
+ self,
34
+ name: str = "watch",
35
+ group: Optional[Group] = None,
36
+ inputs: Iterable[AnyInput] = [],
37
+ envs: Iterable[Env] = [],
38
+ env_files: Iterable[EnvFile] = [],
39
+ icon: Optional[str] = None,
40
+ color: Optional[str] = None,
41
+ description: str = "",
42
+ upstreams: Iterable[AnyTask] = [],
43
+ fallbacks: Iterable[AnyTask] = [],
44
+ on_triggered: Optional[OnTriggered] = None,
45
+ on_waiting: Optional[OnWaiting] = None,
46
+ on_skipped: Optional[OnSkipped] = None,
47
+ on_started: Optional[OnStarted] = None,
48
+ on_ready: Optional[OnReady] = None,
49
+ on_retry: Optional[OnRetry] = None,
50
+ on_failed: Optional[OnFailed] = None,
51
+ checking_interval: Union[int, float] = 0.1,
52
+ progress_interval: Union[int, float] = 30,
53
+ expected_result: bool = True,
54
+ should_execute: Union[bool, str, Callable[..., bool]] = True,
55
+ ):
56
+ Checker.__init__(
57
+ self,
58
+ name=name,
59
+ group=group,
60
+ inputs=inputs,
61
+ envs=envs,
62
+ env_files=env_files,
63
+ icon=icon,
64
+ color=color,
65
+ description=description,
66
+ upstreams=upstreams,
67
+ fallbacks=fallbacks,
68
+ on_triggered=on_triggered,
69
+ on_waiting=on_waiting,
70
+ on_skipped=on_skipped,
71
+ on_started=on_started,
72
+ on_ready=on_ready,
73
+ on_retry=on_retry,
74
+ on_failed=on_failed,
75
+ checking_interval=checking_interval,
76
+ should_execute=should_execute,
77
+ progress_interval=progress_interval,
78
+ expected_result=expected_result,
79
+ )
80
+ self._identifier = get_random_name()
81
+
82
+ def get_identifier(self):
83
+ return self._identifier
84
+
85
+ async def run(self, *args: Any, **kwargs: Any) -> bool:
86
+ if not looper.is_registered(self._identifier):
87
+ asyncio.create_task(
88
+ looper.register(self._identifier, self.create_loop_inspector())
89
+ )
90
+ return await super().run(*args, **kwargs)
91
+
92
+ async def inspect(self, *args, **kwargs: Any) -> Optional[bool]:
93
+ result = await looper.pop(self._identifier)
94
+ return result
95
+
96
+ def create_loop_inspector(self) -> Callable[..., Optional[bool]]:
97
+ def loop_inspect() -> Optional[bool]:
98
+ return None
99
+
100
+ return loop_inspect
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zrb
3
- Version: 0.15.1
3
+ Version: 0.17.0
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
@@ -1,11 +1,11 @@
1
- zrb/__init__.py,sha256=QNtBHrQnz4dEnnFtgrUBUUl9Ph7cUVFt6zJe3PpN0d0,2683
1
+ zrb/__init__.py,sha256=KH6bpJxEldzDMn-39IdUxEs9Ir5qUeqW_iC56iQKPMk,2814
2
2
  zrb/__main__.py,sha256=-_k0XOahDF-06n41Uly-oUMkZ8XDSxO-WUUImWz6GiA,171
3
3
  zrb/action/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- zrb/action/runner.py,sha256=Z_Rh60DA0U_HqO4t_NzWTmXvHVfdgaL5RQJfTnEZiFQ,4951
4
+ zrb/action/runner.py,sha256=oizUiLa4wa4CjXppbw_YUFlkAUSwapfoQ1YAo9TEY30,5079
5
5
  zrb/advertisement.py,sha256=eqdQvr2IDS2ZTiOhg2R12wq0A5tnaoPATaH5nd1xfC8,699
6
6
  zrb/builtin/__init__.py,sha256=QrJY08zBC55xkJa_kNhGAWEnhHwATZbNGxEEXEn-Ywk,3247
7
7
  zrb/builtin/_helper/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- zrb/builtin/_helper/reccuring_action.py,sha256=_oApvOrn7K4NZa-vt-A-xvpUSPjPEG2gydPXzJEUzSo,1940
8
+ zrb/builtin/_helper/reccuring_action.py,sha256=3YeMtGM13iwSV8kMJ64wuGDwAgKB7KD2nuaqKXuMRPw,1186
9
9
  zrb/builtin/base64/__init__.py,sha256=Dl5eG_I7xvigiEp4mURXoF8J78Y2UixaDmI1O74bsrY,218
10
10
  zrb/builtin/base64/_group.py,sha256=4aDDjvtoZUpXBst4TPDxsJvXhlefvQjUaSOteyJP83w,109
11
11
  zrb/builtin/base64/_input.py,sha256=dQA-svhwA7qrtGRpe2jYjVuwvQxxyq9MKIR-e1ay8ks,128
@@ -30,7 +30,7 @@ zrb/builtin/devtool/install/gvm/resource/config.sh,sha256=M_r6XjtoYZjx8rJaT3FIwV
30
30
  zrb/builtin/devtool/install/helix/helix.py,sha256=W0Xm_eq0wIHcoq0CzG9WrQIH9tPByT-AZ-zi4Z72-U0,2098
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
- zrb/builtin/devtool/install/helix/resource/config.toml,sha256=zEeA-oiVbCGs6dQNZAbeFfMJ3giClOkuhgJ6Pcel2ww,282
33
+ zrb/builtin/devtool/install/helix/resource/config.toml,sha256=35IwzDzXGfSnUH3O3nyd2IzDVOWyKqj6Kb3QuympXCE,305
34
34
  zrb/builtin/devtool/install/helix/resource/themes/gruvbox_transparent.toml,sha256=Kz6AhxXKVHrP5mpFI99uPFyfUOvB_0x2fapj0tUkIaA,56
35
35
  zrb/builtin/devtool/install/helm/helm.py,sha256=Y2Iklrcw4-OQYNIjeuKmuIzpdAqGGcl82ZSHMFWck_E,762
36
36
  zrb/builtin/devtool/install/helm/install.sh,sha256=fqjaTsO25B2MV7134sriSY5hQ2A1VRsaLc3WmD5jKu4,228
@@ -90,7 +90,7 @@ zrb/builtin/process/__init__.py,sha256=dWM1zIehY65rkJvEAFvX6HGJHL8kb1gkzaSz0Woay
90
90
  zrb/builtin/process/_group.py,sha256=KY7iQ_pvqe5iVsRFeLWs-O-xuSyqlTXVj47_TCnu7zQ,118
91
91
  zrb/builtin/process/pid/__init__.py,sha256=Rcb3bYgR_oVNOlaggYjcRipHRehrumZuQyadrguXdR0,293
92
92
  zrb/builtin/process/pid/_group.py,sha256=SUe7G8tmfg_Q-X3nHOqCUMjDXlTE_hb9gAKVie2wQ2A,195
93
- zrb/builtin/process/pid/get_by_name.py,sha256=fjJw0n353lhhyR1wnPHLXV13M9aNQmIAeQ_jUmBMVsA,625
93
+ zrb/builtin/process/pid/get_by_name.py,sha256=cW4c9l4JUGa9w5VHUFWi_3tUyx55t7-6i12Gm_hCkhQ,637
94
94
  zrb/builtin/process/pid/get_by_port.py,sha256=_yz0jBAV7qIo-eU33peDgVhtzYhMAxAHOc6xQtDwXyc,640
95
95
  zrb/builtin/project/__init__.py,sha256=i1YF08_6UjNznYeL0iGTbU8o5yz7iw41RYd8UgeB8U4,887
96
96
  zrb/builtin/project/_group.py,sha256=RPWt2YOLIqF7ZznzkSeFm9dozz1oCKmpCWCA1jjgsl4,112
@@ -1280,7 +1280,7 @@ zrb/builtin/project/create/template/src/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeu
1280
1280
  zrb/builtin/project/create/template/template.env,sha256=Y_11yIi7fA5aIOHxrcexFXNxz1UqXaEiIgqUHBazy0w,102
1281
1281
  zrb/builtin/project/create/template/zrb_init.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1282
1282
  zrb/builtin/say.py,sha256=KHuzgwitmWQy5g7N11qqs9iV-66vvAl0Uyke9hdc-Eg,3859
1283
- zrb/builtin/schedule.py,sha256=-yOfTAx4lORR7_4PCBpOwn8m-imN7oHs4-RTUL2DC-Y,990
1283
+ zrb/builtin/schedule.py,sha256=yzprSE8oPgtXssK1sW3YpaqhTHcECTQWnuuIweEqBNw,1048
1284
1284
  zrb/builtin/ubuntu/__init__.py,sha256=79JyTzx6pyhtEa7hrsUTNaAQ7xlrgDX1KU74OG8mhic,486
1285
1285
  zrb/builtin/ubuntu/_group.py,sha256=6VhCi-wZRuwvmELlvqvVSOfYLnibP8SQIT2GpaDPtOA,115
1286
1286
  zrb/builtin/ubuntu/install/__init__.py,sha256=AoGq7pPn8FWoRXrEwiaX1ZBBQTzFpqIjpwIx4aajpa8,472
@@ -1292,7 +1292,7 @@ zrb/builtin/ubuntu/install/toys.py,sha256=VlWb8_aHW1kRK6vpjpAveNByc1FlsMPLFXl1X9
1292
1292
  zrb/builtin/ubuntu/update.py,sha256=m3UREE6DhwlbdkQpuug2sfEM6fBLZn0ZGClt_EOsZ_I,372
1293
1293
  zrb/builtin/update.py,sha256=89i_fPUlL27IXczLI7Lr7k4STMpnxyw2je8daCKUTQo,225
1294
1294
  zrb/builtin/version.py,sha256=vjbmSeOSEjT0SgdeQHGslwFCQMukwVZkOOkusZGZNcU,394
1295
- zrb/builtin/watch_changes.py,sha256=5oe1zPRamo5ZsWQKvgVWCXiBv4encS4kN7mubgvacjk,1192
1295
+ zrb/builtin/watch_changes.py,sha256=Vr__e_T31nnbefcPftvyn78dT3-UXqNRpH0KO-COeKQ,1220
1296
1296
  zrb/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1297
1297
  zrb/config/config.py,sha256=6uDxOLmHHstLCosj2miBVxlO9dMTIoXYedqaff8EVBU,1567
1298
1298
  zrb/helper/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1302,7 +1302,7 @@ zrb/helper/accessories/icon.py,sha256=uKm3w5G1fV454MUhz_CZMy48AD99kBV9j2zRlJWzB1
1302
1302
  zrb/helper/accessories/name.py,sha256=e1uvU3MzuvDb5j6YIWBA048y4qeM-LfrxRKWlMehliE,1638
1303
1303
  zrb/helper/accessories/untyped_color.py,sha256=4wRbHSClbCzPRWtW-Cy2agOMozANVi7u4p8lMXMSuJg,194
1304
1304
  zrb/helper/advertisement.py,sha256=-J9xVxf6AmcB0Uy-D4tFl-IRIZsdKTy07wIoE59jMnc,896
1305
- zrb/helper/callable.py,sha256=iRL-qVNjSYbiwxM9RRjh0hn0z0s1eS22mkZA4xWRuhg,385
1305
+ zrb/helper/callable.py,sha256=G_seq9gAricPjzlUdZs2rswQEt8sfnKQo1EFKi9JYyQ,478
1306
1306
  zrb/helper/cli.py,sha256=Ux0zkKYahrM_aVQkkuPxfOuMNmTrCIWxdOIgNJvu5OY,2177
1307
1307
  zrb/helper/codemod/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1308
1308
  zrb/helper/codemod/add_argument_to_function.py,sha256=Xh5Tlb4iO3cv4dRR12ms9kf0j-atVSnCh88TuqCof8I,1435
@@ -1355,34 +1355,37 @@ 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=WHsEUfWjRvvDqF0fxHh4_q2LmdJcnfVC9y0HFleiBZ0,39393
1358
+ zrb/task/any_task.py,sha256=1jc0qVZs1yIMfGuInktgH7EqDbI8IqmsaSlH5VijThc,39435
1359
1359
  zrb/task/any_task_event_handler.py,sha256=AjTC6lIcprutRusNBGl83EifQe4TbZzxdlVIR4ndWN4,524
1360
1360
  zrb/task/base_remote_cmd_task.py,sha256=tZi3jODMXfTkDAmWiFR2YdA-b4-TDTP1uLtO0ulmM34,10101
1361
1361
  zrb/task/base_task/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1362
- zrb/task/base_task/base_task.py,sha256=UNaMB9fBz3NqBKDr59ayAUPnUr6cNOhXUdePJcZjcko,20207
1362
+ zrb/task/base_task/base_task.py,sha256=qnQSG-nAppmm38JY11wC03-0mZSOjjk-XJ62leco2rw,20354
1363
1363
  zrb/task/base_task/component/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1364
1364
  zrb/task/base_task/component/base_task_model.py,sha256=YlEuvYSzlrK83Kds07bu2drKSw3rKyl7qIR1qwIPNZk,10376
1365
1365
  zrb/task/base_task/component/common_task_model.py,sha256=JoAAf8EvuPx31fCK_TBxX_TBeHZrZyFDjLeK8ts0w3A,12285
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
1368
  zrb/task/base_task/component/trackers.py,sha256=gM9eOukMh6kvNJnRsHscQ_JN--Haa2YA4bIufqh8upE,1950
1369
- zrb/task/checker.py,sha256=dA_AitSmOmvqSl_ZRffeUNGfMRHRXokRH5Ti_94hZ0o,3343
1370
- zrb/task/cmd_task.py,sha256=OamHPXW-lLBHPTOwgnSyLQ3UBvz4iuTVVm_5S6kgcqA,13892
1369
+ zrb/task/checker.py,sha256=fn6gmcCUHsar458NQ4Af4-kt2skcyibd0ewsPTRd5-w,3383
1370
+ zrb/task/cmd_task.py,sha256=_wjF9MbKGA0EAr_df47AMC0lROCiaJTUyxT-AFQKKJo,14181
1371
1371
  zrb/task/decorator.py,sha256=j62l7ITIRZtk_qE97d4naVl9gbbMoi2eEXOPOmYdF8M,3039
1372
1372
  zrb/task/docker_compose_task.py,sha256=fnFEOAS9yEi2ve7pSzN9guXOVeYt8HYTdQvtjk-yoRQ,14886
1373
1373
  zrb/task/flow_task.py,sha256=QIIZgq9C7e-kvTRJ0Y1Slb5AQyy15N9H4NZxdFR3FI8,4867
1374
- zrb/task/http_checker.py,sha256=0DvfE6mTyBMZQYc4iX9woYm6Ip1CX-7P167Tpci-yD8,5403
1374
+ zrb/task/http_checker.py,sha256=DRIwXjC8StucHo2nP1duM3Ime3zzw1BQH8ib2bFZVmA,5692
1375
+ zrb/task/looper.py,sha256=aU7hwXLlfZRhH4rlusIwsusz5fHEhaYfwEpRUVcHx4o,1284
1375
1376
  zrb/task/notifier.py,sha256=19E4EcFgFZ0thU9p2P1dGUYR04721pa0K3lqsj6a4Xc,6217
1376
1377
  zrb/task/parallel.py,sha256=-coMuiFlS29GpBgW6plPVaCLesgzzD0bYib29OvhXFg,1193
1377
- zrb/task/path_checker.py,sha256=65GpplGing3QeBPDtc1A_-PWXXiNItCXqabMLcgzm6s,4384
1378
- zrb/task/path_watcher.py,sha256=UR-XMEB-Kly76T4QgGZSTeLxCk_sIiNi__Pm8FfO-yA,6499
1379
- zrb/task/port_checker.py,sha256=uvOJPQqcZ_YQ1TTOxTfgfSc-7rAnSCWTYTgnY-eG4HY,4303
1380
- zrb/task/recurring_task.py,sha256=q9vzZP3iFpHWEnQ2nJAdoAu15ZQothilZQ-ID16bUV8,7366
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
1381
1382
  zrb/task/remote_cmd_task.py,sha256=rmLB5uCcbbfZBy8-nAZI8mgnNd-J2d4SBemLEDwSlV4,3951
1382
- zrb/task/resource_maker.py,sha256=vY55cBCJMYQzkVKygkb6aNTJT6kfSjPunJqHKGA2N2Y,7355
1383
+ zrb/task/resource_maker.py,sha256=jQSO7PVIuTZi__JcrpRC4Ni_xmuJszJiMAxH_qfJPhs,7644
1383
1384
  zrb/task/rsync_task.py,sha256=bgCeZQTG-4isvjZGGs_05oOEkkwGc930NTyuUYUm_cg,4187
1385
+ zrb/task/server.py,sha256=CdzBkdWZvX1PTvloDrOXtCbAbMeK4KK75C33N_Wz3Sc,6408
1384
1386
  zrb/task/task.py,sha256=dHv4cmnd0QFPT9PwrfmHpxTaXj86mm8xf7_jAj_engI,329
1385
- zrb/task/time_watcher.py,sha256=c5IPxUs4b5BsgGqyvjRVpeqXFJCHgCV-ke6uIvZ-_7A,4139
1387
+ zrb/task/time_watcher.py,sha256=xx82w8ygpL-6pUbeuWjsxSVLSZhkWVWHsAoZUXPV7Jk,5075
1388
+ zrb/task/watcher.py,sha256=m72YhUKtQsE4mZSm1y2MKiSdfj6HZo3rj9f3nQLU7Oo,3252
1386
1389
  zrb/task/wiki_task.py,sha256=Mcugk_6Pd7pzubi2ZP4eegJs8e9niYKh-9mCrNHXE_g,4330
1387
1390
  zrb/task_env/constant.py,sha256=ySdHv2dawWE-UoeBHl8FEOmrBl4vfkRI67TIBdkK6l8,220
1388
1391
  zrb/task_env/env.py,sha256=C9IzavEtWlpap4C92XONi1ID-RK9gDDLQKl5rYGBsyc,5195
@@ -1400,8 +1403,8 @@ zrb/task_input/int_input.py,sha256=d2fXcm5fCo09472eMAm6PdzLQD82ZBV9ARq5CjKepAo,4
1400
1403
  zrb/task_input/password_input.py,sha256=g_g8ZWAzDaHx4h2EHY3UCGvTigC6esAUBzXU0T9nDUk,4192
1401
1404
  zrb/task_input/str_input.py,sha256=BNflOhrJvST9bWK0rGdCi7C7y-QDvHj9ISQMRmujIWU,4200
1402
1405
  zrb/task_input/task_input.py,sha256=x1sGHsoSYAYMdQBrCLmcvZa_ZmGggMPj3goAQzewUKI,2181
1403
- zrb-0.15.1.dist-info/LICENSE,sha256=WfnGCl8G60EYOPAEkuc8C9m9pdXWDe08NsKj3TBbxsM,728
1404
- zrb-0.15.1.dist-info/METADATA,sha256=izpcqCCdrTuJBe7HKKxyMVeKZX9X2rc58JohKVaV228,16460
1405
- zrb-0.15.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
1406
- zrb-0.15.1.dist-info/entry_points.txt,sha256=xTgXc1kBKYhJHEujdaSPHUcJT3-hbyP1mLgwkv-5sSk,40
1407
- zrb-0.15.1.dist-info/RECORD,,
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,,
File without changes
File without changes