zrb 0.16.0__py3-none-any.whl → 0.17.1__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
@@ -35,6 +35,7 @@ from zrb.task.rsync_task import RsyncTask
35
35
  from zrb.task.server import Controller, Server
36
36
  from zrb.task.task import Task
37
37
  from zrb.task.time_watcher import TimeWatcher
38
+ from zrb.task.watcher import Watcher
38
39
  from zrb.task.wiki_task import create_wiki_tasks
39
40
  from zrb.task_env.env import Env
40
41
  from zrb.task_env.env_file import EnvFile
@@ -77,6 +78,7 @@ assert PathChecker
77
78
  assert PathWatcher
78
79
  assert Controller
79
80
  assert Server
81
+ assert Watcher
80
82
  assert TimeWatcher
81
83
  assert ResourceMaker
82
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
@@ -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
  ),
zrb/builtin/schedule.py CHANGED
@@ -28,8 +28,6 @@ schedule = Server(
28
28
  ),
29
29
  action=create_recurring_action(
30
30
  notif_title="Schedule",
31
- trigger_caption="Schedule",
32
- trigger_xcom_key="watch-time.scheduled-time",
33
31
  ),
34
32
  )
35
33
  ],
@@ -35,8 +35,6 @@ watch_changes = Server(
35
35
  ),
36
36
  action=create_recurring_action(
37
37
  notif_title="Watch",
38
- trigger_caption="File changes",
39
- trigger_xcom_key="watch-path.file",
40
38
  ),
41
39
  )
42
40
  ],
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
@@ -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,43 @@
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
+ while len(self._queue[identifier]) > 1000:
36
+ self._queue[identifier].pop(0)
37
+ self._queue[identifier].append(result)
38
+ except KeyboardInterrupt:
39
+ self.stop()
40
+ break
41
+
42
+
43
+ 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(
@@ -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 CHANGED
@@ -86,34 +86,33 @@ class Controller:
86
86
  task = self._get_task()
87
87
 
88
88
  async def fn() -> Any:
89
+ task.print_out_dark(f"Starting controller: {self._name}")
89
90
  task_fn = task.to_function(is_async=True)
90
91
  return await task_fn(*self._args, **self._kwargs)
91
92
 
92
93
  return fn
93
94
 
94
95
  def _get_task(self) -> AnyTask:
95
- kebab_name = to_kebab_case(self._name)
96
- actions = self._actions
97
- actions.insert(0, self._get_reschedule_task())
96
+ actions = [action.copy() for action in self._actions]
97
+ actions.insert(0, self._get_remonitor_task())
98
+ triggers = [trigger.copy() for trigger in self._triggers]
98
99
  task: AnyTask = FlowTask(
99
- name=f"{kebab_name}-flow",
100
+ name=to_kebab_case(self._name),
100
101
  inputs=self._inputs,
101
102
  envs=self._envs,
102
103
  env_files=self._env_files,
103
- steps=[self._triggers, actions],
104
+ steps=[triggers, actions],
104
105
  )
105
106
  return task
106
107
 
107
- def _get_reschedule_task(self) -> AnyTask:
108
- kebab_name = to_kebab_case(self._name)
109
-
108
+ def _get_remonitor_task(self) -> AnyTask:
110
109
  async def on_ready(task: AnyTask):
111
110
  task = self._get_task()
112
111
  fn = task.to_function(is_async=True)
113
112
  await fn()
114
113
 
115
114
  return BaseTask(
116
- name=f"{kebab_name}-reschedule",
115
+ name=f"monitor-{to_kebab_case(self._name)}",
117
116
  on_ready=on_ready,
118
117
  )
119
118
 
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.16.0
3
+ Version: 0.17.1
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=x6qsXjDRBqBjW6EP1HXy3GlaJfeJxOJrmi7iTTjBINw,2762
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
@@ -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
@@ -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=DtAhHhG56LuneLo9xENPkaP91tOD3Hb1pwBSmkr2eLo,1154
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=ifDs6sXAMaOmCas0j8RRb5_u8v9_V2zMVzyYH3Dz364,1320
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,35 +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=i7WEsvTImjzYDxYUb1VifAunkJfiYQx2I4e3NKOOqY8,20212
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=0eM3wEIC_RbThg60MRbK4Az16vt81O5p12cORAYTfnI,1430
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
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
1380
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
1384
- zrb/task/server.py,sha256=w99_9cCEitf6k2sZfLTAc9CgEOsBlcK6yO1SjU3FdWQ,6398
1385
+ zrb/task/server.py,sha256=w0gO73tt9PLWXVtgY-oe6qzvODtxVcizBGCUjRrhp5w,6478
1385
1386
  zrb/task/task.py,sha256=dHv4cmnd0QFPT9PwrfmHpxTaXj86mm8xf7_jAj_engI,329
1386
- 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
1387
1389
  zrb/task/wiki_task.py,sha256=Mcugk_6Pd7pzubi2ZP4eegJs8e9niYKh-9mCrNHXE_g,4330
1388
1390
  zrb/task_env/constant.py,sha256=ySdHv2dawWE-UoeBHl8FEOmrBl4vfkRI67TIBdkK6l8,220
1389
1391
  zrb/task_env/env.py,sha256=C9IzavEtWlpap4C92XONi1ID-RK9gDDLQKl5rYGBsyc,5195
@@ -1401,8 +1403,8 @@ zrb/task_input/int_input.py,sha256=d2fXcm5fCo09472eMAm6PdzLQD82ZBV9ARq5CjKepAo,4
1401
1403
  zrb/task_input/password_input.py,sha256=g_g8ZWAzDaHx4h2EHY3UCGvTigC6esAUBzXU0T9nDUk,4192
1402
1404
  zrb/task_input/str_input.py,sha256=BNflOhrJvST9bWK0rGdCi7C7y-QDvHj9ISQMRmujIWU,4200
1403
1405
  zrb/task_input/task_input.py,sha256=x1sGHsoSYAYMdQBrCLmcvZa_ZmGggMPj3goAQzewUKI,2181
1404
- zrb-0.16.0.dist-info/LICENSE,sha256=WfnGCl8G60EYOPAEkuc8C9m9pdXWDe08NsKj3TBbxsM,728
1405
- zrb-0.16.0.dist-info/METADATA,sha256=jDQM9ixJuvZpigI0o7T6LhNsL15-71CxyWhgAdbyfCQ,16460
1406
- zrb-0.16.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
1407
- zrb-0.16.0.dist-info/entry_points.txt,sha256=xTgXc1kBKYhJHEujdaSPHUcJT3-hbyP1mLgwkv-5sSk,40
1408
- zrb-0.16.0.dist-info/RECORD,,
1406
+ zrb-0.17.1.dist-info/LICENSE,sha256=WfnGCl8G60EYOPAEkuc8C9m9pdXWDe08NsKj3TBbxsM,728
1407
+ zrb-0.17.1.dist-info/METADATA,sha256=hswyEpXbHtSdNOnq4_PQPuDruzGo9795YOZpSi6lkfM,16460
1408
+ zrb-0.17.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
1409
+ zrb-0.17.1.dist-info/entry_points.txt,sha256=xTgXc1kBKYhJHEujdaSPHUcJT3-hbyP1mLgwkv-5sSk,40
1410
+ zrb-0.17.1.dist-info/RECORD,,
File without changes
File without changes