ddeutil-workflow 0.0.47__py3-none-any.whl → 0.0.49__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/__init__.py +4 -2
- ddeutil/workflow/api/api.py +2 -1
- ddeutil/workflow/api/repeat.py +2 -1
- ddeutil/workflow/api/routes/job.py +1 -1
- ddeutil/workflow/api/routes/logs.py +6 -5
- ddeutil/workflow/api/routes/schedules.py +2 -1
- ddeutil/workflow/api/routes/workflows.py +2 -2
- ddeutil/workflow/conf.py +61 -66
- ddeutil/workflow/job.py +13 -5
- ddeutil/workflow/logs.py +282 -105
- ddeutil/workflow/result.py +19 -8
- ddeutil/workflow/reusables.py +4 -5
- ddeutil/workflow/scheduler.py +70 -50
- ddeutil/workflow/stages.py +288 -83
- ddeutil/workflow/utils.py +3 -3
- ddeutil/workflow/workflow.py +135 -103
- {ddeutil_workflow-0.0.47.dist-info → ddeutil_workflow-0.0.49.dist-info}/METADATA +24 -26
- ddeutil_workflow-0.0.49.dist-info/RECORD +31 -0
- ddeutil_workflow-0.0.47.dist-info/RECORD +0 -31
- {ddeutil_workflow-0.0.47.dist-info → ddeutil_workflow-0.0.49.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.47.dist-info → ddeutil_workflow-0.0.49.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.47.dist-info → ddeutil_workflow-0.0.49.dist-info}/top_level.txt +0 -0
ddeutil/workflow/stages.py
CHANGED
@@ -38,13 +38,12 @@ from concurrent.futures import (
|
|
38
38
|
ThreadPoolExecutor,
|
39
39
|
as_completed,
|
40
40
|
)
|
41
|
-
from dataclasses import is_dataclass
|
42
41
|
from inspect import Parameter
|
43
42
|
from pathlib import Path
|
44
43
|
from subprocess import CompletedProcess
|
45
44
|
from textwrap import dedent
|
46
45
|
from threading import Event
|
47
|
-
from typing import Annotated, Any, Optional, Union, get_type_hints
|
46
|
+
from typing import Annotated, Any, Optional, TypeVar, Union, get_type_hints
|
48
47
|
|
49
48
|
from pydantic import BaseModel, Field
|
50
49
|
from pydantic.functional_validators import model_validator
|
@@ -60,17 +59,7 @@ from .utils import (
|
|
60
59
|
make_exec,
|
61
60
|
)
|
62
61
|
|
63
|
-
|
64
|
-
"EmptyStage",
|
65
|
-
"BashStage",
|
66
|
-
"PyStage",
|
67
|
-
"CallStage",
|
68
|
-
"TriggerStage",
|
69
|
-
"ForEachStage",
|
70
|
-
"ParallelStage",
|
71
|
-
"RaiseStage",
|
72
|
-
"Stage",
|
73
|
-
)
|
62
|
+
T = TypeVar("T")
|
74
63
|
|
75
64
|
|
76
65
|
class BaseStage(BaseModel, ABC):
|
@@ -206,6 +195,7 @@ class BaseStage(BaseModel, ABC):
|
|
206
195
|
run_id=run_id,
|
207
196
|
parent_run_id=parent_run_id,
|
208
197
|
id_logic=self.iden,
|
198
|
+
extras=self.extras,
|
209
199
|
)
|
210
200
|
|
211
201
|
try:
|
@@ -284,6 +274,25 @@ class BaseStage(BaseModel, ABC):
|
|
284
274
|
to["stages"][_id] = {"outputs": output, **skipping, **errors}
|
285
275
|
return to
|
286
276
|
|
277
|
+
def get_outputs(self, outputs: DictData) -> DictData:
|
278
|
+
"""Get the outputs from stages data.
|
279
|
+
|
280
|
+
:rtype: DictData
|
281
|
+
"""
|
282
|
+
if self.id is None and not dynamic(
|
283
|
+
"stage_default_id", extras=self.extras
|
284
|
+
):
|
285
|
+
return {}
|
286
|
+
|
287
|
+
_id: str = (
|
288
|
+
param2template(self.id, params=outputs, extras=self.extras)
|
289
|
+
if self.id
|
290
|
+
else gen_id(
|
291
|
+
param2template(self.name, params=outputs, extras=self.extras)
|
292
|
+
)
|
293
|
+
)
|
294
|
+
return outputs.get("stages", {}).get(_id, {})
|
295
|
+
|
287
296
|
def is_skipped(self, params: DictData | None = None) -> bool:
|
288
297
|
"""Return true if condition of this stage do not correct. This process
|
289
298
|
use build-in eval function to execute the if-condition.
|
@@ -389,14 +398,15 @@ class BaseAsyncStage(BaseStage):
|
|
389
398
|
run_id=run_id,
|
390
399
|
parent_run_id=parent_run_id,
|
391
400
|
id_logic=self.iden,
|
401
|
+
extras=self.extras,
|
392
402
|
)
|
393
403
|
|
394
404
|
try:
|
395
405
|
rs: Result = await self.axecute(params, result=result, event=event)
|
396
|
-
if to is not None:
|
406
|
+
if to is not None: # pragma: no cov
|
397
407
|
return self.set_outputs(rs.context, to=to)
|
398
408
|
return rs
|
399
|
-
except Exception as e:
|
409
|
+
except Exception as e: # pragma: no cov
|
400
410
|
await result.trace.aerror(f"[STAGE]: {e.__class__.__name__}: {e}")
|
401
411
|
|
402
412
|
if dynamic("stage_raise_error", f=raise_error, extras=self.extras):
|
@@ -818,7 +828,7 @@ class CallStage(BaseStage):
|
|
818
828
|
has_keyword: bool = False
|
819
829
|
call_func: TagFunc = extract_call(
|
820
830
|
param2template(self.uses, params, extras=self.extras),
|
821
|
-
registries=self.extras.get("
|
831
|
+
registries=self.extras.get("registry_caller"),
|
822
832
|
)()
|
823
833
|
|
824
834
|
# VALIDATE: check input task caller parameters that exists before
|
@@ -856,6 +866,7 @@ class CallStage(BaseStage):
|
|
856
866
|
)
|
857
867
|
|
858
868
|
args = self.parse_model_args(call_func, args, result)
|
869
|
+
|
859
870
|
if inspect.iscoroutinefunction(call_func):
|
860
871
|
loop = asyncio.get_event_loop()
|
861
872
|
rs: DictData = loop.run_until_complete(
|
@@ -904,8 +915,11 @@ class CallStage(BaseStage):
|
|
904
915
|
args[arg] = args.pop(arg.removeprefix("_"))
|
905
916
|
|
906
917
|
t: Any = type_hints[arg]
|
907
|
-
|
908
|
-
|
918
|
+
|
919
|
+
# NOTE: Check Result argument was passed to this caller function.
|
920
|
+
#
|
921
|
+
# if is_dataclass(t) and t.__name__ == "Result" and arg not in args:
|
922
|
+
# args[arg] = result
|
909
923
|
|
910
924
|
if issubclass(t, BaseModel) and arg in args:
|
911
925
|
args[arg] = t.model_validate(obj=args[arg])
|
@@ -1141,13 +1155,59 @@ class ForEachStage(BaseStage):
|
|
1141
1155
|
)
|
1142
1156
|
concurrent: int = Field(
|
1143
1157
|
default=1,
|
1144
|
-
|
1158
|
+
ge=1,
|
1159
|
+
lt=10,
|
1145
1160
|
description=(
|
1146
1161
|
"A concurrent value allow to run each item at the same time. It "
|
1147
1162
|
"will be sequential mode if this value equal 1."
|
1148
1163
|
),
|
1149
1164
|
)
|
1150
1165
|
|
1166
|
+
def execute_item(
|
1167
|
+
self,
|
1168
|
+
item: Union[str, int],
|
1169
|
+
params: DictData,
|
1170
|
+
context: DictData,
|
1171
|
+
result: Result,
|
1172
|
+
) -> tuple[Status, DictData]:
|
1173
|
+
"""Execute foreach item from list of item.
|
1174
|
+
|
1175
|
+
:param item: (str | int) An item that want to execution.
|
1176
|
+
:param params: (DictData) A parameter that want to pass to stage
|
1177
|
+
execution.
|
1178
|
+
:param context: (DictData)
|
1179
|
+
:param result: (Result)
|
1180
|
+
|
1181
|
+
:rtype: tuple[Status, DictData]
|
1182
|
+
"""
|
1183
|
+
result.trace.debug(f"[STAGE]: Execute foreach item: {item!r}")
|
1184
|
+
params["item"] = item
|
1185
|
+
to: DictData = {"item": item, "stages": {}}
|
1186
|
+
status: Status = SUCCESS
|
1187
|
+
for stage in self.stages:
|
1188
|
+
|
1189
|
+
if self.extras:
|
1190
|
+
stage.extras = self.extras
|
1191
|
+
|
1192
|
+
try:
|
1193
|
+
stage.set_outputs(
|
1194
|
+
stage.handler_execute(
|
1195
|
+
params=params,
|
1196
|
+
run_id=result.run_id,
|
1197
|
+
parent_run_id=result.parent_run_id,
|
1198
|
+
).context,
|
1199
|
+
to=to,
|
1200
|
+
)
|
1201
|
+
except StageException as e: # pragma: no cov
|
1202
|
+
status = FAILED
|
1203
|
+
result.trace.error(
|
1204
|
+
f"[STAGE]: Catch:\n\t{e.__class__.__name__}:" f"\n\t{e}"
|
1205
|
+
)
|
1206
|
+
to.update({"errors": e.to_dict()})
|
1207
|
+
|
1208
|
+
context["foreach"][item] = to
|
1209
|
+
return status, context
|
1210
|
+
|
1151
1211
|
def execute(
|
1152
1212
|
self,
|
1153
1213
|
params: DictData,
|
@@ -1181,41 +1241,26 @@ class ForEachStage(BaseStage):
|
|
1181
1241
|
)
|
1182
1242
|
|
1183
1243
|
result.trace.info(f"[STAGE]: Foreach-Execute: {foreach!r}.")
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
params=params,
|
1201
|
-
run_id=result.run_id,
|
1202
|
-
parent_run_id=result.parent_run_id,
|
1203
|
-
).context,
|
1204
|
-
to=context,
|
1205
|
-
)
|
1206
|
-
except StageException as e: # pragma: no cov
|
1207
|
-
status = FAILED
|
1208
|
-
result.trace.error(
|
1209
|
-
f"[STAGE]: Catch:\n\t{e.__class__.__name__}:" f"\n\t{e}"
|
1210
|
-
)
|
1211
|
-
context.update({"errors": e.to_dict()})
|
1212
|
-
|
1213
|
-
rs["foreach"][item] = context
|
1244
|
+
context: DictData = {"items": foreach, "foreach": {}}
|
1245
|
+
statuses: list[Union[SUCCESS, FAILED]] = []
|
1246
|
+
with ThreadPoolExecutor(max_workers=self.concurrent) as executor:
|
1247
|
+
futures: list[Future] = [
|
1248
|
+
executor.submit(
|
1249
|
+
self.execute_item,
|
1250
|
+
item=item,
|
1251
|
+
params=params.copy(),
|
1252
|
+
context=context,
|
1253
|
+
result=result,
|
1254
|
+
)
|
1255
|
+
for item in foreach
|
1256
|
+
]
|
1257
|
+
for future in as_completed(futures):
|
1258
|
+
status, context = future.result()
|
1259
|
+
statuses.append(status)
|
1214
1260
|
|
1215
|
-
return result.catch(status=
|
1261
|
+
return result.catch(status=max(statuses), context=context)
|
1216
1262
|
|
1217
1263
|
|
1218
|
-
# TODO: Not implement this stages yet
|
1219
1264
|
class UntilStage(BaseStage): # pragma: no cov
|
1220
1265
|
"""Until execution stage.
|
1221
1266
|
|
@@ -1242,27 +1287,144 @@ class UntilStage(BaseStage): # pragma: no cov
|
|
1242
1287
|
"correct."
|
1243
1288
|
),
|
1244
1289
|
)
|
1245
|
-
|
1246
|
-
default=
|
1247
|
-
|
1290
|
+
max_until_loop: int = Field(
|
1291
|
+
default=10,
|
1292
|
+
ge=1,
|
1293
|
+
lt=100,
|
1294
|
+
description="The maximum value of loop for this until stage.",
|
1248
1295
|
)
|
1249
1296
|
|
1297
|
+
def execute_item(
|
1298
|
+
self,
|
1299
|
+
item: T,
|
1300
|
+
loop: int,
|
1301
|
+
params: DictData,
|
1302
|
+
context: DictData,
|
1303
|
+
result: Result,
|
1304
|
+
) -> tuple[Status, DictData, T]:
|
1305
|
+
"""Execute until item set item by some stage or by default loop
|
1306
|
+
variable.
|
1307
|
+
|
1308
|
+
:param item: (T) An item that want to execution.
|
1309
|
+
:param loop: (int) A number of loop.
|
1310
|
+
:param params: (DictData) A parameter that want to pass to stage
|
1311
|
+
execution.
|
1312
|
+
:param context: (DictData)
|
1313
|
+
:param result: (Result)
|
1314
|
+
|
1315
|
+
:rtype: tuple[Status, DictData, T]
|
1316
|
+
"""
|
1317
|
+
result.trace.debug(f"[STAGE]: Execute until item: {item!r}")
|
1318
|
+
params["item"] = item
|
1319
|
+
to: DictData = {"item": item, "stages": {}}
|
1320
|
+
status: Status = SUCCESS
|
1321
|
+
next_item: T = None
|
1322
|
+
for stage in self.stages:
|
1323
|
+
|
1324
|
+
if self.extras:
|
1325
|
+
stage.extras = self.extras
|
1326
|
+
|
1327
|
+
try:
|
1328
|
+
stage.set_outputs(
|
1329
|
+
stage.handler_execute(
|
1330
|
+
params=params,
|
1331
|
+
run_id=result.run_id,
|
1332
|
+
parent_run_id=result.parent_run_id,
|
1333
|
+
).context,
|
1334
|
+
to=to,
|
1335
|
+
)
|
1336
|
+
if "item" in (
|
1337
|
+
outputs := stage.get_outputs(to).get("outputs", {})
|
1338
|
+
):
|
1339
|
+
next_item = outputs["item"]
|
1340
|
+
except StageException as e:
|
1341
|
+
status = FAILED
|
1342
|
+
result.trace.error(
|
1343
|
+
f"[STAGE]: Catch:\n\t{e.__class__.__name__}:" f"\n\t{e}"
|
1344
|
+
)
|
1345
|
+
to.update({"errors": e.to_dict()})
|
1346
|
+
|
1347
|
+
context["until"][loop] = to
|
1348
|
+
return status, context, next_item
|
1349
|
+
|
1250
1350
|
def execute(
|
1251
1351
|
self,
|
1252
1352
|
params: DictData,
|
1253
1353
|
*,
|
1254
1354
|
result: Result | None = None,
|
1255
1355
|
event: Event | None = None,
|
1256
|
-
) -> Result:
|
1356
|
+
) -> Result:
|
1357
|
+
"""Execute the stages that pass item from until condition field and
|
1358
|
+
setter step.
|
1359
|
+
|
1360
|
+
:param params: A parameter that want to pass before run any statement.
|
1361
|
+
:param result: (Result) A result object for keeping context and status
|
1362
|
+
data.
|
1363
|
+
:param event: (Event) An event manager that use to track parent execute
|
1364
|
+
was not force stopped.
|
1365
|
+
|
1366
|
+
:rtype: Result
|
1367
|
+
"""
|
1368
|
+
if result is None: # pragma: no cov
|
1369
|
+
result: Result = Result(
|
1370
|
+
run_id=gen_id(self.name + (self.id or ""), unique=True)
|
1371
|
+
)
|
1372
|
+
|
1373
|
+
item: Union[str, int, bool] = param2template(
|
1374
|
+
self.item, params, extras=self.extras
|
1375
|
+
)
|
1376
|
+
result.trace.info(f"[STAGE]: Until-Execution: {self.until}")
|
1377
|
+
track: bool = True
|
1378
|
+
exceed_loop: bool = False
|
1379
|
+
loop: int = 1
|
1380
|
+
context: DictData = {"until": {}}
|
1381
|
+
statuses: list[Union[SUCCESS, FAILED]] = []
|
1382
|
+
while track and not (exceed_loop := loop >= self.max_until_loop):
|
1383
|
+
status, context, item = self.execute_item(
|
1384
|
+
item=item,
|
1385
|
+
loop=loop,
|
1386
|
+
params=params.copy(),
|
1387
|
+
context=context,
|
1388
|
+
result=result,
|
1389
|
+
)
|
1390
|
+
|
1391
|
+
loop += 1
|
1392
|
+
if item is None:
|
1393
|
+
result.trace.warning(
|
1394
|
+
"... Does not have set item stage. It will use loop by "
|
1395
|
+
"default."
|
1396
|
+
)
|
1397
|
+
item = loop
|
1398
|
+
|
1399
|
+
next_track: bool = eval(
|
1400
|
+
param2template(
|
1401
|
+
self.until, params | {"item": item}, extras=self.extras
|
1402
|
+
),
|
1403
|
+
globals() | params | {"item": item},
|
1404
|
+
{},
|
1405
|
+
)
|
1406
|
+
if not isinstance(next_track, bool):
|
1407
|
+
raise TypeError(
|
1408
|
+
"Return type of until condition does not be boolean, it"
|
1409
|
+
f"return: {next_track!r}"
|
1410
|
+
)
|
1411
|
+
track = not next_track
|
1412
|
+
statuses.append(status)
|
1413
|
+
|
1414
|
+
if exceed_loop:
|
1415
|
+
raise StageException(
|
1416
|
+
f"The until loop was exceed {self.max_until_loop} loops"
|
1417
|
+
)
|
1418
|
+
return result.catch(status=max(statuses), context=context)
|
1257
1419
|
|
1258
1420
|
|
1259
|
-
# TODO: Not implement this stages yet
|
1260
1421
|
class Match(BaseModel):
|
1422
|
+
"""Match model for the Case Stage."""
|
1423
|
+
|
1261
1424
|
case: Union[str, int]
|
1262
1425
|
stage: Stage
|
1263
1426
|
|
1264
1427
|
|
1265
|
-
# TODO: Not implement this stages yet
|
1266
1428
|
class CaseStage(BaseStage): # pragma: no cov
|
1267
1429
|
"""Case execution stage.
|
1268
1430
|
|
@@ -1298,7 +1460,9 @@ class CaseStage(BaseStage): # pragma: no cov
|
|
1298
1460
|
"""
|
1299
1461
|
|
1300
1462
|
case: str = Field(description="A case condition for routing.")
|
1301
|
-
match: list[Match]
|
1463
|
+
match: list[Match] = Field(
|
1464
|
+
description="A list of Match model that should not be an empty list.",
|
1465
|
+
)
|
1302
1466
|
|
1303
1467
|
def execute(
|
1304
1468
|
self,
|
@@ -1321,10 +1485,14 @@ class CaseStage(BaseStage): # pragma: no cov
|
|
1321
1485
|
result: Result = Result(
|
1322
1486
|
run_id=gen_id(self.name + (self.id or ""), unique=True)
|
1323
1487
|
)
|
1324
|
-
|
1488
|
+
|
1325
1489
|
_case = param2template(self.case, params, extras=self.extras)
|
1490
|
+
|
1491
|
+
result.trace.info(f"[STAGE]: Case-Execute: {_case!r}.")
|
1326
1492
|
_else = None
|
1493
|
+
stage: Optional[Stage] = None
|
1327
1494
|
context = {}
|
1495
|
+
status = SUCCESS
|
1328
1496
|
for match in self.match:
|
1329
1497
|
if (c := match.case) != "_":
|
1330
1498
|
_condition = param2template(c, params, extras=self.extras)
|
@@ -1332,27 +1500,35 @@ class CaseStage(BaseStage): # pragma: no cov
|
|
1332
1500
|
_else = match
|
1333
1501
|
continue
|
1334
1502
|
|
1335
|
-
if
|
1503
|
+
if stage is None and _case == _condition:
|
1336
1504
|
stage: Stage = match.stage
|
1337
|
-
if self.extras:
|
1338
|
-
stage.extras = self.extras
|
1339
|
-
|
1340
|
-
try:
|
1341
|
-
stage.set_outputs(
|
1342
|
-
stage.handler_execute(
|
1343
|
-
params=params,
|
1344
|
-
run_id=result.run_id,
|
1345
|
-
parent_run_id=result.parent_run_id,
|
1346
|
-
).context,
|
1347
|
-
to=context,
|
1348
|
-
)
|
1349
|
-
except StageException as e: # pragma: no cov
|
1350
|
-
status = FAILED
|
1351
|
-
result.trace.error(
|
1352
|
-
f"[STAGE]: Catch:\n\t{e.__class__.__name__}:" f"\n\t{e}"
|
1353
|
-
)
|
1354
|
-
context.update({"errors": e.to_dict()})
|
1355
1505
|
|
1506
|
+
if stage is None:
|
1507
|
+
if _else is None:
|
1508
|
+
raise StageException(
|
1509
|
+
"This stage does not set else for support not match "
|
1510
|
+
"any case."
|
1511
|
+
)
|
1512
|
+
|
1513
|
+
stage: Stage = _else.stage
|
1514
|
+
|
1515
|
+
if self.extras:
|
1516
|
+
stage.extras = self.extras
|
1517
|
+
|
1518
|
+
try:
|
1519
|
+
context.update(
|
1520
|
+
stage.handler_execute(
|
1521
|
+
params=params,
|
1522
|
+
run_id=result.run_id,
|
1523
|
+
parent_run_id=result.parent_run_id,
|
1524
|
+
).context
|
1525
|
+
)
|
1526
|
+
except StageException as e: # pragma: no cov
|
1527
|
+
status = FAILED
|
1528
|
+
result.trace.error(
|
1529
|
+
f"[STAGE]: Catch:\n\t{e.__class__.__name__}:" f"\n\t{e}"
|
1530
|
+
)
|
1531
|
+
context.update({"errors": e.to_dict()})
|
1356
1532
|
return result.catch(status=status, context=context)
|
1357
1533
|
|
1358
1534
|
|
@@ -1403,7 +1579,8 @@ class HookStage(BaseStage): # pragma: no cov
|
|
1403
1579
|
*,
|
1404
1580
|
result: Result | None = None,
|
1405
1581
|
event: Event | None = None,
|
1406
|
-
) -> Result:
|
1582
|
+
) -> Result:
|
1583
|
+
raise NotImplementedError("Hook Stage does not implement yet.")
|
1407
1584
|
|
1408
1585
|
|
1409
1586
|
# TODO: Not implement this stages yet
|
@@ -1436,15 +1613,20 @@ class DockerStage(BaseStage): # pragma: no cov
|
|
1436
1613
|
*,
|
1437
1614
|
result: Result | None = None,
|
1438
1615
|
event: Event | None = None,
|
1439
|
-
) -> Result:
|
1616
|
+
) -> Result:
|
1617
|
+
raise NotImplementedError("Docker Stage does not implement yet.")
|
1440
1618
|
|
1441
1619
|
|
1442
1620
|
# TODO: Not implement this stages yet
|
1443
1621
|
class VirtualPyStage(PyStage): # pragma: no cov
|
1444
1622
|
"""Python Virtual Environment stage execution."""
|
1445
1623
|
|
1446
|
-
|
1447
|
-
|
1624
|
+
deps: list[str] = Field(
|
1625
|
+
description=(
|
1626
|
+
"list of Python dependency that want to install before execution "
|
1627
|
+
"stage."
|
1628
|
+
),
|
1629
|
+
)
|
1448
1630
|
|
1449
1631
|
def create_py_file(self, py: str, run_id: str | None): ...
|
1450
1632
|
|
@@ -1455,7 +1637,25 @@ class VirtualPyStage(PyStage): # pragma: no cov
|
|
1455
1637
|
result: Result | None = None,
|
1456
1638
|
event: Event | None = None,
|
1457
1639
|
) -> Result:
|
1458
|
-
|
1640
|
+
"""Execute the Python statement via Python virtual environment.
|
1641
|
+
|
1642
|
+
:param params: A parameter that want to pass before run any statement.
|
1643
|
+
:param result: (Result) A result object for keeping context and status
|
1644
|
+
data.
|
1645
|
+
:param event: (Event) An event manager that use to track parent execute
|
1646
|
+
was not force stopped.
|
1647
|
+
|
1648
|
+
:rtype: Result
|
1649
|
+
"""
|
1650
|
+
if result is None: # pragma: no cov
|
1651
|
+
result: Result = Result(
|
1652
|
+
run_id=gen_id(self.name + (self.id or ""), unique=True)
|
1653
|
+
)
|
1654
|
+
|
1655
|
+
result.trace.info(f"[STAGE]: Py-Virtual-Execute: {self.name}")
|
1656
|
+
raise NotImplementedError(
|
1657
|
+
"Python Virtual Stage does not implement yet."
|
1658
|
+
)
|
1459
1659
|
|
1460
1660
|
|
1461
1661
|
# NOTE:
|
@@ -1465,11 +1665,16 @@ class VirtualPyStage(PyStage): # pragma: no cov
|
|
1465
1665
|
#
|
1466
1666
|
Stage = Annotated[
|
1467
1667
|
Union[
|
1668
|
+
DockerStage,
|
1468
1669
|
BashStage,
|
1469
1670
|
CallStage,
|
1671
|
+
HookStage,
|
1470
1672
|
TriggerStage,
|
1471
1673
|
ForEachStage,
|
1674
|
+
UntilStage,
|
1472
1675
|
ParallelStage,
|
1676
|
+
CaseStage,
|
1677
|
+
VirtualPyStage,
|
1473
1678
|
PyStage,
|
1474
1679
|
RaiseStage,
|
1475
1680
|
EmptyStage,
|
ddeutil/workflow/utils.py
CHANGED
@@ -15,7 +15,7 @@ from inspect import isfunction
|
|
15
15
|
from itertools import chain, islice, product
|
16
16
|
from pathlib import Path
|
17
17
|
from random import randrange
|
18
|
-
from typing import Any, TypeVar
|
18
|
+
from typing import Any, Final, TypeVar
|
19
19
|
from zoneinfo import ZoneInfo
|
20
20
|
|
21
21
|
from ddeutil.core import hash_str
|
@@ -23,7 +23,7 @@ from ddeutil.core import hash_str
|
|
23
23
|
from .__types import DictData, Matrix
|
24
24
|
|
25
25
|
T = TypeVar("T")
|
26
|
-
UTC = ZoneInfo("UTC")
|
26
|
+
UTC: Final[ZoneInfo] = ZoneInfo("UTC")
|
27
27
|
|
28
28
|
|
29
29
|
def get_dt_now(
|
@@ -142,7 +142,7 @@ def gen_id(
|
|
142
142
|
if not isinstance(value, str):
|
143
143
|
value: str = str(value)
|
144
144
|
|
145
|
-
if config.
|
145
|
+
if config.generate_id_simple_mode:
|
146
146
|
return (
|
147
147
|
f"{datetime.now(tz=config.tz):%Y%m%d%H%M%S%f}T" if unique else ""
|
148
148
|
) + hash_str(f"{(value if sensitive else value.lower())}", n=10)
|