zrb 0.9.2__py3-none-any.whl → 0.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zrb/__init__.py +2 -0
- zrb/builtin/generator/docker_compose_task/template/src/kebab-zrb-task-name/image/Dockerfile +1 -0
- zrb/builtin/generator/docker_compose_task/template/src/kebab-zrb-task-name/image/pyproject.toml +1 -1
- zrb/builtin/generator/fastapp/add.py +17 -5
- zrb/builtin/generator/fastapp/template/src/kebab-zrb-app-name/loadtest/pyproject.toml +1 -1
- zrb/builtin/generator/fastapp/template/src/kebab-zrb-app-name/src/Dockerfile +1 -0
- zrb/builtin/generator/fastapp/template/src/kebab-zrb-app-name/src/config.py +3 -1
- zrb/builtin/generator/fastapp/template/src/kebab-zrb-app-name/src/module/auth/entity/group/api.py +67 -52
- zrb/builtin/generator/fastapp/template/src/kebab-zrb-app-name/src/module/auth/entity/permission/api.py +67 -54
- zrb/builtin/generator/fastapp/template/src/kebab-zrb-app-name/src/module/auth/entity/user/api.py +85 -67
- zrb/builtin/generator/fastapp/template/src/kebab-zrb-app-name/src/module/log/entity/activity/api.py +30 -23
- zrb/builtin/generator/fastapp/template/src/kebab-zrb-app-name/src/module/log/entity/activity/event.py +1 -3
- zrb/builtin/generator/fastapp/template/src/kebab-zrb-app-name/src/pyproject.toml +1 -1
- zrb/builtin/generator/fastapp/template/src/kebab-zrb-app-name/src/start.sh +20 -15
- zrb/builtin/generator/fastapp_crud/template/src/kebab-zrb-app-name/src/module/snake_zrb_module_name/entity/snake_zrb_entity_name/api.py +82 -58
- zrb/builtin/generator/fastapp_crud/template/src/kebab-zrb-app-name/src/module/snake_zrb_module_name/schema/snake_zrb_entity_name.py +1 -1
- zrb/builtin/generator/fastapp_field/helper.py +1 -1
- zrb/builtin/generator/pip_package/template/_automate/snake_zrb_package_name/cmd/publish.sh +1 -1
- zrb/builtin/generator/pip_package/template/_automate/snake_zrb_package_name/local.py +1 -9
- zrb/builtin/generator/pip_package/template/src/kebab-zrb-package-name/pyproject.toml +1 -1
- zrb/builtin/generator/plugin/template/_cmd/publish.sh +1 -1
- zrb/builtin/generator/plugin/template/pyproject.toml +1 -1
- zrb/builtin/generator/plugin/template/zrb_init.py +1 -9
- zrb/builtin/generator/project/template/pyproject.toml +1 -1
- zrb/builtin/generator/simple_python_app/template/src/kebab-zrb-app-name/src/Dockerfile +1 -0
- zrb/builtin/generator/simple_python_app/template/src/kebab-zrb-app-name/src/pyproject.toml +1 -1
- zrb/config/config.py +10 -7
- zrb/helper/accessories/name.py +60 -116
- zrb/helper/codemod/add_property_to_class.py +18 -1
- zrb/shell-scripts/ensure-podman-is-installed.sh +55 -0
- zrb/task/any_task.py +83 -0
- zrb/task/base_remote_cmd_task.py +2 -0
- zrb/task/base_task/base_task.py +53 -15
- zrb/task/base_task/component/base_task_model.py +2 -0
- zrb/task/base_task/component/common_task_model.py +26 -0
- zrb/task/checker.py +2 -0
- zrb/task/cmd_task.py +2 -0
- zrb/task/docker_compose_task.py +27 -21
- zrb/task/flow_task.py +2 -0
- zrb/task/http_checker.py +2 -0
- zrb/task/notifier.py +2 -0
- zrb/task/path_checker.py +2 -0
- zrb/task/path_watcher.py +2 -0
- zrb/task/port_checker.py +2 -0
- zrb/task/recurring_task.py +2 -0
- zrb/task/remote_cmd_task.py +2 -0
- zrb/task/resource_maker.py +2 -0
- zrb/task/rsync_task.py +2 -0
- zrb/task/time_watcher.py +2 -0
- zrb/task/wiki_task.py +119 -0
- {zrb-0.9.2.dist-info → zrb-0.10.0.dist-info}/METADATA +1 -1
- {zrb-0.9.2.dist-info → zrb-0.10.0.dist-info}/RECORD +55 -53
- {zrb-0.9.2.dist-info → zrb-0.10.0.dist-info}/LICENSE +0 -0
- {zrb-0.9.2.dist-info → zrb-0.10.0.dist-info}/WHEEL +0 -0
- {zrb-0.9.2.dist-info → zrb-0.10.0.dist-info}/entry_points.txt +0 -0
zrb/helper/accessories/name.py
CHANGED
@@ -9,124 +9,68 @@ def get_random_name(
|
|
9
9
|
separator: str = "-", add_random_digit: bool = True, digit_count: int = 4
|
10
10
|
) -> str:
|
11
11
|
prefixes = [
|
12
|
-
"
|
13
|
-
"
|
14
|
-
"
|
15
|
-
"
|
16
|
-
"
|
17
|
-
"
|
18
|
-
"
|
19
|
-
"
|
20
|
-
"
|
21
|
-
"
|
22
|
-
"
|
23
|
-
"
|
24
|
-
"
|
25
|
-
"
|
26
|
-
"
|
27
|
-
"
|
28
|
-
"
|
29
|
-
"
|
30
|
-
"
|
31
|
-
"
|
32
|
-
"
|
33
|
-
"
|
34
|
-
"
|
35
|
-
"
|
36
|
-
"
|
37
|
-
"
|
38
|
-
"
|
39
|
-
"
|
40
|
-
"
|
41
|
-
"
|
42
|
-
"lunar",
|
43
|
-
"mystic",
|
44
|
-
"nephrite",
|
45
|
-
"nocturnal",
|
46
|
-
"obsidian",
|
47
|
-
"opal",
|
48
|
-
"pearl",
|
49
|
-
"platinum",
|
50
|
-
"prismatic",
|
51
|
-
"ruby",
|
52
|
-
"sapphire",
|
53
|
-
"serpentine",
|
54
|
-
"silver",
|
55
|
-
"sol",
|
56
|
-
"solar",
|
57
|
-
"spiritual",
|
58
|
-
"stellar",
|
59
|
-
"tempest",
|
60
|
-
"topaz",
|
61
|
-
"turquoise",
|
62
|
-
"verde",
|
63
|
-
"vermillion",
|
64
|
-
"vitreous",
|
65
|
-
"zephyr",
|
66
|
-
"zircon",
|
12
|
+
"bold",
|
13
|
+
"calm",
|
14
|
+
"dark",
|
15
|
+
"deep",
|
16
|
+
"fast",
|
17
|
+
"firm",
|
18
|
+
"glad",
|
19
|
+
"grey",
|
20
|
+
"hard",
|
21
|
+
"high",
|
22
|
+
"kind",
|
23
|
+
"late",
|
24
|
+
"lean",
|
25
|
+
"long",
|
26
|
+
"loud",
|
27
|
+
"mild",
|
28
|
+
"neat",
|
29
|
+
"pure",
|
30
|
+
"rare",
|
31
|
+
"rich",
|
32
|
+
"safe",
|
33
|
+
"slow",
|
34
|
+
"soft",
|
35
|
+
"tall",
|
36
|
+
"thin",
|
37
|
+
"trim",
|
38
|
+
"vast",
|
39
|
+
"warm",
|
40
|
+
"weak",
|
41
|
+
"wild",
|
67
42
|
]
|
68
43
|
suffixes = [
|
69
|
-
"
|
70
|
-
"
|
71
|
-
"
|
72
|
-
"
|
73
|
-
"
|
74
|
-
"
|
75
|
-
"
|
76
|
-
"
|
77
|
-
"
|
78
|
-
"
|
79
|
-
"
|
80
|
-
"
|
81
|
-
"
|
82
|
-
"
|
83
|
-
"
|
84
|
-
"
|
85
|
-
"
|
86
|
-
"
|
87
|
-
"
|
88
|
-
"
|
89
|
-
"
|
90
|
-
"
|
91
|
-
"
|
92
|
-
"
|
93
|
-
"
|
94
|
-
"
|
95
|
-
"
|
96
|
-
"
|
97
|
-
"
|
98
|
-
"
|
99
|
-
"lux",
|
100
|
-
"magicae",
|
101
|
-
"magnum",
|
102
|
-
"materia",
|
103
|
-
"metallum",
|
104
|
-
"mysticum",
|
105
|
-
"natura",
|
106
|
-
"occultum",
|
107
|
-
"omnipotentis",
|
108
|
-
"opulentia",
|
109
|
-
"philosophia",
|
110
|
-
"philosophorum",
|
111
|
-
"praeparatum",
|
112
|
-
"praestantissimum",
|
113
|
-
"prima",
|
114
|
-
"primordium",
|
115
|
-
"quintessentia",
|
116
|
-
"regeneratio",
|
117
|
-
"ritualis",
|
118
|
-
"sanctum",
|
119
|
-
"spiritus",
|
120
|
-
"tenebris",
|
121
|
-
"terra",
|
122
|
-
"tinctura",
|
123
|
-
"transmutationis",
|
124
|
-
"universalis",
|
125
|
-
"vapores",
|
126
|
-
"venenum",
|
127
|
-
"veritas",
|
128
|
-
"vitae",
|
129
|
-
"volatus",
|
44
|
+
"arch",
|
45
|
+
"area",
|
46
|
+
"atom",
|
47
|
+
"base",
|
48
|
+
"beam",
|
49
|
+
"bell",
|
50
|
+
"bolt",
|
51
|
+
"bone",
|
52
|
+
"bulk",
|
53
|
+
"bush",
|
54
|
+
"cell",
|
55
|
+
"chip",
|
56
|
+
"clay",
|
57
|
+
"coal",
|
58
|
+
"coil",
|
59
|
+
"cone",
|
60
|
+
"cube",
|
61
|
+
"disk",
|
62
|
+
"dust",
|
63
|
+
"face",
|
64
|
+
"film",
|
65
|
+
"foam",
|
66
|
+
"frog",
|
67
|
+
"fuel",
|
68
|
+
"gate",
|
69
|
+
"gear",
|
70
|
+
"hall",
|
71
|
+
"hand",
|
72
|
+
"horn",
|
73
|
+
"leaf",
|
130
74
|
]
|
131
75
|
prefix = random.choice(prefixes)
|
132
76
|
suffix = random.choice(suffixes)
|
@@ -65,7 +65,7 @@ def add_property_to_class(
|
|
65
65
|
) -> str:
|
66
66
|
module = cst.parse_module(code)
|
67
67
|
property_name_node = cst.Name(value=property_name)
|
68
|
-
property_type_node =
|
68
|
+
property_type_node = _get_property_type_node(property_type)
|
69
69
|
property_value_node = _get_property_value_node(property_value)
|
70
70
|
transformed_module = module.visit(
|
71
71
|
AddPropertyTransformer(
|
@@ -78,6 +78,23 @@ def add_property_to_class(
|
|
78
78
|
return transformed_module.code
|
79
79
|
|
80
80
|
|
81
|
+
def _get_property_type_node(property_type: str) -> cst.Annotation:
|
82
|
+
if property_type.startswith("Optional[") and property_type.endswith("]"):
|
83
|
+
inner_type = property_type[len("Optional[") : -1]
|
84
|
+
return cst.Annotation(
|
85
|
+
annotation=cst.Subscript(
|
86
|
+
value=cst.Name("Optional"),
|
87
|
+
slice=[
|
88
|
+
cst.SubscriptElement(
|
89
|
+
slice=cst.Index(value=cst.Name(value=inner_type))
|
90
|
+
)
|
91
|
+
],
|
92
|
+
)
|
93
|
+
)
|
94
|
+
else:
|
95
|
+
return cst.Annotation(cst.Name(value=property_type))
|
96
|
+
|
97
|
+
|
81
98
|
def _get_property_value_node(
|
82
99
|
property_value: Optional[str],
|
83
100
|
) -> Optional[cst.BaseExpression]:
|
@@ -0,0 +1,55 @@
|
|
1
|
+
set -e
|
2
|
+
if command_exists podman
|
3
|
+
then
|
4
|
+
log_info "Podman is already installed."
|
5
|
+
else
|
6
|
+
log_info "Installing Podman..."
|
7
|
+
if [ "$OS_TYPE" = "Darwin" ]
|
8
|
+
then
|
9
|
+
if command_exists brew
|
10
|
+
then
|
11
|
+
brew install --cask podman
|
12
|
+
log_info "Please start Podman before proceeding."
|
13
|
+
else
|
14
|
+
log_info "Homebrew not found. Please install Homebrew and try again."
|
15
|
+
exit 1
|
16
|
+
fi
|
17
|
+
elif [ "$OS_TYPE" = "Linux" ]
|
18
|
+
then
|
19
|
+
if command_exists apt
|
20
|
+
then
|
21
|
+
try_sudo apt update
|
22
|
+
try_sudo apt install -y podman
|
23
|
+
elif command_exists yum
|
24
|
+
then
|
25
|
+
try_sudo yum install -y podman
|
26
|
+
elif command_exists dnf
|
27
|
+
then
|
28
|
+
try_sudo dnf install -y podman
|
29
|
+
elif command_exists pacman
|
30
|
+
then
|
31
|
+
try_sudo pacman -Syu --noconfirm podman
|
32
|
+
else
|
33
|
+
log_info "No known package manager found. Please install Podman manually."
|
34
|
+
exit 1
|
35
|
+
fi
|
36
|
+
else
|
37
|
+
log_info "Unsupported OS type. Please install Podman manually."
|
38
|
+
exit 1
|
39
|
+
fi
|
40
|
+
fi
|
41
|
+
|
42
|
+
if ! command_exists podman-compose
|
43
|
+
then
|
44
|
+
log_info "Installing Podman Compose plugin..."
|
45
|
+
pip install podman-compose
|
46
|
+
fi
|
47
|
+
|
48
|
+
# Check Podman Compose plugin installation
|
49
|
+
if command_exists podman && command_exists podman-compose
|
50
|
+
then
|
51
|
+
log_info "Podman Compose plugin is already installed."
|
52
|
+
else
|
53
|
+
log_info "Podman Compose plugin is not installed or podman is not running. Please check your installation."
|
54
|
+
exit 1
|
55
|
+
fi
|
zrb/task/any_task.py
CHANGED
@@ -384,6 +384,44 @@ class AnyTask(ABC):
|
|
384
384
|
"""
|
385
385
|
pass
|
386
386
|
|
387
|
+
@abstractmethod
|
388
|
+
def insert_fallback(self, *fallbacks: TAnyTask):
|
389
|
+
"""
|
390
|
+
Inserts one or more `AnyTask` instances at the beginning of the current task's fallback list.
|
391
|
+
|
392
|
+
This method is used to define dependencies for the current task. Tasks in the fallback list are executed when the task is failed.
|
393
|
+
Adding a task to the beginning of the list means it will be
|
394
|
+
executed earlier than those already in the list.
|
395
|
+
|
396
|
+
Args:
|
397
|
+
fallbacks (TAnyTask): One or more task instances to be added to the fallback list.
|
398
|
+
|
399
|
+
Examples:
|
400
|
+
>>> from zrb import Task
|
401
|
+
>>> task = Task(name='task')
|
402
|
+
>>> fallback_task = Task(name='fallback-task')
|
403
|
+
>>> task.insert_fallback(fallback_task)
|
404
|
+
"""
|
405
|
+
pass
|
406
|
+
|
407
|
+
@abstractmethod
|
408
|
+
def add_fallback(self, *fallbacks: TAnyTask):
|
409
|
+
"""
|
410
|
+
Adds one or more `AnyTask` instances to the end of the current task's fallback list.
|
411
|
+
|
412
|
+
This method appends tasks to the fallback list, indicating that these tasks should be executed when the task is failed.
|
413
|
+
|
414
|
+
Args:
|
415
|
+
fallbacks (TAnyTask): One or more task instances to be added to the fallback list.
|
416
|
+
|
417
|
+
Examples:
|
418
|
+
>>> from zrb import Task
|
419
|
+
>>> task = Task(name='task')
|
420
|
+
>>> fallback_task = Task(name='fallback-task')
|
421
|
+
>>> task.add_fallback(fallback_task)
|
422
|
+
"""
|
423
|
+
pass
|
424
|
+
|
387
425
|
@abstractmethod
|
388
426
|
def add_upstream(self, *upstreams: TAnyTask):
|
389
427
|
"""
|
@@ -519,6 +557,20 @@ class AnyTask(ABC):
|
|
519
557
|
"""
|
520
558
|
pass
|
521
559
|
|
560
|
+
@abstractmethod
|
561
|
+
def _lock_upstreams(self):
|
562
|
+
"""
|
563
|
+
Lock upstreams so that it cannot be altered anymore
|
564
|
+
"""
|
565
|
+
pass
|
566
|
+
|
567
|
+
@abstractmethod
|
568
|
+
def _lock_fallbacks(self):
|
569
|
+
"""
|
570
|
+
Lock fallbacks so that it cannot be altered anymore
|
571
|
+
"""
|
572
|
+
pass
|
573
|
+
|
522
574
|
@abstractmethod
|
523
575
|
def _set_execution_id(self, execution_id: str):
|
524
576
|
"""
|
@@ -885,6 +937,37 @@ class AnyTask(ABC):
|
|
885
937
|
"""
|
886
938
|
pass
|
887
939
|
|
940
|
+
@abstractmethod
|
941
|
+
def inject_fallbacks(self):
|
942
|
+
"""
|
943
|
+
Injects fallback tasks into the current task.
|
944
|
+
|
945
|
+
This method is used for programmatically adding fallback to the task.
|
946
|
+
fallback tasks are those that must be completed when the task is failed.
|
947
|
+
Override this method in subclasses to specify such dependencies.
|
948
|
+
|
949
|
+
Examples:
|
950
|
+
>>> from zrb import Task
|
951
|
+
>>> class MyTask(Task):
|
952
|
+
>>> def inject_fallbacks(self):
|
953
|
+
>>> self.add_fallback(another_task)
|
954
|
+
"""
|
955
|
+
pass
|
956
|
+
|
957
|
+
@abstractmethod
|
958
|
+
def _get_fallbacks(self) -> Iterable[TAnyTask]:
|
959
|
+
"""
|
960
|
+
Retrieves the fallback tasks of the current task.
|
961
|
+
|
962
|
+
An internal method to get the list of fallback tasks that have been set for the
|
963
|
+
task, either statically or through `inject_fallbacks`. This is essential for task
|
964
|
+
fallback scenario.
|
965
|
+
|
966
|
+
Returns:
|
967
|
+
Iterable[TAnyTask]: An iterable of fallback tasks.
|
968
|
+
"""
|
969
|
+
pass
|
970
|
+
|
888
971
|
@abstractmethod
|
889
972
|
def _get_combined_inputs(self) -> Iterable[AnyInput]:
|
890
973
|
"""
|
zrb/task/base_remote_cmd_task.py
CHANGED
@@ -78,6 +78,7 @@ class SingleBaseRemoteCmdTask(CmdTask):
|
|
78
78
|
post_cmd_path: CmdVal = "",
|
79
79
|
cwd: Optional[Union[str, pathlib.Path]] = None,
|
80
80
|
upstreams: Iterable[AnyTask] = [],
|
81
|
+
fallbacks: Iterable[AnyTask] = [],
|
81
82
|
on_triggered: Optional[OnTriggered] = None,
|
82
83
|
on_waiting: Optional[OnWaiting] = None,
|
83
84
|
on_skipped: Optional[OnSkipped] = None,
|
@@ -110,6 +111,7 @@ class SingleBaseRemoteCmdTask(CmdTask):
|
|
110
111
|
cmd_path=cmd_path,
|
111
112
|
cwd=cwd,
|
112
113
|
upstreams=upstreams,
|
114
|
+
fallbacks=fallbacks,
|
113
115
|
on_triggered=on_triggered,
|
114
116
|
on_waiting=on_waiting,
|
115
117
|
on_skipped=on_skipped,
|
zrb/task/base_task/base_task.py
CHANGED
@@ -39,6 +39,7 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
|
|
39
39
|
"""
|
40
40
|
|
41
41
|
__xcom: Mapping[str, Mapping[str, str]] = {}
|
42
|
+
__running_tasks: List[AnyTask] = []
|
42
43
|
|
43
44
|
def __init__(
|
44
45
|
self,
|
@@ -53,6 +54,7 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
|
|
53
54
|
retry: int = 2,
|
54
55
|
retry_interval: Union[float, int] = 1,
|
55
56
|
upstreams: Iterable[AnyTask] = [],
|
57
|
+
fallbacks: Iterable[AnyTask] = [],
|
56
58
|
checkers: Iterable[AnyTask] = [],
|
57
59
|
checking_interval: Union[float, int] = 0,
|
58
60
|
run: Optional[Callable[..., Any]] = None,
|
@@ -87,6 +89,7 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
|
|
87
89
|
retry=retry,
|
88
90
|
retry_interval=retry_interval,
|
89
91
|
upstreams=upstreams,
|
92
|
+
fallbacks=fallbacks,
|
90
93
|
checkers=checkers,
|
91
94
|
checking_interval=checking_interval,
|
92
95
|
run=run,
|
@@ -257,7 +260,7 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
|
|
257
260
|
except Exception as e:
|
258
261
|
self.log_error(f"{e}")
|
259
262
|
if raise_error:
|
260
|
-
raise
|
263
|
+
raise e
|
261
264
|
finally:
|
262
265
|
if show_done_info:
|
263
266
|
self._show_env_prefix()
|
@@ -303,7 +306,7 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
|
|
303
306
|
self.log_debug("Waiting execution to be started")
|
304
307
|
while not self.__is_execution_started:
|
305
308
|
# Don't start checking before the execution itself has been started
|
306
|
-
await asyncio.sleep(0.
|
309
|
+
await asyncio.sleep(0.05)
|
307
310
|
check_coroutines: Iterable[asyncio.Task] = []
|
308
311
|
for checker_task in self._get_checkers():
|
309
312
|
checker_task._set_execution_id(self.get_execution_id())
|
@@ -334,15 +337,7 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
|
|
334
337
|
await self.on_triggered()
|
335
338
|
self.__is_execution_triggered = True
|
336
339
|
await self.on_waiting()
|
337
|
-
|
338
|
-
upstream_check_processes: Iterable[asyncio.Task] = []
|
339
|
-
self._lock_upstreams()
|
340
|
-
for upstream_task in self._get_upstreams():
|
341
|
-
upstream_check_processes.append(
|
342
|
-
asyncio.create_task(upstream_task._loop_check())
|
343
|
-
)
|
344
|
-
# wait all upstream checkers to complete
|
345
|
-
await asyncio.gather(*upstream_check_processes)
|
340
|
+
await self.__check_upstreams()
|
346
341
|
# mark execution as started, so that checkers can start checking
|
347
342
|
self.__is_execution_started = True
|
348
343
|
local_kwargs = dict(kwargs)
|
@@ -353,27 +348,70 @@ class BaseTask(FinishTracker, AttemptTracker, Renderer, BaseTaskModel, AnyTask):
|
|
353
348
|
return None
|
354
349
|
# start running task
|
355
350
|
result: Any = None
|
351
|
+
is_failed: bool = False
|
356
352
|
while self._should_attempt():
|
357
353
|
try:
|
358
354
|
self.log_debug(f"Started with args: {args} and kwargs: {local_kwargs}")
|
359
355
|
await self.on_started()
|
356
|
+
self.__running_tasks.append(self)
|
360
357
|
result = await run_async(self.run, *args, **local_kwargs)
|
358
|
+
self.__running_tasks.remove(self)
|
361
359
|
break
|
362
360
|
except Exception as e:
|
363
|
-
is_last_attempt = self._is_last_attempt()
|
364
|
-
await self.on_failed(is_last_attempt, e)
|
365
|
-
if is_last_attempt:
|
366
|
-
raise e
|
367
361
|
attempt = self._get_attempt()
|
368
362
|
self.log_error(f"Encounter error on attempt {attempt}")
|
363
|
+
if self._is_last_attempt():
|
364
|
+
is_failed = True
|
365
|
+
raise e
|
366
|
+
self.__running_tasks.remove(self)
|
367
|
+
await self.on_failed(is_last_attempt=False, exception=e)
|
369
368
|
self._increase_attempt()
|
370
369
|
await asyncio.sleep(self._retry_interval)
|
371
370
|
await self.on_retry()
|
371
|
+
finally:
|
372
|
+
if is_failed:
|
373
|
+
running_tasks = self.__running_tasks
|
374
|
+
self.__running_tasks = []
|
375
|
+
await self.__trigger_failure(running_tasks)
|
376
|
+
await self.__trigger_fallbacks(running_tasks, kwargs)
|
372
377
|
self.set_xcom(self.get_name(), f"{result}")
|
373
378
|
self.log_debug(f"XCom: {self.__xcom}")
|
374
379
|
await self._mark_done()
|
375
380
|
return result
|
376
381
|
|
382
|
+
async def __check_upstreams(self):
|
383
|
+
coroutines: Iterable[asyncio.Task] = []
|
384
|
+
self._lock_upstreams()
|
385
|
+
for upstream_task in self._get_upstreams():
|
386
|
+
coroutines.append(asyncio.create_task(upstream_task._loop_check()))
|
387
|
+
# wait all upstream checkers to complete
|
388
|
+
await asyncio.gather(*coroutines)
|
389
|
+
|
390
|
+
async def __trigger_failure(self, tasks: List[AnyTask]):
|
391
|
+
coroutines = [
|
392
|
+
task.on_failed(is_last_attempt=True, exception=Exception("canceled"))
|
393
|
+
for task in tasks
|
394
|
+
]
|
395
|
+
await asyncio.gather(*coroutines)
|
396
|
+
|
397
|
+
async def __trigger_fallbacks(
|
398
|
+
self, tasks: List[AnyTask], kwargs: Mapping[str, Any]
|
399
|
+
):
|
400
|
+
coroutines: Iterable[asyncio.Task] = []
|
401
|
+
for fallback in self.__get_all_fallbacks(tasks):
|
402
|
+
fallback._set_execution_id(self.get_execution_id())
|
403
|
+
coroutines.append(asyncio.create_task(fallback._run_all(**kwargs)))
|
404
|
+
await asyncio.gather(*coroutines)
|
405
|
+
|
406
|
+
def __get_all_fallbacks(self, tasks: List[AnyTask]) -> List[AnyTask]:
|
407
|
+
all_fallbacks: List[AnyTask] = []
|
408
|
+
for task in tasks:
|
409
|
+
task._lock_fallbacks()
|
410
|
+
for fallback in task._get_fallbacks():
|
411
|
+
if fallback not in all_fallbacks:
|
412
|
+
all_fallbacks.append(fallback)
|
413
|
+
return all_fallbacks
|
414
|
+
|
377
415
|
async def _check_should_execute(self, *args: Any, **kwargs: Any) -> bool:
|
378
416
|
if callable(self._should_execute):
|
379
417
|
return await run_async(self._should_execute, *args, **kwargs)
|
@@ -56,6 +56,7 @@ class BaseTaskModel(CommonTaskModel, PidModel, TimeTracker):
|
|
56
56
|
retry: int = 2,
|
57
57
|
retry_interval: Union[int, float] = 1,
|
58
58
|
upstreams: Iterable[AnyTask] = [],
|
59
|
+
fallbacks: Iterable[AnyTask] = [],
|
59
60
|
checkers: Iterable[AnyTask] = [],
|
60
61
|
checking_interval: Union[int, float] = 0,
|
61
62
|
run: Optional[Callable[..., Any]] = None,
|
@@ -85,6 +86,7 @@ class BaseTaskModel(CommonTaskModel, PidModel, TimeTracker):
|
|
85
86
|
retry=retry,
|
86
87
|
retry_interval=retry_interval,
|
87
88
|
upstreams=upstreams,
|
89
|
+
fallbacks=fallbacks,
|
88
90
|
checkers=checkers,
|
89
91
|
checking_interval=checking_interval,
|
90
92
|
run=run,
|
@@ -47,6 +47,7 @@ class CommonTaskModel:
|
|
47
47
|
retry: int = 2,
|
48
48
|
retry_interval: Union[float, int] = 1,
|
49
49
|
upstreams: Iterable[AnyTask] = [],
|
50
|
+
fallbacks: Iterable[AnyTask] = [],
|
50
51
|
checkers: Iterable[AnyTask] = [],
|
51
52
|
checking_interval: Union[float, int] = 0,
|
52
53
|
run: Optional[Callable[..., Any]] = None,
|
@@ -73,6 +74,7 @@ class CommonTaskModel:
|
|
73
74
|
self._retry = retry
|
74
75
|
self._retry_interval = retry_interval
|
75
76
|
self._upstreams = upstreams
|
77
|
+
self._fallbacks = fallbacks
|
76
78
|
self._checkers = [checker.copy() for checker in checkers]
|
77
79
|
self._checking_interval = checking_interval
|
78
80
|
self._run_function: Optional[Callable[..., Any]] = run
|
@@ -90,16 +92,21 @@ class CommonTaskModel:
|
|
90
92
|
self.__allow_add_env_files = True
|
91
93
|
self.__allow_add_inputs = True
|
92
94
|
self.__allow_add_upstreams: bool = True
|
95
|
+
self.__allow_add_fallbacks: bool = True
|
93
96
|
self.__allow_add_checkers: bool = True
|
94
97
|
self.__has_already_inject_env_files: bool = False
|
95
98
|
self.__has_already_inject_envs: bool = False
|
96
99
|
self.__has_already_inject_inputs: bool = False
|
97
100
|
self.__has_already_inject_upstreams: bool = False
|
101
|
+
self.__has_already_inject_fallbacks: bool = False
|
98
102
|
self.__all_inputs: Optional[List[AnyInput]] = None
|
99
103
|
|
100
104
|
def _lock_upstreams(self):
|
101
105
|
self.__allow_add_upstreams = False
|
102
106
|
|
107
|
+
def _lock_fallbacks(self):
|
108
|
+
self.__allow_add_fallbacks = False
|
109
|
+
|
103
110
|
def _set_execution_id(self, execution_id: str):
|
104
111
|
if self.__execution_id == "":
|
105
112
|
self.__execution_id = execution_id
|
@@ -276,6 +283,25 @@ class CommonTaskModel:
|
|
276
283
|
self.__has_already_inject_upstreams = True
|
277
284
|
return list(self._upstreams)
|
278
285
|
|
286
|
+
def insert_fallback(self, *fallbacks: AnyTask):
|
287
|
+
if not self.__allow_add_fallbacks:
|
288
|
+
raise Exception(f"Cannot insert fallbacks to `{self.get_name()}`")
|
289
|
+
self._fallbacks = list(fallbacks) + list(self._fallbacks)
|
290
|
+
|
291
|
+
def add_fallback(self, *fallbacks: AnyTask):
|
292
|
+
if not self.__allow_add_fallbacks:
|
293
|
+
raise Exception(f"Cannot add fallbacks to `{self.get_name()}`")
|
294
|
+
self._fallbacks = list(self._fallbacks) + list(fallbacks)
|
295
|
+
|
296
|
+
def inject_fallbacks(self):
|
297
|
+
pass
|
298
|
+
|
299
|
+
def _get_fallbacks(self) -> List[AnyTask]:
|
300
|
+
if not self.__has_already_inject_fallbacks:
|
301
|
+
self.inject_fallbacks()
|
302
|
+
self.__has_already_inject_fallbacks = True
|
303
|
+
return list(self._fallbacks)
|
304
|
+
|
279
305
|
def get_icon(self) -> str:
|
280
306
|
return self._icon
|
281
307
|
|
zrb/task/checker.py
CHANGED
@@ -32,6 +32,7 @@ class Checker(BaseTask):
|
|
32
32
|
color: Optional[str] = None,
|
33
33
|
description: str = "",
|
34
34
|
upstreams: Iterable[AnyTask] = [],
|
35
|
+
fallbacks: Iterable[AnyTask] = [],
|
35
36
|
on_triggered: Optional[OnTriggered] = None,
|
36
37
|
on_waiting: Optional[OnWaiting] = None,
|
37
38
|
on_skipped: Optional[OnSkipped] = None,
|
@@ -55,6 +56,7 @@ class Checker(BaseTask):
|
|
55
56
|
color=color,
|
56
57
|
description=description,
|
57
58
|
upstreams=upstreams,
|
59
|
+
fallbacks=fallbacks,
|
58
60
|
on_triggered=on_triggered,
|
59
61
|
on_waiting=on_waiting,
|
60
62
|
on_skipped=on_skipped,
|
zrb/task/cmd_task.py
CHANGED
@@ -113,6 +113,7 @@ class CmdTask(BaseTask):
|
|
113
113
|
cmd_path: CmdVal = "",
|
114
114
|
cwd: Optional[Union[str, pathlib.Path]] = None,
|
115
115
|
upstreams: Iterable[AnyTask] = [],
|
116
|
+
fallbacks: Iterable[AnyTask] = [],
|
116
117
|
on_triggered: Optional[OnTriggered] = None,
|
117
118
|
on_waiting: Optional[OnWaiting] = None,
|
118
119
|
on_skipped: Optional[OnSkipped] = None,
|
@@ -141,6 +142,7 @@ class CmdTask(BaseTask):
|
|
141
142
|
color=color,
|
142
143
|
description=description,
|
143
144
|
upstreams=upstreams,
|
145
|
+
fallbacks=fallbacks,
|
144
146
|
on_triggered=on_triggered,
|
145
147
|
on_waiting=on_waiting,
|
146
148
|
on_skipped=on_skipped,
|