zrb 0.0.117__py3-none-any.whl → 0.0.119__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/builtin/__init__.py +2 -2
- zrb/builtin/helper/__init__.py +0 -0
- zrb/builtin/helper/reccuring_action.py +46 -0
- zrb/builtin/schedule.py +12 -22
- zrb/builtin/watch_changes.py +31 -0
- zrb/helper/env_map/fetch.py +6 -6
- zrb/shell-scripts/notify.ps1 +16 -0
- zrb/task/base_remote_cmd_task.py +4 -1
- zrb/task/base_task/__init__.py +0 -0
- zrb/task/{base_task.py → base_task/base_task.py} +62 -201
- zrb/task/base_task/component/__init__.py +0 -0
- zrb/task/base_task/component/base_task_model.py +258 -0
- zrb/task/base_task/component/common_task_model.py +282 -0
- zrb/task/base_task/component/pid_model.py +17 -0
- zrb/task/base_task/component/renderer.py +119 -0
- zrb/task/base_task/component/trackers.py +76 -0
- zrb/task/checker.py +4 -1
- zrb/task/cmd_task.py +56 -36
- zrb/task/docker_compose_task.py +23 -19
- zrb/task/flow_task.py +1 -1
- zrb/task/notifier.py +157 -0
- zrb/task/recurring_task.py +20 -5
- zrb/task/resource_maker.py +1 -1
- zrb/task/task.py +1 -1
- zrb/task/time_watcher.py +2 -2
- zrb/task_env/env.py +26 -14
- zrb/task_env/env_file.py +15 -15
- zrb/task_input/base_input.py +2 -2
- {zrb-0.0.117.dist-info → zrb-0.0.119.dist-info}/METADATA +2 -2
- {zrb-0.0.117.dist-info → zrb-0.0.119.dist-info}/RECORD +33 -23
- zrb/builtin/watch.py +0 -44
- zrb/task/base_task_composite.py +0 -558
- {zrb-0.0.117.dist-info → zrb-0.0.119.dist-info}/LICENSE +0 -0
- {zrb-0.0.117.dist-info → zrb-0.0.119.dist-info}/WHEEL +0 -0
- {zrb-0.0.117.dist-info → zrb-0.0.119.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,258 @@
|
|
1
|
+
from zrb.helper.typing import (
|
2
|
+
Any, Callable, Iterable, List, Mapping, Optional, Union
|
3
|
+
)
|
4
|
+
from zrb.helper.typecheck import typechecked
|
5
|
+
from zrb.config.config import show_time
|
6
|
+
from zrb.task.any_task import AnyTask
|
7
|
+
from zrb.task.any_task_event_handler import (
|
8
|
+
OnTriggered, OnWaiting, OnSkipped, OnStarted, OnReady, OnRetry, OnFailed
|
9
|
+
)
|
10
|
+
from zrb.helper.log import logger
|
11
|
+
from zrb.helper.accessories.color import colored
|
12
|
+
from zrb.task_input.any_input import AnyInput
|
13
|
+
from zrb.task_group.group import Group
|
14
|
+
from zrb.task_env.env import Env
|
15
|
+
from zrb.task_env.env_file import EnvFile
|
16
|
+
from zrb.task.base_task.component.common_task_model import CommonTaskModel
|
17
|
+
from zrb.task.base_task.component.pid_model import PidModel
|
18
|
+
from zrb.task.base_task.component.trackers import TimeTracker
|
19
|
+
from zrb.config.config import env_prefix
|
20
|
+
from zrb.helper.string.modification import double_quote
|
21
|
+
from zrb.helper.string.conversion import to_variable_name
|
22
|
+
|
23
|
+
import datetime
|
24
|
+
import os
|
25
|
+
import sys
|
26
|
+
|
27
|
+
LOG_NAME_LENGTH = 20
|
28
|
+
|
29
|
+
|
30
|
+
@typechecked
|
31
|
+
class BaseTaskModel(CommonTaskModel, PidModel, TimeTracker):
|
32
|
+
def __init__(
|
33
|
+
self,
|
34
|
+
name: str,
|
35
|
+
group: Optional[Group] = None,
|
36
|
+
description: str = '',
|
37
|
+
inputs: List[AnyInput] = [],
|
38
|
+
envs: Iterable[Env] = [],
|
39
|
+
env_files: Iterable[EnvFile] = [],
|
40
|
+
icon: Optional[str] = None,
|
41
|
+
color: Optional[str] = None,
|
42
|
+
retry: int = 2,
|
43
|
+
retry_interval: Union[int, float] = 1,
|
44
|
+
upstreams: Iterable[AnyTask] = [],
|
45
|
+
checkers: Iterable[AnyTask] = [],
|
46
|
+
checking_interval: Union[int, float] = 0,
|
47
|
+
run: Optional[Callable[..., Any]] = None,
|
48
|
+
on_triggered: Optional[OnTriggered] = None,
|
49
|
+
on_waiting: Optional[OnWaiting] = None,
|
50
|
+
on_skipped: Optional[OnSkipped] = None,
|
51
|
+
on_started: Optional[OnStarted] = None,
|
52
|
+
on_ready: Optional[OnReady] = None,
|
53
|
+
on_retry: Optional[OnRetry] = None,
|
54
|
+
on_failed: Optional[OnFailed] = None,
|
55
|
+
should_execute: Union[bool, str, Callable[..., bool]] = True,
|
56
|
+
return_upstream_result: bool = False
|
57
|
+
):
|
58
|
+
self.__rjust_full_cmd_name: Optional[str] = None
|
59
|
+
self.__has_cli_interface = False
|
60
|
+
self.__complete_name: Optional[str] = None
|
61
|
+
CommonTaskModel.__init__(
|
62
|
+
self,
|
63
|
+
name=name,
|
64
|
+
group=group,
|
65
|
+
description=description,
|
66
|
+
inputs=inputs,
|
67
|
+
envs=envs,
|
68
|
+
env_files=env_files,
|
69
|
+
icon=icon,
|
70
|
+
color=color,
|
71
|
+
retry=retry,
|
72
|
+
retry_interval=retry_interval,
|
73
|
+
upstreams=upstreams,
|
74
|
+
checkers=checkers,
|
75
|
+
checking_interval=checking_interval,
|
76
|
+
run=run,
|
77
|
+
on_triggered=on_triggered,
|
78
|
+
on_waiting=on_waiting,
|
79
|
+
on_skipped=on_skipped,
|
80
|
+
on_started=on_started,
|
81
|
+
on_ready=on_ready,
|
82
|
+
on_retry=on_retry,
|
83
|
+
on_failed=on_failed,
|
84
|
+
should_execute=should_execute,
|
85
|
+
return_upstream_result=return_upstream_result
|
86
|
+
)
|
87
|
+
PidModel.__init__(self)
|
88
|
+
TimeTracker.__init__(self)
|
89
|
+
self.__args: List[Any] = []
|
90
|
+
self.__kwargs: Mapping[str, Any] = {}
|
91
|
+
|
92
|
+
def _set_args(self, args: Iterable[Any]):
|
93
|
+
self.__args = list(args)
|
94
|
+
|
95
|
+
def _set_kwargs(self, kwargs: Mapping[str, Any]):
|
96
|
+
self.__kwargs = kwargs
|
97
|
+
|
98
|
+
def log_debug(self, message: Any):
|
99
|
+
prefix = self.__get_log_prefix()
|
100
|
+
colored_message = colored(
|
101
|
+
f'{prefix} • {message}', attrs=['dark']
|
102
|
+
)
|
103
|
+
logger.debug(colored_message)
|
104
|
+
|
105
|
+
def log_warn(self, message: Any):
|
106
|
+
prefix = self.__get_log_prefix()
|
107
|
+
colored_message = colored(
|
108
|
+
f'{prefix} • {message}', attrs=['dark']
|
109
|
+
)
|
110
|
+
logger.warning(colored_message)
|
111
|
+
|
112
|
+
def log_info(self, message: Any):
|
113
|
+
prefix = self.__get_log_prefix()
|
114
|
+
colored_message = colored(
|
115
|
+
f'{prefix} • {message}', attrs=['dark']
|
116
|
+
)
|
117
|
+
logger.info(colored_message)
|
118
|
+
|
119
|
+
def log_error(self, message: Any):
|
120
|
+
prefix = self.__get_log_prefix()
|
121
|
+
colored_message = colored(
|
122
|
+
f'{prefix} • {message}', color='red', attrs=['bold']
|
123
|
+
)
|
124
|
+
logger.error(colored_message, exc_info=True)
|
125
|
+
|
126
|
+
def log_critical(self, message: Any):
|
127
|
+
prefix = self.__get_log_prefix()
|
128
|
+
colored_message = colored(
|
129
|
+
f'{prefix} • {message}', color='red', attrs=['bold']
|
130
|
+
)
|
131
|
+
logger.critical(colored_message, exc_info=True)
|
132
|
+
|
133
|
+
def print_out(self, message: Any, trim_message: bool = True):
|
134
|
+
prefix = self.__get_colored_print_prefix()
|
135
|
+
message_str = f'{message}'.rstrip() if trim_message else f'{message}'
|
136
|
+
print(f'🤖 ○ {prefix} • {message_str}', file=sys.stderr)
|
137
|
+
sys.stderr.flush()
|
138
|
+
|
139
|
+
def print_err(self, message: Any, trim_message: bool = True):
|
140
|
+
prefix = self.__get_colored_print_prefix()
|
141
|
+
message_str = f'{message}'.rstrip() if trim_message else f'{message}'
|
142
|
+
print(f'🤖 △ {prefix} • {message_str}', file=sys.stderr)
|
143
|
+
sys.stderr.flush()
|
144
|
+
|
145
|
+
def print_out_dark(self, message: Any, trim_message: bool = True):
|
146
|
+
message_str = f'{message}'
|
147
|
+
self.print_out(colored(message_str, attrs=['dark']), trim_message)
|
148
|
+
|
149
|
+
def _print_result(self, result: Any):
|
150
|
+
if result is None:
|
151
|
+
return
|
152
|
+
if self._return_upstream_result:
|
153
|
+
# if _return_upstream_result, result is list (see: self._run_all)
|
154
|
+
upstreams = self._get_upstreams()
|
155
|
+
upstream_results = list(result)
|
156
|
+
for upstream_index, upstream_result in enumerate(upstream_results):
|
157
|
+
upstreams[upstream_index]._print_result(upstream_result)
|
158
|
+
return
|
159
|
+
self.print_result(result)
|
160
|
+
|
161
|
+
def print_result(self, result: Any):
|
162
|
+
'''
|
163
|
+
Print result to stdout so that it can be processed further.
|
164
|
+
e.g.: echo $(zrb explain solid) > solid-principle.txt
|
165
|
+
|
166
|
+
You need to override this method
|
167
|
+
if you want to show the result differently.
|
168
|
+
'''
|
169
|
+
print(result)
|
170
|
+
|
171
|
+
def _play_bell(self):
|
172
|
+
print('\a', end='', file=sys.stderr, flush=True)
|
173
|
+
|
174
|
+
def _show_done_info(self):
|
175
|
+
elapsed_time = self._get_elapsed_time()
|
176
|
+
self.print_out_dark(f'Completed in {elapsed_time} seconds')
|
177
|
+
self._play_bell()
|
178
|
+
|
179
|
+
def _show_env_prefix(self):
|
180
|
+
if env_prefix == '':
|
181
|
+
return
|
182
|
+
colored_env_prefix = colored(env_prefix, color='yellow')
|
183
|
+
colored_label = colored('Your current environment: ', attrs=['dark'])
|
184
|
+
print(colored(f'{colored_label}{colored_env_prefix}'), file=sys.stderr)
|
185
|
+
|
186
|
+
def _show_run_command(self):
|
187
|
+
params: List[str] = [double_quote(arg) for arg in self.__args]
|
188
|
+
for task_input in self._get_combined_inputs():
|
189
|
+
if task_input.is_hidden():
|
190
|
+
continue
|
191
|
+
key = task_input.get_name()
|
192
|
+
kwarg_key = to_variable_name(key)
|
193
|
+
quoted_value = double_quote(str(self.__kwargs[kwarg_key]))
|
194
|
+
params.append(f'--{key} {quoted_value}')
|
195
|
+
run_cmd = self._get_full_cmd_name()
|
196
|
+
run_cmd_with_param = run_cmd
|
197
|
+
if len(params) > 0:
|
198
|
+
param_str = ' '.join(params)
|
199
|
+
run_cmd_with_param += ' ' + param_str
|
200
|
+
colored_command = colored(run_cmd_with_param, color='yellow')
|
201
|
+
colored_label = colored('To run again: ', attrs=['dark'])
|
202
|
+
print(colored(f'{colored_label}{colored_command}'), file=sys.stderr)
|
203
|
+
|
204
|
+
def __get_colored_print_prefix(self) -> str:
|
205
|
+
return self.__get_colored(self.__get_print_prefix())
|
206
|
+
|
207
|
+
def __get_colored(self, text: str) -> str:
|
208
|
+
return colored(text, color=self.get_color())
|
209
|
+
|
210
|
+
def __get_print_prefix(self) -> str:
|
211
|
+
common_prefix = self.__get_common_prefix(show_time=show_time)
|
212
|
+
icon = self.get_icon()
|
213
|
+
rjust_cmd_name = self.__get_rjust_full_cmd_name()
|
214
|
+
return f'{common_prefix} {icon} {rjust_cmd_name}'
|
215
|
+
|
216
|
+
def __get_log_prefix(self) -> str:
|
217
|
+
common_prefix = self.__get_common_prefix(show_time=False)
|
218
|
+
icon = self.get_icon()
|
219
|
+
filled_name = self.__get_rjust_full_cmd_name()
|
220
|
+
return f'{common_prefix} {icon} {filled_name}'
|
221
|
+
|
222
|
+
def __get_common_prefix(self, show_time: bool) -> str:
|
223
|
+
attempt = self._get_attempt()
|
224
|
+
max_attempt = self._get_max_attempt()
|
225
|
+
pid = str(self._get_task_pid()).rjust(6)
|
226
|
+
if show_time:
|
227
|
+
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
|
228
|
+
return f'◷ {now} ❁ {pid} → {attempt}/{max_attempt}'
|
229
|
+
return f'❁ {pid} → {attempt}/{max_attempt}'
|
230
|
+
|
231
|
+
def __get_rjust_full_cmd_name(self) -> str:
|
232
|
+
if self.__rjust_full_cmd_name is not None:
|
233
|
+
return self.__rjust_full_cmd_name
|
234
|
+
complete_name = self._get_full_cmd_name()
|
235
|
+
self.__rjust_full_cmd_name = complete_name.rjust(LOG_NAME_LENGTH, ' ')
|
236
|
+
return self.__rjust_full_cmd_name
|
237
|
+
|
238
|
+
def __get_executable_name(self) -> str:
|
239
|
+
if len(sys.argv) > 0 and sys.argv[0] != '':
|
240
|
+
return os.path.basename(sys.argv[0])
|
241
|
+
return 'zrb'
|
242
|
+
|
243
|
+
def _get_full_cmd_name(self) -> str:
|
244
|
+
if self.__complete_name is not None:
|
245
|
+
return self.__complete_name
|
246
|
+
executable_prefix = ''
|
247
|
+
if self.__has_cli_interface:
|
248
|
+
executable_prefix += self.__get_executable_name() + ' '
|
249
|
+
cmd_name = self.get_cmd_name()
|
250
|
+
if self._group is None:
|
251
|
+
self.__complete_name = f'{executable_prefix}{cmd_name}'
|
252
|
+
return self.__complete_name
|
253
|
+
group_cmd_name = self._group.get_complete_name()
|
254
|
+
self.__complete_name = f'{executable_prefix}{group_cmd_name} {cmd_name}' # noqa
|
255
|
+
return self.__complete_name
|
256
|
+
|
257
|
+
def _set_has_cli_interface(self):
|
258
|
+
self.__has_cli_interface = True
|
@@ -0,0 +1,282 @@
|
|
1
|
+
from zrb.helper.typing import (
|
2
|
+
Any, Callable, Iterable, List, Mapping, Optional, Union
|
3
|
+
)
|
4
|
+
from zrb.helper.typecheck import typechecked
|
5
|
+
from zrb.task.any_task_event_handler import (
|
6
|
+
OnTriggered, OnWaiting, OnSkipped, OnStarted, OnReady, OnRetry, OnFailed
|
7
|
+
)
|
8
|
+
from zrb.task.any_task import AnyTask
|
9
|
+
from zrb.helper.string.conversion import to_cmd_name
|
10
|
+
from zrb.helper.accessories.color import get_random_color
|
11
|
+
from zrb.helper.accessories.icon import get_random_icon
|
12
|
+
from zrb.helper.util import coalesce_str
|
13
|
+
from zrb.task_input.any_input import AnyInput
|
14
|
+
from zrb.task_group.group import Group
|
15
|
+
from zrb.task_env.constant import RESERVED_ENV_NAMES
|
16
|
+
from zrb.task_env.env import Env
|
17
|
+
from zrb.task_env.env_file import EnvFile
|
18
|
+
|
19
|
+
import os
|
20
|
+
|
21
|
+
|
22
|
+
@typechecked
|
23
|
+
class CommonTaskModel():
|
24
|
+
def __init__(
|
25
|
+
self,
|
26
|
+
name: str,
|
27
|
+
group: Optional[Group] = None,
|
28
|
+
description: str = '',
|
29
|
+
inputs: List[AnyInput] = [],
|
30
|
+
envs: Iterable[Env] = [],
|
31
|
+
env_files: Iterable[EnvFile] = [],
|
32
|
+
icon: Optional[str] = None,
|
33
|
+
color: Optional[str] = None,
|
34
|
+
retry: int = 2,
|
35
|
+
retry_interval: Union[float, int] = 1,
|
36
|
+
upstreams: Iterable[AnyTask] = [],
|
37
|
+
checkers: Iterable[AnyTask] = [],
|
38
|
+
checking_interval: Union[float, int] = 0,
|
39
|
+
run: Optional[Callable[..., Any]] = None,
|
40
|
+
on_triggered: Optional[OnTriggered] = None,
|
41
|
+
on_waiting: Optional[OnWaiting] = None,
|
42
|
+
on_skipped: Optional[OnSkipped] = None,
|
43
|
+
on_started: Optional[OnStarted] = None,
|
44
|
+
on_ready: Optional[OnReady] = None,
|
45
|
+
on_retry: Optional[OnRetry] = None,
|
46
|
+
on_failed: Optional[OnFailed] = None,
|
47
|
+
should_execute: Union[bool, str, Callable[..., bool]] = True,
|
48
|
+
return_upstream_result: bool = False
|
49
|
+
):
|
50
|
+
self._name = name
|
51
|
+
self._group = group
|
52
|
+
if group is not None:
|
53
|
+
group.add_task(self)
|
54
|
+
self._description = coalesce_str(description, name)
|
55
|
+
self._inputs = inputs
|
56
|
+
self._envs = envs
|
57
|
+
self._env_files = env_files
|
58
|
+
self._icon = coalesce_str(icon, get_random_icon())
|
59
|
+
self._color = coalesce_str(color, get_random_color())
|
60
|
+
self._retry = retry
|
61
|
+
self._retry_interval = retry_interval
|
62
|
+
self._upstreams = upstreams
|
63
|
+
self._checkers = checkers
|
64
|
+
self._checking_interval = checking_interval
|
65
|
+
self._run_function: Optional[Callable[..., Any]] = run
|
66
|
+
self._on_triggered = on_triggered
|
67
|
+
self._on_waiting = on_waiting
|
68
|
+
self._on_skipped = on_skipped
|
69
|
+
self._on_started = on_started
|
70
|
+
self._on_ready = on_ready
|
71
|
+
self._on_retry = on_retry
|
72
|
+
self._on_failed = on_failed
|
73
|
+
self._should_execute = should_execute
|
74
|
+
self._return_upstream_result = return_upstream_result
|
75
|
+
self.__execution_id = ''
|
76
|
+
self.__allow_add_envs = True
|
77
|
+
self.__allow_add_env_files = True
|
78
|
+
self.__allow_add_inputs = True
|
79
|
+
self.__allow_add_upstreams: bool = True
|
80
|
+
self.__has_already_inject_env_files: bool = False
|
81
|
+
self.__has_already_inject_envs: bool = False
|
82
|
+
self.__has_already_inject_inputs: bool = False
|
83
|
+
self.__has_already_inject_checkers: bool = False
|
84
|
+
self.__has_already_inject_upstreams: bool = False
|
85
|
+
self.__all_inputs: Optional[List[AnyInput]] = None
|
86
|
+
|
87
|
+
def _lock_upstreams(self):
|
88
|
+
self.__allow_add_upstreams = False
|
89
|
+
|
90
|
+
def _set_execution_id(self, execution_id: str):
|
91
|
+
if self.__execution_id == '':
|
92
|
+
self.__execution_id = execution_id
|
93
|
+
|
94
|
+
def _propagate_execution_id(self):
|
95
|
+
execution_id = self.get_execution_id()
|
96
|
+
for upstream_task in self._get_upstreams():
|
97
|
+
upstream_task._set_execution_id(execution_id)
|
98
|
+
upstream_task._propagate_execution_id()
|
99
|
+
for checker_task in self._get_checkers():
|
100
|
+
checker_task._set_execution_id(execution_id)
|
101
|
+
checker_task._propagate_execution_id()
|
102
|
+
|
103
|
+
def get_execution_id(self) -> str:
|
104
|
+
return self.__execution_id
|
105
|
+
|
106
|
+
def set_name(self, new_name: str):
|
107
|
+
if self._description == self._name:
|
108
|
+
self._description = new_name
|
109
|
+
self._name = new_name
|
110
|
+
|
111
|
+
def get_cmd_name(self) -> str:
|
112
|
+
return to_cmd_name(self._name)
|
113
|
+
|
114
|
+
def set_description(self, new_description: str):
|
115
|
+
self._description = new_description
|
116
|
+
|
117
|
+
def get_description(self) -> str:
|
118
|
+
return self._description
|
119
|
+
|
120
|
+
def set_icon(self, new_icon: str):
|
121
|
+
self._icon = new_icon
|
122
|
+
|
123
|
+
def set_color(self, new_color: str):
|
124
|
+
self._color = new_color
|
125
|
+
|
126
|
+
def set_retry(self, new_retry: int):
|
127
|
+
self._retry = new_retry
|
128
|
+
|
129
|
+
def set_should_execute(
|
130
|
+
self, should_execute: Union[bool, str, Callable[..., bool]]
|
131
|
+
):
|
132
|
+
self._should_execute = should_execute
|
133
|
+
|
134
|
+
def set_retry_interval(self, new_retry_interval: Union[float, int]):
|
135
|
+
self._retry_interval = new_retry_interval
|
136
|
+
|
137
|
+
def set_checking_interval(self, new_checking_interval: Union[float, int]):
|
138
|
+
self._checking_interval = new_checking_interval
|
139
|
+
|
140
|
+
def insert_input(self, *inputs: AnyInput):
|
141
|
+
if not self.__allow_add_inputs:
|
142
|
+
raise Exception(f'Cannot insert inputs for `{self._name}`')
|
143
|
+
self._inputs = list(inputs) + list(self._inputs)
|
144
|
+
|
145
|
+
def add_input(self, *inputs: AnyInput):
|
146
|
+
if not self.__allow_add_inputs:
|
147
|
+
raise Exception(f'Cannot add inputs for `{self._name}`')
|
148
|
+
self._inputs = list(self._inputs) + list(inputs)
|
149
|
+
|
150
|
+
def inject_inputs(self):
|
151
|
+
pass
|
152
|
+
|
153
|
+
def _get_inputs(self) -> List[AnyInput]:
|
154
|
+
if not self.__has_already_inject_inputs:
|
155
|
+
self.inject_inputs()
|
156
|
+
self.__has_already_inject_inputs = True
|
157
|
+
return list(self._inputs)
|
158
|
+
|
159
|
+
def _get_combined_inputs(self) -> Iterable[AnyInput]:
|
160
|
+
''''
|
161
|
+
Getting all inputs of this task and all its upstream, non-duplicated.
|
162
|
+
'''
|
163
|
+
if self.__all_inputs is not None:
|
164
|
+
return self.__all_inputs
|
165
|
+
self.__all_inputs: List[AnyInput] = []
|
166
|
+
existing_input_names: Mapping[str, bool] = {}
|
167
|
+
# Add task inputs
|
168
|
+
inputs = self._get_inputs()
|
169
|
+
for input_index, first_occurence_task_input in enumerate(inputs):
|
170
|
+
input_name = first_occurence_task_input.get_name()
|
171
|
+
if input_name in existing_input_names:
|
172
|
+
continue
|
173
|
+
# Look for all input with the same name in the current task
|
174
|
+
task_inputs = [
|
175
|
+
candidate
|
176
|
+
for candidate in inputs[input_index:]
|
177
|
+
if candidate.get_name() == input_name
|
178
|
+
]
|
179
|
+
# Get the last input, and add it to _all_inputs
|
180
|
+
task_input = task_inputs[-1]
|
181
|
+
self.__all_inputs.append(task_input)
|
182
|
+
existing_input_names[input_name] = True
|
183
|
+
# Add upstream inputs
|
184
|
+
for upstream in self._get_upstreams():
|
185
|
+
upstream_inputs = upstream._get_combined_inputs()
|
186
|
+
for upstream_input in upstream_inputs:
|
187
|
+
if upstream_input.get_name() in existing_input_names:
|
188
|
+
continue
|
189
|
+
self.__all_inputs.append(upstream_input)
|
190
|
+
existing_input_names[upstream_input.get_name()] = True
|
191
|
+
self._lock_upstreams()
|
192
|
+
self.__allow_add_inputs = False
|
193
|
+
return self.__all_inputs
|
194
|
+
|
195
|
+
def insert_env(self, *envs: Env):
|
196
|
+
if not self.__allow_add_envs:
|
197
|
+
raise Exception(f'Cannot insert envs to `{self._name}`')
|
198
|
+
self._envs = list(envs) + list(self._envs)
|
199
|
+
|
200
|
+
def add_env(self, *envs: Env):
|
201
|
+
if not self.__allow_add_envs:
|
202
|
+
raise Exception(f'Cannot add envs to `{self._name}`')
|
203
|
+
self._envs = list(self._envs) + list(envs)
|
204
|
+
|
205
|
+
def inject_envs(self):
|
206
|
+
pass
|
207
|
+
|
208
|
+
def _get_envs(self) -> List[Env]:
|
209
|
+
if not self.__has_already_inject_envs:
|
210
|
+
self.inject_envs()
|
211
|
+
self.__has_already_inject_envs = True
|
212
|
+
return list(self._envs)
|
213
|
+
|
214
|
+
def _get_combined_env(self) -> Mapping[str, Env]:
|
215
|
+
all_envs: Mapping[str, Env] = {}
|
216
|
+
for env_name in os.environ:
|
217
|
+
if env_name in RESERVED_ENV_NAMES:
|
218
|
+
continue
|
219
|
+
all_envs[env_name] = Env(
|
220
|
+
name=env_name, os_name=env_name, should_render=False
|
221
|
+
)
|
222
|
+
for env_file in self._get_env_files():
|
223
|
+
for env in env_file.get_envs():
|
224
|
+
all_envs[env.get_name()] = env
|
225
|
+
for env in self._get_envs():
|
226
|
+
all_envs[env.get_name()] = env
|
227
|
+
self.__allow_add_envs = False
|
228
|
+
self.__allow_add_env_files = False
|
229
|
+
return all_envs
|
230
|
+
|
231
|
+
def insert_env_file(self, *env_files: EnvFile):
|
232
|
+
if not self.__allow_add_env_files:
|
233
|
+
raise Exception(f'Cannot insert env_files to `{self._name}`')
|
234
|
+
self._env_files = list(env_files) + list(self._env_files)
|
235
|
+
|
236
|
+
def add_env_file(self, *env_files: EnvFile):
|
237
|
+
if not self.__allow_add_env_files:
|
238
|
+
raise Exception(f'Cannot add env_files to `{self._name}`')
|
239
|
+
self._env_files = list(self._env_files) + list(env_files)
|
240
|
+
|
241
|
+
def inject_env_files(self):
|
242
|
+
pass
|
243
|
+
|
244
|
+
def insert_upstream(self, *upstreams: AnyTask):
|
245
|
+
if not self.__allow_add_upstreams:
|
246
|
+
raise Exception(f'Cannot insert upstreams to `{self._name}`')
|
247
|
+
self._upstreams = list(upstreams) + list(self._upstreams)
|
248
|
+
|
249
|
+
def add_upstream(self, *upstreams: AnyTask):
|
250
|
+
if not self.__allow_add_upstreams:
|
251
|
+
raise Exception(f'Cannot add upstreams to `{self._name}`')
|
252
|
+
self._upstreams = list(self._upstreams) + list(upstreams)
|
253
|
+
|
254
|
+
def inject_upstreams(self):
|
255
|
+
pass
|
256
|
+
|
257
|
+
def _get_upstreams(self) -> List[AnyTask]:
|
258
|
+
if not self.__has_already_inject_upstreams:
|
259
|
+
self.inject_upstreams()
|
260
|
+
self.__has_already_inject_upstreams = True
|
261
|
+
return list(self._upstreams)
|
262
|
+
|
263
|
+
def get_icon(self) -> str:
|
264
|
+
return self._icon
|
265
|
+
|
266
|
+
def get_color(self) -> str:
|
267
|
+
return self._color
|
268
|
+
|
269
|
+
def _get_env_files(self) -> List[EnvFile]:
|
270
|
+
if not self.__has_already_inject_env_files:
|
271
|
+
self.inject_env_files()
|
272
|
+
self.__has_already_inject_env_files = True
|
273
|
+
return self._env_files
|
274
|
+
|
275
|
+
def inject_checkers(self):
|
276
|
+
pass
|
277
|
+
|
278
|
+
def _get_checkers(self) -> List[AnyTask]:
|
279
|
+
if not self.__has_already_inject_checkers:
|
280
|
+
self.inject_checkers()
|
281
|
+
self.__has_already_inject_checkers = True
|
282
|
+
return list(self._checkers)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from zrb.helper.typecheck import typechecked
|
2
|
+
|
3
|
+
import os
|
4
|
+
|
5
|
+
|
6
|
+
@typechecked
|
7
|
+
class PidModel():
|
8
|
+
|
9
|
+
def __init__(self):
|
10
|
+
self.__task_pid: int = os.getpid()
|
11
|
+
|
12
|
+
def _set_task_pid(self, pid: int):
|
13
|
+
self.__task_pid = pid
|
14
|
+
|
15
|
+
def _get_task_pid(self) -> int:
|
16
|
+
return self.__task_pid
|
17
|
+
|
@@ -0,0 +1,119 @@
|
|
1
|
+
from zrb.helper.typing import Any, Mapping, Optional, Union
|
2
|
+
from zrb.helper.typecheck import typechecked
|
3
|
+
from zrb.helper.string.conversion import to_boolean
|
4
|
+
from zrb.helper.string.jinja import is_probably_jinja
|
5
|
+
from zrb.helper.render_data import DEFAULT_RENDER_DATA
|
6
|
+
|
7
|
+
import os
|
8
|
+
import jinja2
|
9
|
+
|
10
|
+
|
11
|
+
class AnyExtensionFileSystemLoader(jinja2.FileSystemLoader):
|
12
|
+
def get_source(self, environment, template):
|
13
|
+
for search_dir in self.searchpath:
|
14
|
+
file_path = os.path.join(search_dir, template)
|
15
|
+
if os.path.exists(file_path):
|
16
|
+
with open(file_path, 'r') as file:
|
17
|
+
contents = file.read()
|
18
|
+
return contents, file_path, lambda: False
|
19
|
+
raise jinja2.TemplateNotFound(template)
|
20
|
+
|
21
|
+
|
22
|
+
@typechecked
|
23
|
+
class Renderer():
|
24
|
+
|
25
|
+
def __init__(self):
|
26
|
+
self.__input_map: Mapping[str, Any] = {}
|
27
|
+
self.__env_map: Mapping[str, str] = {}
|
28
|
+
self.__render_data: Optional[Mapping[str, Any]] = None
|
29
|
+
self.__rendered_str: Mapping[str, str] = {}
|
30
|
+
|
31
|
+
def get_input_map(self) -> Mapping[str, Any]:
|
32
|
+
# This return reference to input map, so input map can be updated
|
33
|
+
return self.__input_map
|
34
|
+
|
35
|
+
def _set_input_map(self, key: str, val: Any):
|
36
|
+
self.__input_map[key] = val
|
37
|
+
|
38
|
+
def get_env_map(self) -> Mapping[str, str]:
|
39
|
+
# This return reference to env map, so env map can be updated
|
40
|
+
return self.__env_map
|
41
|
+
|
42
|
+
def _set_env_map(self, key: str, val: str):
|
43
|
+
self.__env_map[key] = val
|
44
|
+
|
45
|
+
def render_any(
|
46
|
+
self, val: Any, data: Optional[Mapping[str, Any]] = None
|
47
|
+
) -> Any:
|
48
|
+
if isinstance(val, str):
|
49
|
+
return self.render_str(val, data)
|
50
|
+
return val
|
51
|
+
|
52
|
+
def render_float(
|
53
|
+
self, val: Union[str, float], data: Optional[Mapping[str, Any]] = None
|
54
|
+
) -> float:
|
55
|
+
if isinstance(val, str):
|
56
|
+
return float(self.render_str(val, data))
|
57
|
+
return val
|
58
|
+
|
59
|
+
def render_int(
|
60
|
+
self, val: Union[str, int], data: Optional[Mapping[str, Any]] = None
|
61
|
+
) -> int:
|
62
|
+
if isinstance(val, str):
|
63
|
+
return int(self.render_str(val, data))
|
64
|
+
return val
|
65
|
+
|
66
|
+
def render_bool(
|
67
|
+
self, val: Union[str, bool], data: Optional[Mapping[str, Any]] = None
|
68
|
+
) -> bool:
|
69
|
+
if isinstance(val, str):
|
70
|
+
return to_boolean(self.render_str(val, data))
|
71
|
+
return val
|
72
|
+
|
73
|
+
def render_str(
|
74
|
+
self, val: str, data: Optional[Mapping[str, Any]] = None
|
75
|
+
) -> str:
|
76
|
+
if val in self.__rendered_str:
|
77
|
+
return self.__rendered_str[val]
|
78
|
+
if not is_probably_jinja(val):
|
79
|
+
return val
|
80
|
+
template = jinja2.Template(val)
|
81
|
+
render_data = self.__get_render_data(additional_data=data)
|
82
|
+
try:
|
83
|
+
rendered_text = template.render(render_data)
|
84
|
+
except Exception:
|
85
|
+
raise Exception(f'Fail to render "{val}" with data: {render_data}')
|
86
|
+
self.__rendered_str[val] = rendered_text
|
87
|
+
return rendered_text
|
88
|
+
|
89
|
+
def render_file(
|
90
|
+
self, location: str, data: Optional[Mapping[str, Any]] = None
|
91
|
+
) -> str:
|
92
|
+
location_dir = os.path.dirname(location)
|
93
|
+
env = jinja2.Environment(
|
94
|
+
loader=AnyExtensionFileSystemLoader([location_dir])
|
95
|
+
)
|
96
|
+
template = env.get_template(location)
|
97
|
+
render_data = self.__get_render_data(additional_data=data)
|
98
|
+
render_data['TEMPLATE_DIR'] = location_dir
|
99
|
+
rendered_text = template.render(render_data)
|
100
|
+
return rendered_text
|
101
|
+
|
102
|
+
def __get_render_data(
|
103
|
+
self, additional_data: Optional[Mapping[str, Any]] = None
|
104
|
+
) -> Mapping[str, Any]:
|
105
|
+
self.__ensure_cached_render_data()
|
106
|
+
if additional_data is None:
|
107
|
+
return self.__render_data
|
108
|
+
return {**self.__render_data, **additional_data}
|
109
|
+
|
110
|
+
def __ensure_cached_render_data(self):
|
111
|
+
if self.__render_data is not None:
|
112
|
+
return self.__render_data
|
113
|
+
render_data = dict(DEFAULT_RENDER_DATA)
|
114
|
+
render_data.update({
|
115
|
+
'env': self.__env_map,
|
116
|
+
'input': self.__input_map,
|
117
|
+
})
|
118
|
+
self.__render_data = render_data
|
119
|
+
return render_data
|