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.
@@ -1,558 +0,0 @@
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.helper.string.conversion import to_boolean, to_cmd_name
8
- from zrb.helper.string.jinja import is_probably_jinja
9
- from zrb.helper.render_data import DEFAULT_RENDER_DATA
10
- from zrb.helper.log import logger
11
- from zrb.helper.accessories.color import colored, get_random_color
12
- from zrb.helper.accessories.icon import get_random_icon
13
- from zrb.helper.util import coalesce_str
14
- from zrb.task_input.any_input import AnyInput
15
- from zrb.task_group.group import Group
16
- from zrb.task_env.env import Env
17
- from zrb.task_env.env_file import EnvFile
18
-
19
- import asyncio
20
- import datetime
21
- import os
22
- import time
23
- import jinja2
24
- import sys
25
-
26
- LOG_NAME_LENGTH = 20
27
-
28
-
29
- @typechecked
30
- class CommonTaskModel():
31
- def __init__(
32
- self,
33
- name: str,
34
- group: Optional[Group] = None,
35
- description: str = '',
36
- inputs: List[AnyInput] = [],
37
- envs: Iterable[Env] = [],
38
- env_files: Iterable[EnvFile] = [],
39
- icon: Optional[str] = None,
40
- color: Optional[str] = None,
41
- retry: int = 2,
42
- retry_interval: Union[float, int] = 1,
43
- upstreams: Iterable[AnyTask] = [],
44
- checkers: Iterable[AnyTask] = [],
45
- checking_interval: Union[float, int] = 0,
46
- run: Optional[Callable[..., Any]] = None,
47
- should_execute: Union[bool, str, Callable[..., bool]] = True
48
- ):
49
- self._name = name
50
- self._group = group
51
- if group is not None:
52
- group.add_task(self)
53
- self._description = coalesce_str(description, name)
54
- self._inputs = inputs
55
- self._envs = envs
56
- self._env_files = env_files
57
- self._icon = coalesce_str(icon, get_random_icon())
58
- self._color = coalesce_str(color, get_random_color())
59
- self._retry = retry
60
- self._retry_interval = retry_interval
61
- self._upstreams = upstreams
62
- self._checkers = checkers
63
- self._checking_interval = checking_interval
64
- self._run_function: Optional[Callable[..., Any]] = run
65
- self._should_execute = should_execute
66
- self._allow_add_envs = True
67
- self._allow_add_env_files = True
68
- self._allow_add_inputs = True
69
- self._allow_add_upstreams: bool = True
70
- self._has_already_inject_env_files: bool = False
71
- self._has_already_inject_envs: bool = False
72
- self._has_already_inject_inputs: bool = False
73
- self._has_already_inject_checkers: bool = False
74
- self._has_already_inject_upstreams: bool = False
75
- self._execution_id = ''
76
-
77
- def _set_execution_id(self, execution_id: str):
78
- if self._execution_id == '':
79
- self._execution_id = execution_id
80
-
81
- def set_name(self, new_name: str):
82
- if self._description == self._name:
83
- self._description = new_name
84
- self._name = new_name
85
-
86
- def set_description(self, new_description: str):
87
- self._description = new_description
88
-
89
- def set_icon(self, new_icon: str):
90
- self._icon = new_icon
91
-
92
- def set_color(self, new_color: str):
93
- self._color = new_color
94
-
95
- def set_retry(self, new_retry: int):
96
- self._retry = new_retry
97
-
98
- def set_should_execute(
99
- self, should_execute: Union[bool, str, Callable[..., bool]]
100
- ):
101
- self._should_execute = should_execute
102
-
103
- def set_retry_interval(self, new_retry_interval: Union[float, int]):
104
- self._retry_interval = new_retry_interval
105
-
106
- def set_checking_interval(self, new_checking_interval: Union[float, int]):
107
- self._checking_interval = new_checking_interval
108
-
109
- def insert_input(self, *inputs: AnyInput):
110
- if not self._allow_add_inputs:
111
- raise Exception(f'Cannot insert inputs for `{self._name}`')
112
- self._inputs = list(inputs) + list(self._inputs)
113
-
114
- def add_input(self, *inputs: AnyInput):
115
- if not self._allow_add_inputs:
116
- raise Exception(f'Cannot add inputs for `{self._name}`')
117
- self._inputs = list(self._inputs) + list(inputs)
118
-
119
- def insert_env(self, *envs: Env):
120
- if not self._allow_add_envs:
121
- raise Exception(f'Cannot insert envs to `{self._name}`')
122
- self._envs = list(envs) + list(self._envs)
123
-
124
- def add_env(self, *envs: Env):
125
- if not self._allow_add_envs:
126
- raise Exception(f'Cannot add envs to `{self._name}`')
127
- self._envs = list(self._envs) + list(envs)
128
-
129
- def insert_env_file(self, *env_files: EnvFile):
130
- if not self._allow_add_env_files:
131
- raise Exception(f'Cannot insert env_files to `{self._name}`')
132
- self._env_files = list(env_files) + list(self._env_files)
133
-
134
- def add_env_file(self, *env_files: EnvFile):
135
- if not self._allow_add_env_files:
136
- raise Exception(f'Cannot add env_files to `{self._name}`')
137
- self._env_files = list(self._env_files) + list(env_files)
138
-
139
- def insert_upstream(self, *upstreams: AnyTask):
140
- if not self._allow_add_upstreams:
141
- raise Exception(f'Cannot insert upstreams to `{self._name}`')
142
- self._upstreams = list(upstreams) + list(self._upstreams)
143
-
144
- def add_upstream(self, *upstreams: AnyTask):
145
- if not self._allow_add_upstreams:
146
- raise Exception(f'Cannot add upstreams to `{self._name}`')
147
- self._upstreams = list(self._upstreams) + list(upstreams)
148
-
149
- def get_execution_id(self) -> str:
150
- return self._execution_id
151
-
152
- def get_icon(self) -> str:
153
- return self._icon
154
-
155
- def get_color(self) -> str:
156
- return self._color
157
-
158
- def inject_env_files(self):
159
- pass
160
-
161
- def _get_env_files(self) -> List[EnvFile]:
162
- if not self._has_already_inject_env_files:
163
- self.inject_env_files()
164
- self._has_already_inject_env_files = True
165
- return self._env_files
166
-
167
- def inject_envs(self):
168
- pass
169
-
170
- def _get_envs(self) -> List[Env]:
171
- if not self._has_already_inject_envs:
172
- self.inject_envs()
173
- self._has_already_inject_envs = True
174
- return list(self._envs)
175
-
176
- def inject_inputs(self):
177
- pass
178
-
179
- def _get_inputs(self) -> List[AnyInput]:
180
- if not self._has_already_inject_inputs:
181
- self.inject_inputs()
182
- self._has_already_inject_inputs = True
183
- return list(self._inputs)
184
-
185
- def inject_checkers(self):
186
- pass
187
-
188
- def _get_checkers(self) -> List[AnyTask]:
189
- if not self._has_already_inject_checkers:
190
- self.inject_checkers()
191
- self._has_already_inject_checkers = True
192
- return list(self._checkers)
193
-
194
- def inject_upstreams(self):
195
- pass
196
-
197
- def _get_upstreams(self) -> List[AnyTask]:
198
- if not self._has_already_inject_upstreams:
199
- self.inject_upstreams()
200
- self._has_already_inject_upstreams = True
201
- return list(self._upstreams)
202
-
203
- def get_description(self) -> str:
204
- return self._description
205
-
206
- def get_cmd_name(self) -> str:
207
- return to_cmd_name(self._name)
208
-
209
-
210
- @typechecked
211
- class TimeTracker():
212
-
213
- def __init__(self):
214
- self.__start_time: float = 0
215
- self.__end_time: float = 0
216
-
217
- def _start_timer(self):
218
- self.__start_time = time.time()
219
-
220
- def _end_timer(self):
221
- self.__end_time = time.time()
222
-
223
- def _get_elapsed_time(self) -> float:
224
- return self.__end_time - self.__start_time
225
-
226
-
227
- @typechecked
228
- class AttemptTracker():
229
-
230
- def __init__(self, retry: int = 2):
231
- self.__retry = retry
232
- self.__attempt: int = 1
233
- self.__no_more_attempt: bool = False
234
-
235
- def _get_max_attempt(self) -> int:
236
- return self.__retry + 1
237
-
238
- def _get_attempt(self) -> int:
239
- return self.__attempt
240
-
241
- def _increase_attempt(self):
242
- self.__attempt += 1
243
-
244
- def _should_attempt(self) -> bool:
245
- attempt = self._get_attempt()
246
- max_attempt = self._get_max_attempt()
247
- return attempt <= max_attempt
248
-
249
- def _is_last_attempt(self) -> bool:
250
- attempt = self._get_attempt()
251
- max_attempt = self._get_max_attempt()
252
- return attempt >= max_attempt
253
-
254
-
255
- @typechecked
256
- class FinishTracker():
257
-
258
- def __init__(self):
259
- self.__execution_queue: Optional[asyncio.Queue] = None
260
- self.__counter = 0
261
-
262
- async def _mark_awaited(self):
263
- if self.__execution_queue is None:
264
- self.__execution_queue = asyncio.Queue()
265
- self.__counter += 1
266
-
267
- async def _mark_done(self):
268
- # Tracker might be started several times
269
- # However, when the execution is marked as done, it applied globally
270
- # Thus, we need to send event as much as the counter.
271
- for i in range(self.__counter):
272
- await self.__execution_queue.put(True)
273
-
274
- async def _is_done(self) -> bool:
275
- while self.__execution_queue is None:
276
- await asyncio.sleep(0.05)
277
- return await self.__execution_queue.get()
278
-
279
-
280
- @typechecked
281
- class PidModel():
282
-
283
- def __init__(self):
284
- self.__task_pid: int = os.getpid()
285
-
286
- def _set_task_pid(self, pid: int):
287
- self.__task_pid = pid
288
-
289
- def _get_task_pid(self) -> int:
290
- return self.__task_pid
291
-
292
-
293
- class AnyExtensionFileSystemLoader(jinja2.FileSystemLoader):
294
- def get_source(self, environment, template):
295
- for search_dir in self.searchpath:
296
- file_path = os.path.join(search_dir, template)
297
- if os.path.exists(file_path):
298
- with open(file_path, 'r') as file:
299
- contents = file.read()
300
- return contents, file_path, lambda: False
301
- raise jinja2.TemplateNotFound(template)
302
-
303
-
304
- @typechecked
305
- class Renderer():
306
-
307
- def __init__(self):
308
- self.__input_map: Mapping[str, Any] = {}
309
- self.__env_map: Mapping[str, str] = {}
310
- self.__render_data: Optional[Mapping[str, Any]] = None
311
- self.__rendered_str: Mapping[str, str] = {}
312
-
313
- def get_input_map(self) -> Mapping[str, Any]:
314
- # This return reference to input map, so input map can be updated
315
- return self.__input_map
316
-
317
- def _set_input_map(self, key: str, val: Any):
318
- self.__input_map[key] = val
319
-
320
- def get_env_map(self) -> Mapping[str, str]:
321
- # This return reference to env map, so env map can be updated
322
- return self.__env_map
323
-
324
- def _set_env_map(self, key: str, val: str):
325
- self.__env_map[key] = val
326
-
327
- def render_any(
328
- self, val: Any, data: Optional[Mapping[str, Any]] = None
329
- ) -> Any:
330
- if isinstance(val, str):
331
- return self.render_str(val, data)
332
- return val
333
-
334
- def render_float(
335
- self, val: Union[str, float], data: Optional[Mapping[str, Any]] = None
336
- ) -> float:
337
- if isinstance(val, str):
338
- return float(self.render_str(val, data))
339
- return val
340
-
341
- def render_int(
342
- self, val: Union[str, int], data: Optional[Mapping[str, Any]] = None
343
- ) -> int:
344
- if isinstance(val, str):
345
- return int(self.render_str(val, data))
346
- return val
347
-
348
- def render_bool(
349
- self, val: Union[str, bool], data: Optional[Mapping[str, Any]] = None
350
- ) -> bool:
351
- if isinstance(val, str):
352
- return to_boolean(self.render_str(val, data))
353
- return val
354
-
355
- def render_str(
356
- self, val: str, data: Optional[Mapping[str, Any]] = None
357
- ) -> str:
358
- if val in self.__rendered_str:
359
- return self.__rendered_str[val]
360
- if not is_probably_jinja(val):
361
- return val
362
- template = jinja2.Template(val)
363
- render_data = self._get_render_data(additional_data=data)
364
- try:
365
- rendered_text = template.render(render_data)
366
- except Exception:
367
- raise Exception(f'Fail to render "{val}" with data: {render_data}')
368
- self.__rendered_str[val] = rendered_text
369
- return rendered_text
370
-
371
- def render_file(
372
- self, location: str, data: Optional[Mapping[str, Any]] = None
373
- ) -> str:
374
- location_dir = os.path.dirname(location)
375
- env = jinja2.Environment(
376
- loader=AnyExtensionFileSystemLoader([location_dir])
377
- )
378
- template = env.get_template(location)
379
- render_data = self._get_render_data(additional_data=data)
380
- render_data['TEMPLATE_DIR'] = location_dir
381
- rendered_text = template.render(render_data)
382
- return rendered_text
383
-
384
- def _get_render_data(
385
- self, additional_data: Optional[Mapping[str, Any]] = None
386
- ) -> Mapping[str, Any]:
387
- self._ensure_cached_render_data()
388
- if additional_data is None:
389
- return self.__render_data
390
- return {**self.__render_data, **additional_data}
391
-
392
- def _ensure_cached_render_data(self):
393
- if self.__render_data is not None:
394
- return self.__render_data
395
- render_data = dict(DEFAULT_RENDER_DATA)
396
- render_data.update({
397
- 'env': self.__env_map,
398
- 'input': self.__input_map,
399
- })
400
- self.__render_data = render_data
401
- return render_data
402
-
403
-
404
- @typechecked
405
- class TaskModelWithPrinterAndTracker(
406
- CommonTaskModel, PidModel, TimeTracker
407
- ):
408
- def __init__(
409
- self,
410
- name: str,
411
- group: Optional[Group] = None,
412
- description: str = '',
413
- inputs: List[AnyInput] = [],
414
- envs: Iterable[Env] = [],
415
- env_files: Iterable[EnvFile] = [],
416
- icon: Optional[str] = None,
417
- color: Optional[str] = None,
418
- retry: int = 2,
419
- retry_interval: Union[int, float] = 1,
420
- upstreams: Iterable[AnyTask] = [],
421
- checkers: Iterable[AnyTask] = [],
422
- checking_interval: Union[int, float] = 0,
423
- run: Optional[Callable[..., Any]] = None,
424
- should_execute: Union[bool, str, Callable[..., bool]] = True
425
- ):
426
- self._rjust_full_cmd_name: Optional[str] = None
427
- self._has_cli_interface = False
428
- self._complete_name: Optional[str] = None
429
- CommonTaskModel.__init__(
430
- self,
431
- name=name,
432
- group=group,
433
- description=description,
434
- inputs=inputs,
435
- envs=envs,
436
- env_files=env_files,
437
- icon=icon,
438
- color=color,
439
- retry=retry,
440
- retry_interval=retry_interval,
441
- upstreams=upstreams,
442
- checkers=checkers,
443
- checking_interval=checking_interval,
444
- run=run,
445
- should_execute=should_execute,
446
- )
447
- PidModel.__init__(self)
448
- TimeTracker.__init__(self)
449
-
450
- def log_debug(self, message: Any):
451
- prefix = self._get_log_prefix()
452
- colored_message = colored(
453
- f'{prefix} • {message}', attrs=['dark']
454
- )
455
- logger.debug(colored_message)
456
-
457
- def log_warn(self, message: Any):
458
- prefix = self._get_log_prefix()
459
- colored_message = colored(
460
- f'{prefix} • {message}', attrs=['dark']
461
- )
462
- logger.warning(colored_message)
463
-
464
- def log_info(self, message: Any):
465
- prefix = self._get_log_prefix()
466
- colored_message = colored(
467
- f'{prefix} • {message}', attrs=['dark']
468
- )
469
- logger.info(colored_message)
470
-
471
- def log_error(self, message: Any):
472
- prefix = self._get_log_prefix()
473
- colored_message = colored(
474
- f'{prefix} • {message}', color='red', attrs=['bold']
475
- )
476
- logger.error(colored_message, exc_info=True)
477
-
478
- def log_critical(self, message: Any):
479
- prefix = self._get_log_prefix()
480
- colored_message = colored(
481
- f'{prefix} • {message}', color='red', attrs=['bold']
482
- )
483
- logger.critical(colored_message, exc_info=True)
484
-
485
- def print_out(self, message: Any, trim_message: bool = True):
486
- prefix = self._get_colored_print_prefix()
487
- message_str = f'{message}'.rstrip() if trim_message else f'{message}'
488
- print(f'🤖 ○ {prefix} • {message_str}', file=sys.stderr)
489
- sys.stderr.flush()
490
-
491
- def print_err(self, message: Any, trim_message: bool = True):
492
- prefix = self._get_colored_print_prefix()
493
- message_str = f'{message}'.rstrip() if trim_message else f'{message}'
494
- print(f'🤖 △ {prefix} • {message_str}', file=sys.stderr)
495
- sys.stderr.flush()
496
-
497
- def print_out_dark(self, message: Any, trim_message: bool = True):
498
- message_str = f'{message}'
499
- self.print_out(colored(message_str, attrs=['dark']), trim_message)
500
-
501
- def _play_bell(self):
502
- print('\a', end='', file=sys.stderr)
503
-
504
- def _get_colored_print_prefix(self) -> str:
505
- return self._get_colored(self._get_print_prefix())
506
-
507
- def _get_colored(self, text: str) -> str:
508
- return colored(text, color=self.get_color())
509
-
510
- def _get_print_prefix(self) -> str:
511
- common_prefix = self._get_common_prefix(show_time=show_time)
512
- icon = self.get_icon()
513
- rjust_cmd_name = self._get_rjust_full_cmd_name()
514
- return f'{common_prefix} {icon} {rjust_cmd_name}'
515
-
516
- def _get_log_prefix(self) -> str:
517
- common_prefix = self._get_common_prefix(show_time=False)
518
- icon = self.get_icon()
519
- filled_name = self._get_rjust_full_cmd_name()
520
- return f'{common_prefix} {icon} {filled_name}'
521
-
522
- def _get_common_prefix(self, show_time: bool) -> str:
523
- attempt = self._get_attempt()
524
- max_attempt = self._get_max_attempt()
525
- pid = str(self._get_task_pid()).rjust(6)
526
- if show_time:
527
- now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
528
- return f'◷ {now} ❁ {pid} → {attempt}/{max_attempt}'
529
- return f'❁ {pid} → {attempt}/{max_attempt}'
530
-
531
- def _get_rjust_full_cmd_name(self) -> str:
532
- if self._rjust_full_cmd_name is not None:
533
- return self._rjust_full_cmd_name
534
- complete_name = self._get_full_cmd_name()
535
- self._rjust_full_cmd_name = complete_name.rjust(LOG_NAME_LENGTH, ' ')
536
- return self._rjust_full_cmd_name
537
-
538
- def _get_full_cmd_name(self) -> str:
539
- if self._complete_name is not None:
540
- return self._complete_name
541
- executable_prefix = ''
542
- if self._has_cli_interface:
543
- executable_prefix += self._get_executable_name() + ' '
544
- cmd_name = self.get_cmd_name()
545
- if self._group is None:
546
- self._complete_name = f'{executable_prefix}{cmd_name}'
547
- return self._complete_name
548
- group_cmd_name = self._group.get_complete_name()
549
- self._complete_name = f'{executable_prefix}{group_cmd_name} {cmd_name}'
550
- return self._complete_name
551
-
552
- def _get_executable_name(self) -> str:
553
- if len(sys.argv) > 0 and sys.argv[0] != '':
554
- return os.path.basename(sys.argv[0])
555
- return 'zrb'
556
-
557
- def _set_has_cli_interface(self):
558
- self._has_cli_interface = True
File without changes
File without changes