ddeutil-workflow 0.0.55__py3-none-any.whl → 0.0.57__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.
- ddeutil/workflow/__about__.py +1 -1
- ddeutil/workflow/__cron.py +26 -12
- ddeutil/workflow/__init__.py +4 -2
- ddeutil/workflow/__main__.py +30 -0
- ddeutil/workflow/__types.py +1 -0
- ddeutil/workflow/conf.py +163 -101
- ddeutil/workflow/{cron.py → event.py} +37 -20
- ddeutil/workflow/exceptions.py +44 -14
- ddeutil/workflow/job.py +87 -58
- ddeutil/workflow/logs.py +13 -5
- ddeutil/workflow/result.py +9 -4
- ddeutil/workflow/scheduler.py +38 -73
- ddeutil/workflow/stages.py +370 -147
- ddeutil/workflow/utils.py +37 -6
- ddeutil/workflow/workflow.py +243 -302
- {ddeutil_workflow-0.0.55.dist-info → ddeutil_workflow-0.0.57.dist-info}/METADATA +41 -35
- ddeutil_workflow-0.0.57.dist-info/RECORD +31 -0
- {ddeutil_workflow-0.0.55.dist-info → ddeutil_workflow-0.0.57.dist-info}/WHEEL +1 -1
- ddeutil_workflow-0.0.57.dist-info/entry_points.txt +2 -0
- ddeutil_workflow-0.0.55.dist-info/RECORD +0 -30
- {ddeutil_workflow-0.0.55.dist-info → ddeutil_workflow-0.0.57.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.55.dist-info → ddeutil_workflow-0.0.57.dist-info}/top_level.txt +0 -0
ddeutil/workflow/stages.py
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
# Licensed under the MIT License. See LICENSE in the project root for
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
|
-
# [x] Use dynamic config
|
7
6
|
"""Stages module include all stage model that use be the minimum execution layer
|
8
7
|
of this workflow engine. The stage handle the minimize task that run in some
|
9
8
|
thread (same thread at its job owner) that mean it is the lowest executor that
|
@@ -16,11 +15,11 @@ have a lot of use-case, and it should does not worry about it error output.
|
|
16
15
|
So, I will create `handler_execute` for any exception class that raise from
|
17
16
|
the stage execution method.
|
18
17
|
|
19
|
-
Execution --> Ok
|
20
|
-
|
21
|
-
--> Error ┬--( handler )-> Result with `FAILED` (Set `raise_error` flag)
|
18
|
+
Execution --> Ok ┬--( handler )--> Result with `SUCCESS` or `CANCEL`
|
22
19
|
|
|
23
|
-
╰--( handler )
|
20
|
+
╰--( handler )--> Result with `FAILED` (Set `raise_error` flag)
|
21
|
+
|
22
|
+
--> Error ---( handler )--> Raise StageException(...)
|
24
23
|
|
25
24
|
On the context I/O that pass to a stage object at execute process. The
|
26
25
|
execute method receives a `params={"params": {...}}` value for passing template
|
@@ -41,6 +40,7 @@ from abc import ABC, abstractmethod
|
|
41
40
|
from collections.abc import AsyncIterator, Iterator
|
42
41
|
from concurrent.futures import (
|
43
42
|
FIRST_EXCEPTION,
|
43
|
+
CancelledError,
|
44
44
|
Future,
|
45
45
|
ThreadPoolExecutor,
|
46
46
|
as_completed,
|
@@ -58,13 +58,12 @@ from pydantic import BaseModel, Field
|
|
58
58
|
from pydantic.functional_validators import model_validator
|
59
59
|
from typing_extensions import Self
|
60
60
|
|
61
|
-
from .__types import DictData, DictStr, TupleStr
|
61
|
+
from .__types import DictData, DictStr, StrOrInt, TupleStr
|
62
62
|
from .conf import dynamic
|
63
|
-
from .exceptions import StageException,
|
63
|
+
from .exceptions import StageException, to_dict
|
64
64
|
from .result import CANCEL, FAILED, SUCCESS, WAIT, Result, Status
|
65
65
|
from .reusables import TagFunc, extract_call, not_in_template, param2template
|
66
66
|
from .utils import (
|
67
|
-
NEWLINE,
|
68
67
|
delay,
|
69
68
|
filter_func,
|
70
69
|
gen_id,
|
@@ -72,7 +71,6 @@ from .utils import (
|
|
72
71
|
)
|
73
72
|
|
74
73
|
T = TypeVar("T")
|
75
|
-
StrOrInt = Union[str, int]
|
76
74
|
|
77
75
|
|
78
76
|
class BaseStage(BaseModel, ABC):
|
@@ -174,17 +172,23 @@ class BaseStage(BaseModel, ABC):
|
|
174
172
|
|
175
173
|
This stage exception handler still use ok-error concept, but it
|
176
174
|
allows you force catching an output result with error message by
|
177
|
-
specific environment variable,`WORKFLOW_CORE_STAGE_RAISE_ERROR
|
175
|
+
specific environment variable,`WORKFLOW_CORE_STAGE_RAISE_ERROR` or set
|
176
|
+
`raise_error` parameter to True.
|
178
177
|
|
179
178
|
Execution --> Ok --> Result
|
180
179
|
|-status: SUCCESS
|
181
180
|
╰-context:
|
182
181
|
╰-outputs: ...
|
183
182
|
|
184
|
-
-->
|
183
|
+
--> Ok --> Result
|
184
|
+
|-status: CANCEL
|
185
|
+
╰-errors:
|
186
|
+
|-name: ...
|
187
|
+
╰-message: ...
|
188
|
+
|
189
|
+
--> Ok --> Result (if `raise_error` was set)
|
185
190
|
|-status: FAILED
|
186
191
|
╰-errors:
|
187
|
-
|-class: ...
|
188
192
|
|-name: ...
|
189
193
|
╰-message: ...
|
190
194
|
|
@@ -201,6 +205,9 @@ class BaseStage(BaseModel, ABC):
|
|
201
205
|
:param event: (Event) An event manager that pass to the stage execution.
|
202
206
|
:param raise_error: (bool) A flag that all this method raise error
|
203
207
|
|
208
|
+
:raise StageException: If the raise_error was set and the execution
|
209
|
+
raise any error.
|
210
|
+
|
204
211
|
:rtype: Result
|
205
212
|
"""
|
206
213
|
result: Result = Result.construct_with_rs_or_id(
|
@@ -210,20 +217,17 @@ class BaseStage(BaseModel, ABC):
|
|
210
217
|
id_logic=self.iden,
|
211
218
|
extras=self.extras,
|
212
219
|
)
|
213
|
-
|
214
220
|
try:
|
215
221
|
return self.execute(params, result=result, event=event)
|
216
222
|
except Exception as e:
|
217
223
|
e_name: str = e.__class__.__name__
|
218
|
-
result.trace.error(f"[STAGE]: Handler
|
224
|
+
result.trace.error(f"[STAGE]: Error Handler:||{e_name}:||{e}")
|
219
225
|
if dynamic("stage_raise_error", f=raise_error, extras=self.extras):
|
220
226
|
if isinstance(e, StageException):
|
221
227
|
raise
|
222
|
-
|
223
228
|
raise StageException(
|
224
|
-
f"{self.__class__.__name__}: {
|
229
|
+
f"{self.__class__.__name__}: {e_name}: {e}"
|
225
230
|
) from e
|
226
|
-
|
227
231
|
return result.catch(status=FAILED, context={"errors": to_dict(e)})
|
228
232
|
|
229
233
|
def set_outputs(self, output: DictData, to: DictData) -> DictData:
|
@@ -273,13 +277,6 @@ class BaseStage(BaseModel, ABC):
|
|
273
277
|
):
|
274
278
|
return to
|
275
279
|
|
276
|
-
_id: str = (
|
277
|
-
param2template(self.id, params=to, extras=self.extras)
|
278
|
-
if self.id
|
279
|
-
else gen_id(
|
280
|
-
param2template(self.name, params=to, extras=self.extras)
|
281
|
-
)
|
282
|
-
)
|
283
280
|
output: DictData = output.copy()
|
284
281
|
errors: DictData = (
|
285
282
|
{"errors": output.pop("errors", {})} if "errors" in output else {}
|
@@ -289,7 +286,7 @@ class BaseStage(BaseModel, ABC):
|
|
289
286
|
if "skipped" in output
|
290
287
|
else {}
|
291
288
|
)
|
292
|
-
to["stages"][
|
289
|
+
to["stages"][self.gen_id(params=to)] = {
|
293
290
|
"outputs": copy.deepcopy(output),
|
294
291
|
**skipping,
|
295
292
|
**errors,
|
@@ -309,15 +306,11 @@ class BaseStage(BaseModel, ABC):
|
|
309
306
|
"stage_default_id", extras=self.extras
|
310
307
|
):
|
311
308
|
return {}
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
else gen_id(
|
317
|
-
param2template(self.name, params=output, extras=self.extras)
|
318
|
-
)
|
309
|
+
return (
|
310
|
+
output.get("stages", {})
|
311
|
+
.get(self.gen_id(params=output), {})
|
312
|
+
.get("outputs", {})
|
319
313
|
)
|
320
|
-
return output.get("stages", {}).get(_id, {}).get("outputs", {})
|
321
314
|
|
322
315
|
def is_skipped(self, params: DictData) -> bool:
|
323
316
|
"""Return true if condition of this stage do not correct. This process
|
@@ -351,6 +344,22 @@ class BaseStage(BaseModel, ABC):
|
|
351
344
|
except Exception as e:
|
352
345
|
raise StageException(f"{e.__class__.__name__}: {e}") from e
|
353
346
|
|
347
|
+
def gen_id(self, params: DictData) -> str:
|
348
|
+
"""Generate stage ID that dynamic use stage's name if it ID does not
|
349
|
+
set.
|
350
|
+
|
351
|
+
:param params: A parameter data.
|
352
|
+
|
353
|
+
:rtype: str
|
354
|
+
"""
|
355
|
+
return (
|
356
|
+
param2template(self.id, params=params, extras=self.extras)
|
357
|
+
if self.id
|
358
|
+
else gen_id(
|
359
|
+
param2template(self.name, params=params, extras=self.extras)
|
360
|
+
)
|
361
|
+
)
|
362
|
+
|
354
363
|
|
355
364
|
class BaseAsyncStage(BaseStage):
|
356
365
|
"""Base Async Stage model to make any stage model allow async execution for
|
@@ -431,15 +440,14 @@ class BaseAsyncStage(BaseStage):
|
|
431
440
|
try:
|
432
441
|
rs: Result = await self.axecute(params, result=result, event=event)
|
433
442
|
return rs
|
434
|
-
except Exception as e:
|
443
|
+
except Exception as e:
|
435
444
|
e_name: str = e.__class__.__name__
|
436
445
|
await result.trace.aerror(f"[STAGE]: Handler {e_name}: {e}")
|
437
446
|
if dynamic("stage_raise_error", f=raise_error, extras=self.extras):
|
438
447
|
if isinstance(e, StageException):
|
439
448
|
raise
|
440
|
-
|
441
449
|
raise StageException(
|
442
|
-
f"{self.__class__.__name__}: {
|
450
|
+
f"{self.__class__.__name__}: {e_name}: {e}"
|
443
451
|
) from None
|
444
452
|
|
445
453
|
return result.catch(status=FAILED, context={"errors": to_dict(e)})
|
@@ -506,11 +514,9 @@ class EmptyStage(BaseAsyncStage):
|
|
506
514
|
message: str = param2template(
|
507
515
|
dedent(self.echo.strip("\n")), params, extras=self.extras
|
508
516
|
)
|
509
|
-
if "\n" in message:
|
510
|
-
message: str = NEWLINE + message.replace("\n", NEWLINE)
|
511
517
|
|
512
518
|
result.trace.info(
|
513
|
-
f"[STAGE]: Empty-
|
519
|
+
f"[STAGE]: Execute Empty-Stage: {self.name!r}: ( {message} )"
|
514
520
|
)
|
515
521
|
if self.sleep > 0:
|
516
522
|
if self.sleep > 5:
|
@@ -546,12 +552,8 @@ class EmptyStage(BaseAsyncStage):
|
|
546
552
|
message: str = param2template(
|
547
553
|
dedent(self.echo.strip("\n")), params, extras=self.extras
|
548
554
|
)
|
549
|
-
if "\n" in message:
|
550
|
-
message: str = NEWLINE + message.replace("\n", NEWLINE)
|
551
555
|
|
552
|
-
result.trace.info(
|
553
|
-
f"[STAGE]: Empty-Execute: {self.name!r}: ( {message} )"
|
554
|
-
)
|
556
|
+
result.trace.info(f"[STAGE]: Empty-Stage: {self.name!r}: ( {message} )")
|
555
557
|
if self.sleep > 0:
|
556
558
|
if self.sleep > 5:
|
557
559
|
await result.trace.ainfo(
|
@@ -561,7 +563,7 @@ class EmptyStage(BaseAsyncStage):
|
|
561
563
|
return result.catch(status=SUCCESS)
|
562
564
|
|
563
565
|
|
564
|
-
class BashStage(
|
566
|
+
class BashStage(BaseAsyncStage):
|
565
567
|
"""Bash stage executor that execute bash script on the current OS.
|
566
568
|
If your current OS is Windows, it will run on the bash from the current WSL.
|
567
569
|
It will use `bash` for Windows OS and use `sh` for Linux OS.
|
@@ -597,7 +599,16 @@ class BashStage(BaseStage):
|
|
597
599
|
@contextlib.asynccontextmanager
|
598
600
|
async def acreate_sh_file(
|
599
601
|
self, bash: str, env: DictStr, run_id: str | None = None
|
600
|
-
) -> AsyncIterator:
|
602
|
+
) -> AsyncIterator[TupleStr]:
|
603
|
+
"""Async create and write `.sh` file with the `aiofiles` package.
|
604
|
+
|
605
|
+
:param bash: (str) A bash statement.
|
606
|
+
:param env: (DictStr) An environment variable that set before run bash.
|
607
|
+
:param run_id: (str | None) A running stage ID that use for writing sh
|
608
|
+
file instead generate by UUID4.
|
609
|
+
|
610
|
+
:rtype: AsyncIterator[TupleStr]
|
611
|
+
"""
|
601
612
|
import aiofiles
|
602
613
|
|
603
614
|
f_name: str = f"{run_id or uuid.uuid4()}.sh"
|
@@ -616,7 +627,7 @@ class BashStage(BaseStage):
|
|
616
627
|
# NOTE: Make this .sh file able to executable.
|
617
628
|
make_exec(f"./{f_name}")
|
618
629
|
|
619
|
-
yield
|
630
|
+
yield f_shebang, f_name
|
620
631
|
|
621
632
|
# Note: Remove .sh file that use to run bash.
|
622
633
|
Path(f"./{f_name}").unlink()
|
@@ -625,9 +636,8 @@ class BashStage(BaseStage):
|
|
625
636
|
def create_sh_file(
|
626
637
|
self, bash: str, env: DictStr, run_id: str | None = None
|
627
638
|
) -> Iterator[TupleStr]:
|
628
|
-
"""
|
629
|
-
|
630
|
-
After that, it will auto delete this file automatic.
|
639
|
+
"""Create and write the `.sh` file before giving this file name to
|
640
|
+
context. After that, it will auto delete this file automatic.
|
631
641
|
|
632
642
|
:param bash: (str) A bash statement.
|
633
643
|
:param env: (DictStr) An environment variable that set before run bash.
|
@@ -635,6 +645,7 @@ class BashStage(BaseStage):
|
|
635
645
|
file instead generate by UUID4.
|
636
646
|
|
637
647
|
:rtype: Iterator[TupleStr]
|
648
|
+
:return: Return context of prepared bash statement that want to execute.
|
638
649
|
"""
|
639
650
|
f_name: str = f"{run_id or uuid.uuid4()}.sh"
|
640
651
|
f_shebang: str = "bash" if sys.platform.startswith("win") else "sh"
|
@@ -652,7 +663,7 @@ class BashStage(BaseStage):
|
|
652
663
|
# NOTE: Make this .sh file able to executable.
|
653
664
|
make_exec(f"./{f_name}")
|
654
665
|
|
655
|
-
yield
|
666
|
+
yield f_shebang, f_name
|
656
667
|
|
657
668
|
# Note: Remove .sh file that use to run bash.
|
658
669
|
Path(f"./{f_name}").unlink()
|
@@ -680,7 +691,7 @@ class BashStage(BaseStage):
|
|
680
691
|
extras=self.extras,
|
681
692
|
)
|
682
693
|
|
683
|
-
result.trace.info(f"[STAGE]: Shell-
|
694
|
+
result.trace.info(f"[STAGE]: Execute Shell-Stage: {self.name}")
|
684
695
|
|
685
696
|
bash: str = param2template(
|
686
697
|
dedent(self.bash.strip("\n")), params, extras=self.extras
|
@@ -691,21 +702,74 @@ class BashStage(BaseStage):
|
|
691
702
|
env=param2template(self.env, params, extras=self.extras),
|
692
703
|
run_id=result.run_id,
|
693
704
|
) as sh:
|
694
|
-
result.trace.debug(f"... Create `{sh[1]}` file.")
|
705
|
+
result.trace.debug(f"[STAGE]: ... Create `{sh[1]}` file.")
|
706
|
+
rs: CompletedProcess = subprocess.run(
|
707
|
+
sh,
|
708
|
+
shell=False,
|
709
|
+
check=False,
|
710
|
+
capture_output=True,
|
711
|
+
text=True,
|
712
|
+
encoding="utf-8",
|
713
|
+
)
|
714
|
+
if rs.returncode > 0:
|
715
|
+
e: str = rs.stderr.removesuffix("\n")
|
716
|
+
raise StageException(
|
717
|
+
f"Subprocess: {e}\n---( statement )---\n```bash\n{bash}\n```"
|
718
|
+
)
|
719
|
+
return result.catch(
|
720
|
+
status=SUCCESS,
|
721
|
+
context={
|
722
|
+
"return_code": rs.returncode,
|
723
|
+
"stdout": None if (out := rs.stdout.strip("\n")) == "" else out,
|
724
|
+
"stderr": None if (out := rs.stderr.strip("\n")) == "" else out,
|
725
|
+
},
|
726
|
+
)
|
727
|
+
|
728
|
+
async def axecute(
|
729
|
+
self,
|
730
|
+
params: DictData,
|
731
|
+
*,
|
732
|
+
result: Result | None = None,
|
733
|
+
event: Event | None = None,
|
734
|
+
) -> Result:
|
735
|
+
"""Async execution method for this Bash stage that only logging out to
|
736
|
+
stdout.
|
737
|
+
|
738
|
+
:param params: (DictData) A parameter data.
|
739
|
+
:param result: (Result) A Result instance for return context and status.
|
740
|
+
:param event: (Event) An Event manager instance that use to cancel this
|
741
|
+
execution if it forces stopped by parent execution.
|
742
|
+
|
743
|
+
:rtype: Result
|
744
|
+
"""
|
745
|
+
result: Result = result or Result(
|
746
|
+
run_id=gen_id(self.name + (self.id or ""), unique=True),
|
747
|
+
extras=self.extras,
|
748
|
+
)
|
749
|
+
await result.trace.ainfo(f"[STAGE]: Execute Shell-Stage: {self.name}")
|
750
|
+
bash: str = param2template(
|
751
|
+
dedent(self.bash.strip("\n")), params, extras=self.extras
|
752
|
+
)
|
753
|
+
|
754
|
+
async with self.acreate_sh_file(
|
755
|
+
bash=bash,
|
756
|
+
env=param2template(self.env, params, extras=self.extras),
|
757
|
+
run_id=result.run_id,
|
758
|
+
) as sh:
|
759
|
+
await result.trace.adebug(f"[STAGE]: ... Create `{sh[1]}` file.")
|
695
760
|
rs: CompletedProcess = subprocess.run(
|
696
|
-
sh,
|
761
|
+
sh,
|
762
|
+
shell=False,
|
763
|
+
check=False,
|
764
|
+
capture_output=True,
|
765
|
+
text=True,
|
766
|
+
encoding="utf-8",
|
697
767
|
)
|
698
768
|
|
699
769
|
if rs.returncode > 0:
|
700
|
-
|
701
|
-
e: str = (
|
702
|
-
rs.stderr.encode("utf-8").decode("utf-16")
|
703
|
-
if "\\x00" in rs.stderr
|
704
|
-
else rs.stderr
|
705
|
-
).removesuffix("\n")
|
770
|
+
e: str = rs.stderr.removesuffix("\n")
|
706
771
|
raise StageException(
|
707
|
-
f"Subprocess: {e}\n---( statement )---\n"
|
708
|
-
f"```bash\n{bash}\n```"
|
772
|
+
f"Subprocess: {e}\n---( statement )---\n```bash\n{bash}\n```"
|
709
773
|
)
|
710
774
|
return result.catch(
|
711
775
|
status=SUCCESS,
|
@@ -717,7 +781,7 @@ class BashStage(BaseStage):
|
|
717
781
|
)
|
718
782
|
|
719
783
|
|
720
|
-
class PyStage(
|
784
|
+
class PyStage(BaseAsyncStage):
|
721
785
|
"""Python stage that running the Python statement with the current globals
|
722
786
|
and passing an input additional variables via `exec` built-in function.
|
723
787
|
|
@@ -819,7 +883,7 @@ class PyStage(BaseStage):
|
|
819
883
|
| {"result": result}
|
820
884
|
)
|
821
885
|
|
822
|
-
result.trace.info(f"[STAGE]: Py-
|
886
|
+
result.trace.info(f"[STAGE]: Execute Py-Stage: {self.name}")
|
823
887
|
|
824
888
|
# WARNING: The exec build-in function is very dangerous. So, it
|
825
889
|
# should use the re module to validate exec-string before running.
|
@@ -849,15 +913,63 @@ class PyStage(BaseStage):
|
|
849
913
|
|
850
914
|
async def axecute(
|
851
915
|
self,
|
852
|
-
|
853
|
-
|
916
|
+
params: DictData,
|
917
|
+
*,
|
918
|
+
result: Result | None = None,
|
919
|
+
event: Event | None = None,
|
920
|
+
) -> Result:
|
921
|
+
"""Async execution method for this Bash stage that only logging out to
|
922
|
+
stdout.
|
923
|
+
|
924
|
+
:param params: (DictData) A parameter data.
|
925
|
+
:param result: (Result) A Result instance for return context and status.
|
926
|
+
:param event: (Event) An Event manager instance that use to cancel this
|
927
|
+
execution if it forces stopped by parent execution.
|
854
928
|
|
855
929
|
References:
|
856
930
|
- https://stackoverflow.com/questions/44859165/async-exec-in-python
|
931
|
+
|
932
|
+
:rtype: Result
|
857
933
|
"""
|
934
|
+
result: Result = result or Result(
|
935
|
+
run_id=gen_id(self.name + (self.id or ""), unique=True),
|
936
|
+
extras=self.extras,
|
937
|
+
)
|
938
|
+
lc: DictData = {}
|
939
|
+
gb: DictData = (
|
940
|
+
globals()
|
941
|
+
| param2template(self.vars, params, extras=self.extras)
|
942
|
+
| {"result": result}
|
943
|
+
)
|
944
|
+
await result.trace.ainfo(f"[STAGE]: Execute Py-Stage: {self.name}")
|
945
|
+
|
946
|
+
# WARNING: The exec build-in function is very dangerous. So, it
|
947
|
+
# should use the re module to validate exec-string before running.
|
948
|
+
exec(
|
949
|
+
param2template(dedent(self.run), params, extras=self.extras),
|
950
|
+
gb,
|
951
|
+
lc,
|
952
|
+
)
|
953
|
+
return result.catch(
|
954
|
+
status=SUCCESS,
|
955
|
+
context={
|
956
|
+
"locals": {k: lc[k] for k in self.filter_locals(lc)},
|
957
|
+
"globals": {
|
958
|
+
k: gb[k]
|
959
|
+
for k in gb
|
960
|
+
if (
|
961
|
+
not k.startswith("__")
|
962
|
+
and k != "annotations"
|
963
|
+
and not ismodule(gb[k])
|
964
|
+
and not isclass(gb[k])
|
965
|
+
and not isfunction(gb[k])
|
966
|
+
)
|
967
|
+
},
|
968
|
+
},
|
969
|
+
)
|
858
970
|
|
859
971
|
|
860
|
-
class CallStage(
|
972
|
+
class CallStage(BaseAsyncStage):
|
861
973
|
"""Call stage executor that call the Python function from registry with tag
|
862
974
|
decorator function in `reusables` module and run it with input arguments.
|
863
975
|
|
@@ -933,7 +1045,7 @@ class CallStage(BaseStage):
|
|
933
1045
|
)()
|
934
1046
|
|
935
1047
|
result.trace.info(
|
936
|
-
f"[STAGE]: Call-
|
1048
|
+
f"[STAGE]: Execute Call-Stage: {call_func.name}@{call_func.tag}"
|
937
1049
|
)
|
938
1050
|
|
939
1051
|
# VALIDATE: check input task caller parameters that exists before
|
@@ -985,8 +1097,97 @@ class CallStage(BaseStage):
|
|
985
1097
|
rs: DictData = rs.model_dump(by_alias=True)
|
986
1098
|
elif not isinstance(rs, dict):
|
987
1099
|
raise TypeError(
|
988
|
-
f"Return type: '{call_func.name}@{call_func.tag}'
|
989
|
-
f"serialize
|
1100
|
+
f"Return type: '{call_func.name}@{call_func.tag}' can not "
|
1101
|
+
f"serialize, you must set return be `dict` or Pydantic "
|
1102
|
+
f"model."
|
1103
|
+
)
|
1104
|
+
return result.catch(status=SUCCESS, context=rs)
|
1105
|
+
|
1106
|
+
async def axecute(
|
1107
|
+
self,
|
1108
|
+
params: DictData,
|
1109
|
+
*,
|
1110
|
+
result: Result | None = None,
|
1111
|
+
event: Event | None = None,
|
1112
|
+
) -> Result:
|
1113
|
+
"""Async execution method for this Bash stage that only logging out to
|
1114
|
+
stdout.
|
1115
|
+
|
1116
|
+
:param params: (DictData) A parameter data.
|
1117
|
+
:param result: (Result) A Result instance for return context and status.
|
1118
|
+
:param event: (Event) An Event manager instance that use to cancel this
|
1119
|
+
execution if it forces stopped by parent execution.
|
1120
|
+
|
1121
|
+
References:
|
1122
|
+
- https://stackoverflow.com/questions/44859165/async-exec-in-python
|
1123
|
+
|
1124
|
+
:rtype: Result
|
1125
|
+
"""
|
1126
|
+
result: Result = result or Result(
|
1127
|
+
run_id=gen_id(self.name + (self.id or ""), unique=True),
|
1128
|
+
extras=self.extras,
|
1129
|
+
)
|
1130
|
+
|
1131
|
+
call_func: TagFunc = extract_call(
|
1132
|
+
param2template(self.uses, params, extras=self.extras),
|
1133
|
+
registries=self.extras.get("registry_caller"),
|
1134
|
+
)()
|
1135
|
+
|
1136
|
+
await result.trace.ainfo(
|
1137
|
+
f"[STAGE]: Execute Call-Stage: {call_func.name}@{call_func.tag}"
|
1138
|
+
)
|
1139
|
+
|
1140
|
+
# VALIDATE: check input task caller parameters that exists before
|
1141
|
+
# calling.
|
1142
|
+
args: DictData = {"result": result} | param2template(
|
1143
|
+
self.args, params, extras=self.extras
|
1144
|
+
)
|
1145
|
+
ips = inspect.signature(call_func)
|
1146
|
+
necessary_params: list[str] = []
|
1147
|
+
has_keyword: bool = False
|
1148
|
+
for k in ips.parameters:
|
1149
|
+
if (
|
1150
|
+
v := ips.parameters[k]
|
1151
|
+
).default == Parameter.empty and v.kind not in (
|
1152
|
+
Parameter.VAR_KEYWORD,
|
1153
|
+
Parameter.VAR_POSITIONAL,
|
1154
|
+
):
|
1155
|
+
necessary_params.append(k)
|
1156
|
+
elif v.kind == Parameter.VAR_KEYWORD:
|
1157
|
+
has_keyword = True
|
1158
|
+
|
1159
|
+
if any(
|
1160
|
+
(k.removeprefix("_") not in args and k not in args)
|
1161
|
+
for k in necessary_params
|
1162
|
+
):
|
1163
|
+
raise ValueError(
|
1164
|
+
f"Necessary params, ({', '.join(necessary_params)}, ), "
|
1165
|
+
f"does not set to args, {list(args.keys())}."
|
1166
|
+
)
|
1167
|
+
|
1168
|
+
if "result" not in ips.parameters and not has_keyword:
|
1169
|
+
args.pop("result")
|
1170
|
+
|
1171
|
+
args = self.parse_model_args(call_func, args, result)
|
1172
|
+
|
1173
|
+
if inspect.iscoroutinefunction(call_func):
|
1174
|
+
rs: DictData = await call_func(
|
1175
|
+
**param2template(args, params, extras=self.extras)
|
1176
|
+
)
|
1177
|
+
else:
|
1178
|
+
rs: DictData = call_func(
|
1179
|
+
**param2template(args, params, extras=self.extras)
|
1180
|
+
)
|
1181
|
+
|
1182
|
+
# VALIDATE:
|
1183
|
+
# Check the result type from call function, it should be dict.
|
1184
|
+
if isinstance(rs, BaseModel):
|
1185
|
+
rs: DictData = rs.model_dump(by_alias=True)
|
1186
|
+
elif not isinstance(rs, dict):
|
1187
|
+
raise TypeError(
|
1188
|
+
f"Return type: '{call_func.name}@{call_func.tag}' can not "
|
1189
|
+
f"serialize, you must set return be `dict` or Pydantic "
|
1190
|
+
f"model."
|
990
1191
|
)
|
991
1192
|
return result.catch(status=SUCCESS, context=rs)
|
992
1193
|
|
@@ -1078,7 +1279,6 @@ class TriggerStage(BaseStage):
|
|
1078
1279
|
|
1079
1280
|
:rtype: Result
|
1080
1281
|
"""
|
1081
|
-
from .exceptions import WorkflowException
|
1082
1282
|
from .workflow import Workflow
|
1083
1283
|
|
1084
1284
|
result: Result = result or Result(
|
@@ -1087,19 +1287,15 @@ class TriggerStage(BaseStage):
|
|
1087
1287
|
)
|
1088
1288
|
|
1089
1289
|
_trigger: str = param2template(self.trigger, params, extras=self.extras)
|
1090
|
-
result.trace.info(f"[STAGE]: Trigger-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
)
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
)
|
1100
|
-
except WorkflowException as e:
|
1101
|
-
raise StageException("Trigger workflow stage was failed") from e
|
1102
|
-
|
1290
|
+
result.trace.info(f"[STAGE]: Execute Trigger-Stage: {_trigger!r}")
|
1291
|
+
rs: Result = Workflow.from_conf(
|
1292
|
+
name=_trigger,
|
1293
|
+
extras=self.extras | {"stage_raise_error": True},
|
1294
|
+
).execute(
|
1295
|
+
params=param2template(self.params, params, extras=self.extras),
|
1296
|
+
parent_run_id=result.run_id,
|
1297
|
+
event=event,
|
1298
|
+
)
|
1103
1299
|
if rs.status == FAILED:
|
1104
1300
|
err_msg: str | None = (
|
1105
1301
|
f" with:\n{msg}"
|
@@ -1112,7 +1308,7 @@ class TriggerStage(BaseStage):
|
|
1112
1308
|
return rs
|
1113
1309
|
|
1114
1310
|
|
1115
|
-
class ParallelStage(BaseStage):
|
1311
|
+
class ParallelStage(BaseStage):
|
1116
1312
|
"""Parallel stage executor that execute branch stages with multithreading.
|
1117
1313
|
This stage let you set the fix branches for running child stage inside it on
|
1118
1314
|
multithread pool.
|
@@ -1193,7 +1389,7 @@ class ParallelStage(BaseStage): # pragma: no cov
|
|
1193
1389
|
"Branch-Stage was canceled from event that had set before "
|
1194
1390
|
"stage branch execution."
|
1195
1391
|
)
|
1196
|
-
|
1392
|
+
result.catch(
|
1197
1393
|
status=CANCEL,
|
1198
1394
|
parallel={
|
1199
1395
|
branch: {
|
@@ -1203,6 +1399,7 @@ class ParallelStage(BaseStage): # pragma: no cov
|
|
1203
1399
|
}
|
1204
1400
|
},
|
1205
1401
|
)
|
1402
|
+
raise StageException(error_msg, refs=branch)
|
1206
1403
|
|
1207
1404
|
try:
|
1208
1405
|
rs: Result = stage.handler_execute(
|
@@ -1214,10 +1411,7 @@ class ParallelStage(BaseStage): # pragma: no cov
|
|
1214
1411
|
)
|
1215
1412
|
stage.set_outputs(rs.context, to=output)
|
1216
1413
|
stage.set_outputs(stage.get_outputs(output), to=context)
|
1217
|
-
except
|
1218
|
-
result.trace.error(
|
1219
|
-
f"[STAGE]: {e.__class__.__name__}:{NEWLINE}{e}"
|
1220
|
-
)
|
1414
|
+
except StageException as e:
|
1221
1415
|
result.catch(
|
1222
1416
|
status=FAILED,
|
1223
1417
|
parallel={
|
@@ -1228,9 +1422,7 @@ class ParallelStage(BaseStage): # pragma: no cov
|
|
1228
1422
|
},
|
1229
1423
|
},
|
1230
1424
|
)
|
1231
|
-
raise StageException(
|
1232
|
-
f"Sub-Stage raise: {e.__class__.__name__}: {e}"
|
1233
|
-
) from None
|
1425
|
+
raise StageException(str(e), refs=branch) from e
|
1234
1426
|
|
1235
1427
|
if rs.status == FAILED:
|
1236
1428
|
error_msg: str = (
|
@@ -1247,7 +1439,7 @@ class ParallelStage(BaseStage): # pragma: no cov
|
|
1247
1439
|
},
|
1248
1440
|
},
|
1249
1441
|
)
|
1250
|
-
raise StageException(error_msg)
|
1442
|
+
raise StageException(error_msg, refs=branch)
|
1251
1443
|
|
1252
1444
|
return result.catch(
|
1253
1445
|
status=SUCCESS,
|
@@ -1279,12 +1471,12 @@ class ParallelStage(BaseStage): # pragma: no cov
|
|
1279
1471
|
run_id=gen_id(self.name + (self.id or ""), unique=True),
|
1280
1472
|
extras=self.extras,
|
1281
1473
|
)
|
1282
|
-
event: Event = Event()
|
1474
|
+
event: Event = event or Event()
|
1283
1475
|
result.trace.info(
|
1284
|
-
f"[STAGE]: Parallel-
|
1476
|
+
f"[STAGE]: Execute Parallel-Stage: {self.max_workers} workers."
|
1285
1477
|
)
|
1286
1478
|
result.catch(status=WAIT, context={"parallel": {}})
|
1287
|
-
if event and event.is_set():
|
1479
|
+
if event and event.is_set():
|
1288
1480
|
return result.catch(
|
1289
1481
|
status=CANCEL,
|
1290
1482
|
context={
|
@@ -1319,12 +1511,14 @@ class ParallelStage(BaseStage): # pragma: no cov
|
|
1319
1511
|
except StageException as e:
|
1320
1512
|
status = FAILED
|
1321
1513
|
result.trace.error(
|
1322
|
-
f"[STAGE]: {e.__class__.__name__}
|
1514
|
+
f"[STAGE]: Error Handler:||{e.__class__.__name__}:||{e}"
|
1323
1515
|
)
|
1324
1516
|
if "errors" in context:
|
1325
|
-
context["errors"].
|
1517
|
+
context["errors"][e.refs] = e.to_dict()
|
1326
1518
|
else:
|
1327
|
-
context["errors"] =
|
1519
|
+
context["errors"] = e.to_dict(with_refs=True)
|
1520
|
+
except CancelledError:
|
1521
|
+
pass
|
1328
1522
|
return result.catch(status=status, context=context)
|
1329
1523
|
|
1330
1524
|
|
@@ -1378,7 +1572,8 @@ class ForEachStage(BaseStage):
|
|
1378
1572
|
*,
|
1379
1573
|
event: Event | None = None,
|
1380
1574
|
) -> Result:
|
1381
|
-
"""Execute all stage with specific foreach
|
1575
|
+
"""Execute all nested stage that set on this stage with specific foreach
|
1576
|
+
item parameter.
|
1382
1577
|
|
1383
1578
|
:param item: (str | int) An item that want to execution.
|
1384
1579
|
:param params: (DictData) A parameter data.
|
@@ -1386,7 +1581,9 @@ class ForEachStage(BaseStage):
|
|
1386
1581
|
:param event: (Event) An Event manager instance that use to cancel this
|
1387
1582
|
execution if it forces stopped by parent execution.
|
1388
1583
|
|
1389
|
-
:raise StageException: If
|
1584
|
+
:raise StageException: If event was set.
|
1585
|
+
:raise StageException: If the stage execution raise any Exception error.
|
1586
|
+
:raise StageException: If the result from execution has `FAILED` status.
|
1390
1587
|
|
1391
1588
|
:rtype: Result
|
1392
1589
|
"""
|
@@ -1404,12 +1601,11 @@ class ForEachStage(BaseStage):
|
|
1404
1601
|
stage.set_outputs(output={"skipped": True}, to=output)
|
1405
1602
|
continue
|
1406
1603
|
|
1407
|
-
if event and event.is_set():
|
1604
|
+
if event and event.is_set():
|
1408
1605
|
error_msg: str = (
|
1409
|
-
"Item-Stage was canceled
|
1410
|
-
"stage item execution."
|
1606
|
+
"Item-Stage was canceled because event was set."
|
1411
1607
|
)
|
1412
|
-
|
1608
|
+
result.catch(
|
1413
1609
|
status=CANCEL,
|
1414
1610
|
foreach={
|
1415
1611
|
item: {
|
@@ -1419,6 +1615,7 @@ class ForEachStage(BaseStage):
|
|
1419
1615
|
}
|
1420
1616
|
},
|
1421
1617
|
)
|
1618
|
+
raise StageException(error_msg, refs=item)
|
1422
1619
|
|
1423
1620
|
try:
|
1424
1621
|
rs: Result = stage.handler_execute(
|
@@ -1430,10 +1627,7 @@ class ForEachStage(BaseStage):
|
|
1430
1627
|
)
|
1431
1628
|
stage.set_outputs(rs.context, to=output)
|
1432
1629
|
stage.set_outputs(stage.get_outputs(output), to=context)
|
1433
|
-
except
|
1434
|
-
result.trace.error(
|
1435
|
-
f"[STAGE]: {e.__class__.__name__}:{NEWLINE}{e}"
|
1436
|
-
)
|
1630
|
+
except StageException as e:
|
1437
1631
|
result.catch(
|
1438
1632
|
status=FAILED,
|
1439
1633
|
foreach={
|
@@ -1444,9 +1638,7 @@ class ForEachStage(BaseStage):
|
|
1444
1638
|
},
|
1445
1639
|
},
|
1446
1640
|
)
|
1447
|
-
raise StageException(
|
1448
|
-
f"Sub-Stage raise: {e.__class__.__name__}: {e}"
|
1449
|
-
) from None
|
1641
|
+
raise StageException(str(e), refs=item) from e
|
1450
1642
|
|
1451
1643
|
if rs.status == FAILED:
|
1452
1644
|
error_msg: str = (
|
@@ -1464,7 +1656,7 @@ class ForEachStage(BaseStage):
|
|
1464
1656
|
},
|
1465
1657
|
},
|
1466
1658
|
)
|
1467
|
-
raise StageException(error_msg)
|
1659
|
+
raise StageException(error_msg, refs=item)
|
1468
1660
|
|
1469
1661
|
return result.catch(
|
1470
1662
|
status=SUCCESS,
|
@@ -1498,7 +1690,7 @@ class ForEachStage(BaseStage):
|
|
1498
1690
|
run_id=gen_id(self.name + (self.id or ""), unique=True),
|
1499
1691
|
extras=self.extras,
|
1500
1692
|
)
|
1501
|
-
event: Event = Event()
|
1693
|
+
event: Event = event or Event()
|
1502
1694
|
foreach: Union[list[str], list[int]] = (
|
1503
1695
|
param2template(self.foreach, params, extras=self.extras)
|
1504
1696
|
if isinstance(self.foreach, str)
|
@@ -1509,9 +1701,9 @@ class ForEachStage(BaseStage):
|
|
1509
1701
|
if not isinstance(foreach, list):
|
1510
1702
|
raise TypeError(f"Does not support foreach: {foreach!r}")
|
1511
1703
|
|
1512
|
-
result.trace.info(f"[STAGE]: Foreach-
|
1704
|
+
result.trace.info(f"[STAGE]: Execute Foreach-Stage: {foreach!r}.")
|
1513
1705
|
result.catch(status=WAIT, context={"items": foreach, "foreach": {}})
|
1514
|
-
if event and event.is_set():
|
1706
|
+
if event and event.is_set():
|
1515
1707
|
return result.catch(
|
1516
1708
|
status=CANCEL,
|
1517
1709
|
context={
|
@@ -1542,14 +1734,18 @@ class ForEachStage(BaseStage):
|
|
1542
1734
|
done, not_done = wait(futures, return_when=FIRST_EXCEPTION)
|
1543
1735
|
if len(done) != len(futures):
|
1544
1736
|
result.trace.warning(
|
1545
|
-
"[STAGE]: Set event for stop pending stage
|
1737
|
+
"[STAGE]: Set event for stop pending for-each stage."
|
1546
1738
|
)
|
1547
1739
|
event.set()
|
1548
1740
|
for future in not_done:
|
1549
1741
|
future.cancel()
|
1742
|
+
time.sleep(0.075)
|
1550
1743
|
|
1551
1744
|
nd: str = f", item not run: {not_done}" if not_done else ""
|
1552
|
-
result.trace.debug(
|
1745
|
+
result.trace.debug(
|
1746
|
+
f"[STAGE]: ... Foreach-Stage set failed event{nd}"
|
1747
|
+
)
|
1748
|
+
done: list[Future] = as_completed(futures)
|
1553
1749
|
|
1554
1750
|
for future in done:
|
1555
1751
|
try:
|
@@ -1557,9 +1753,14 @@ class ForEachStage(BaseStage):
|
|
1557
1753
|
except StageException as e:
|
1558
1754
|
status = FAILED
|
1559
1755
|
result.trace.error(
|
1560
|
-
f"[STAGE]: {e.__class__.__name__}
|
1756
|
+
f"[STAGE]: Error Handler:||{e.__class__.__name__}:||{e}"
|
1561
1757
|
)
|
1562
|
-
|
1758
|
+
if "errors" in context:
|
1759
|
+
context["errors"][e.refs] = e.to_dict()
|
1760
|
+
else:
|
1761
|
+
context["errors"] = e.to_dict(with_refs=True)
|
1762
|
+
except CancelledError:
|
1763
|
+
pass
|
1563
1764
|
return result.catch(status=status, context=context)
|
1564
1765
|
|
1565
1766
|
|
@@ -1628,7 +1829,7 @@ class UntilStage(BaseStage):
|
|
1628
1829
|
:rtype: tuple[Result, T]
|
1629
1830
|
:return: Return a pair of Result and changed item.
|
1630
1831
|
"""
|
1631
|
-
result.trace.debug(f"... Execute until item: {item!r}")
|
1832
|
+
result.trace.debug(f"[STAGE]: ... Execute until item: {item!r}")
|
1632
1833
|
context: DictData = copy.deepcopy(params)
|
1633
1834
|
context.update({"item": item})
|
1634
1835
|
output: DictData = {"loop": loop, "item": item, "stages": {}}
|
@@ -1677,10 +1878,7 @@ class UntilStage(BaseStage):
|
|
1677
1878
|
next_item = _output["item"]
|
1678
1879
|
|
1679
1880
|
stage.set_outputs(_output, to=context)
|
1680
|
-
except
|
1681
|
-
result.trace.error(
|
1682
|
-
f"[STAGE]: {e.__class__.__name__}:{NEWLINE}{e}"
|
1683
|
-
)
|
1881
|
+
except StageException as e:
|
1684
1882
|
result.catch(
|
1685
1883
|
status=FAILED,
|
1686
1884
|
until={
|
@@ -1692,9 +1890,7 @@ class UntilStage(BaseStage):
|
|
1692
1890
|
}
|
1693
1891
|
},
|
1694
1892
|
)
|
1695
|
-
raise
|
1696
|
-
f"Sub-Stage execution error: {e.__class__.__name__}: {e}"
|
1697
|
-
) from None
|
1893
|
+
raise
|
1698
1894
|
|
1699
1895
|
if rs.status == FAILED:
|
1700
1896
|
error_msg: str = (
|
@@ -1749,7 +1945,7 @@ class UntilStage(BaseStage):
|
|
1749
1945
|
extras=self.extras,
|
1750
1946
|
)
|
1751
1947
|
|
1752
|
-
result.trace.info(f"[STAGE]: Until-
|
1948
|
+
result.trace.info(f"[STAGE]: Execute Until-Stage: {self.until}")
|
1753
1949
|
item: Union[str, int, bool] = param2template(
|
1754
1950
|
self.item, params, extras=self.extras
|
1755
1951
|
)
|
@@ -1781,7 +1977,7 @@ class UntilStage(BaseStage):
|
|
1781
1977
|
loop += 1
|
1782
1978
|
if item is None:
|
1783
1979
|
result.trace.warning(
|
1784
|
-
f"... Loop-Execute not set item. It use loop: {loop} by "
|
1980
|
+
f"[STAGE]: ... Loop-Execute not set item. It use loop: {loop} by "
|
1785
1981
|
f"default."
|
1786
1982
|
)
|
1787
1983
|
item: int = loop
|
@@ -1892,11 +2088,11 @@ class CaseStage(BaseStage):
|
|
1892
2088
|
stage.extras = self.extras
|
1893
2089
|
|
1894
2090
|
if stage.is_skipped(params=context):
|
1895
|
-
result.trace.info(f"... Skip stage: {stage.iden!r}")
|
2091
|
+
result.trace.info(f"[STAGE]: ... Skip stage: {stage.iden!r}")
|
1896
2092
|
stage.set_outputs(output={"skipped": True}, to=output)
|
1897
2093
|
continue
|
1898
2094
|
|
1899
|
-
if event and event.is_set():
|
2095
|
+
if event and event.is_set():
|
1900
2096
|
error_msg: str = (
|
1901
2097
|
"Case-Stage was canceled from event that had set before "
|
1902
2098
|
"stage case execution."
|
@@ -1920,8 +2116,7 @@ class CaseStage(BaseStage):
|
|
1920
2116
|
)
|
1921
2117
|
stage.set_outputs(rs.context, to=output)
|
1922
2118
|
stage.set_outputs(stage.get_outputs(output), to=context)
|
1923
|
-
except
|
1924
|
-
result.trace.error(f"[STAGE]: {e.__class__.__name__}: {e}")
|
2119
|
+
except StageException as e:
|
1925
2120
|
return result.catch(
|
1926
2121
|
status=FAILED,
|
1927
2122
|
context={
|
@@ -1977,7 +2172,7 @@ class CaseStage(BaseStage):
|
|
1977
2172
|
self.case, params, extras=self.extras
|
1978
2173
|
)
|
1979
2174
|
|
1980
|
-
result.trace.info(f"[STAGE]: Case-
|
2175
|
+
result.trace.info(f"[STAGE]: Execute Case-Stage: {_case!r}.")
|
1981
2176
|
_else: Optional[Match] = None
|
1982
2177
|
stages: Optional[list[Stage]] = None
|
1983
2178
|
for match in self.match:
|
@@ -1997,7 +2192,7 @@ class CaseStage(BaseStage):
|
|
1997
2192
|
"any case."
|
1998
2193
|
)
|
1999
2194
|
result.trace.info(
|
2000
|
-
"... Skip this stage because it does not match."
|
2195
|
+
"[STAGE]: ... Skip this stage because it does not match."
|
2001
2196
|
)
|
2002
2197
|
error_msg: str = (
|
2003
2198
|
"Case-Stage was canceled because it does not match any "
|
@@ -2010,7 +2205,7 @@ class CaseStage(BaseStage):
|
|
2010
2205
|
_case: str = "_"
|
2011
2206
|
stages: list[Stage] = _else.stages
|
2012
2207
|
|
2013
|
-
if event and event.is_set():
|
2208
|
+
if event and event.is_set():
|
2014
2209
|
return result.catch(
|
2015
2210
|
status=CANCEL,
|
2016
2211
|
context={
|
@@ -2026,7 +2221,7 @@ class CaseStage(BaseStage):
|
|
2026
2221
|
)
|
2027
2222
|
|
2028
2223
|
|
2029
|
-
class RaiseStage(
|
2224
|
+
class RaiseStage(BaseAsyncStage):
|
2030
2225
|
"""Raise error stage executor that raise `StageException` that use a message
|
2031
2226
|
field for making error message before raise.
|
2032
2227
|
|
@@ -2064,7 +2259,34 @@ class RaiseStage(BaseStage): # pragma: no cov
|
|
2064
2259
|
extras=self.extras,
|
2065
2260
|
)
|
2066
2261
|
message: str = param2template(self.message, params, extras=self.extras)
|
2067
|
-
result.trace.info(f"[STAGE]: Raise-
|
2262
|
+
result.trace.info(f"[STAGE]: Execute Raise-Stage: {message!r}.")
|
2263
|
+
raise StageException(message)
|
2264
|
+
|
2265
|
+
async def axecute(
|
2266
|
+
self,
|
2267
|
+
params: DictData,
|
2268
|
+
*,
|
2269
|
+
result: Result | None = None,
|
2270
|
+
event: Event | None = None,
|
2271
|
+
) -> Result:
|
2272
|
+
"""Async execution method for this Empty stage that only logging out to
|
2273
|
+
stdout.
|
2274
|
+
|
2275
|
+
:param params: (DictData) A context data that want to add output result.
|
2276
|
+
But this stage does not pass any output.
|
2277
|
+
:param result: (Result) A result object for keeping context and status
|
2278
|
+
data.
|
2279
|
+
:param event: (Event) An event manager that use to track parent execute
|
2280
|
+
was not force stopped.
|
2281
|
+
|
2282
|
+
:rtype: Result
|
2283
|
+
"""
|
2284
|
+
result: Result = result or Result(
|
2285
|
+
run_id=gen_id(self.name + (self.id or ""), unique=True),
|
2286
|
+
extras=self.extras,
|
2287
|
+
)
|
2288
|
+
message: str = param2template(self.message, params, extras=self.extras)
|
2289
|
+
await result.trace.ainfo(f"[STAGE]: Execute Raise-Stage: {message!r}.")
|
2068
2290
|
raise StageException(message)
|
2069
2291
|
|
2070
2292
|
|
@@ -2142,7 +2364,7 @@ class DockerStage(BaseStage): # pragma: no cov
|
|
2142
2364
|
decode=True,
|
2143
2365
|
)
|
2144
2366
|
for line in resp:
|
2145
|
-
result.trace.info(f"... {line}")
|
2367
|
+
result.trace.info(f"[STAGE]: ... {line}")
|
2146
2368
|
|
2147
2369
|
if event and event.is_set():
|
2148
2370
|
error_msg: str = (
|
@@ -2178,7 +2400,7 @@ class DockerStage(BaseStage): # pragma: no cov
|
|
2178
2400
|
)
|
2179
2401
|
|
2180
2402
|
for line in container.logs(stream=True, timestamps=True):
|
2181
|
-
result.trace.info(f"... {line.strip().decode()}")
|
2403
|
+
result.trace.info(f"[STAGE]: ... {line.strip().decode()}")
|
2182
2404
|
|
2183
2405
|
# NOTE: This code copy from the docker package.
|
2184
2406
|
exit_status: int = container.wait()["StatusCode"]
|
@@ -2221,8 +2443,9 @@ class DockerStage(BaseStage): # pragma: no cov
|
|
2221
2443
|
extras=self.extras,
|
2222
2444
|
)
|
2223
2445
|
|
2224
|
-
result.trace.info(
|
2225
|
-
|
2446
|
+
result.trace.info(
|
2447
|
+
f"[STAGE]: Execute Docker-Stage: {self.image}:{self.tag}"
|
2448
|
+
)
|
2226
2449
|
raise NotImplementedError("Docker Stage does not implement yet.")
|
2227
2450
|
|
2228
2451
|
|
@@ -2316,7 +2539,7 @@ class VirtualPyStage(PyStage): # pragma: no cov
|
|
2316
2539
|
extras=self.extras,
|
2317
2540
|
)
|
2318
2541
|
|
2319
|
-
result.trace.info(f"[STAGE]:
|
2542
|
+
result.trace.info(f"[STAGE]: Execute VirtualPy-Stage: {self.name}")
|
2320
2543
|
run: str = param2template(dedent(self.run), params, extras=self.extras)
|
2321
2544
|
with self.create_py_file(
|
2322
2545
|
py=run,
|
@@ -2324,7 +2547,7 @@ class VirtualPyStage(PyStage): # pragma: no cov
|
|
2324
2547
|
deps=param2template(self.deps, params, extras=self.extras),
|
2325
2548
|
run_id=result.run_id,
|
2326
2549
|
) as py:
|
2327
|
-
result.trace.debug(f"... Create `{py}` file.")
|
2550
|
+
result.trace.debug(f"[STAGE]: ... Create `{py}` file.")
|
2328
2551
|
rs: CompletedProcess = subprocess.run(
|
2329
2552
|
["uv", "run", py, "--no-cache"],
|
2330
2553
|
# ["uv", "run", "--python", "3.9", py],
|
@@ -2375,4 +2598,4 @@ Stage = Annotated[
|
|
2375
2598
|
EmptyStage,
|
2376
2599
|
],
|
2377
2600
|
Field(union_mode="smart"),
|
2378
|
-
]
|
2601
|
+
] # pragma: no cov
|