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.
@@ -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