ddeutil-workflow 0.0.31__py3-none-any.whl → 0.0.33__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 +4 -6
- ddeutil/workflow/api/route.py +8 -7
- ddeutil/workflow/audit.py +261 -0
- ddeutil/workflow/conf.py +122 -265
- ddeutil/workflow/job.py +61 -52
- ddeutil/workflow/params.py +5 -2
- ddeutil/workflow/result.py +89 -37
- ddeutil/workflow/scheduler.py +118 -45
- ddeutil/workflow/stage.py +75 -56
- ddeutil/workflow/templates.py +13 -4
- ddeutil/workflow/workflow.py +63 -64
- {ddeutil_workflow-0.0.31.dist-info → ddeutil_workflow-0.0.33.dist-info}/METADATA +46 -27
- ddeutil_workflow-0.0.33.dist-info/RECORD +26 -0
- ddeutil_workflow-0.0.31.dist-info/RECORD +0 -25
- {ddeutil_workflow-0.0.31.dist-info → ddeutil_workflow-0.0.33.dist-info}/LICENSE +0 -0
- {ddeutil_workflow-0.0.31.dist-info → ddeutil_workflow-0.0.33.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.31.dist-info → ddeutil_workflow-0.0.33.dist-info}/top_level.txt +0 -0
ddeutil/workflow/scheduler.py
CHANGED
@@ -51,7 +51,8 @@ except ImportError: # pragma: no cov
|
|
51
51
|
|
52
52
|
from .__cron import CronRunner
|
53
53
|
from .__types import DictData, TupleStr
|
54
|
-
from .
|
54
|
+
from .audit import Audit, get_audit
|
55
|
+
from .conf import Loader, config, get_logger
|
55
56
|
from .cron import On
|
56
57
|
from .exceptions import ScheduleException, WorkflowException
|
57
58
|
from .result import Result
|
@@ -275,37 +276,6 @@ class Schedule(BaseModel):
|
|
275
276
|
|
276
277
|
return cls.model_validate(obj=loader_data)
|
277
278
|
|
278
|
-
@classmethod
|
279
|
-
def extract_tasks(
|
280
|
-
cls,
|
281
|
-
schedules: list[str],
|
282
|
-
start_date: datetime,
|
283
|
-
queue: dict[str, ReleaseQueue],
|
284
|
-
externals: DictData | None = None,
|
285
|
-
) -> list[WorkflowTask]:
|
286
|
-
"""Return the list of WorkflowTask object from all schedule object that
|
287
|
-
include in an input schedules argument.
|
288
|
-
|
289
|
-
:param schedules: A list of schedule name that will use `from_loader`
|
290
|
-
method.
|
291
|
-
:param start_date: A start date that get from the workflow schedule.
|
292
|
-
:param queue: A mapping of name and list of datetime for queue.
|
293
|
-
:param externals: An external parameters that pass to the Loader object.
|
294
|
-
|
295
|
-
:rtype: list[WorkflowTask]
|
296
|
-
"""
|
297
|
-
tasks: list[WorkflowTask] = []
|
298
|
-
for name in schedules:
|
299
|
-
schedule: Schedule = Schedule.from_loader(name, externals=externals)
|
300
|
-
tasks.extend(
|
301
|
-
schedule.tasks(
|
302
|
-
start_date,
|
303
|
-
queue=queue,
|
304
|
-
externals=externals,
|
305
|
-
),
|
306
|
-
)
|
307
|
-
return tasks
|
308
|
-
|
309
279
|
def tasks(
|
310
280
|
self,
|
311
281
|
start_date: datetime,
|
@@ -339,6 +309,99 @@ class Schedule(BaseModel):
|
|
339
309
|
|
340
310
|
return workflow_tasks
|
341
311
|
|
312
|
+
def pending(
|
313
|
+
self,
|
314
|
+
*,
|
315
|
+
stop: datetime | None = None,
|
316
|
+
externals: DictData | None = None,
|
317
|
+
log: type[Audit] | None = None,
|
318
|
+
) -> None: # pragma: no cov
|
319
|
+
"""Pending this schedule tasks with the schedule package.
|
320
|
+
|
321
|
+
:param stop: A datetime value that use to stop running schedule.
|
322
|
+
:param externals: An external parameters that pass to Loader.
|
323
|
+
:param log: A log class that use on the workflow task release for
|
324
|
+
writing its release log context.
|
325
|
+
"""
|
326
|
+
try:
|
327
|
+
from schedule import Scheduler
|
328
|
+
except ImportError:
|
329
|
+
raise ImportError(
|
330
|
+
"Should install schedule package before use this method."
|
331
|
+
) from None
|
332
|
+
|
333
|
+
# NOTE: Get default logging.
|
334
|
+
log: type[Audit] = log or get_audit()
|
335
|
+
scheduler: Scheduler = Scheduler()
|
336
|
+
|
337
|
+
# NOTE: Create the start and stop datetime.
|
338
|
+
start_date: datetime = datetime.now(tz=config.tz)
|
339
|
+
stop_date: datetime = stop or (start_date + config.stop_boundary_delta)
|
340
|
+
|
341
|
+
# IMPORTANT: Create main mapping of queue and thread object.
|
342
|
+
queue: dict[str, ReleaseQueue] = {}
|
343
|
+
threads: ReleaseThreads = {}
|
344
|
+
|
345
|
+
start_date_waiting: datetime = start_date.replace(
|
346
|
+
second=0, microsecond=0
|
347
|
+
) + timedelta(minutes=1)
|
348
|
+
|
349
|
+
# NOTE: This schedule job will start every minute at :02 seconds.
|
350
|
+
(
|
351
|
+
scheduler.every(1)
|
352
|
+
.minutes.at(":02")
|
353
|
+
.do(
|
354
|
+
schedule_task,
|
355
|
+
tasks=self.tasks(
|
356
|
+
start_date_waiting, queue=queue, externals=externals
|
357
|
+
),
|
358
|
+
stop=stop_date,
|
359
|
+
queue=queue,
|
360
|
+
threads=threads,
|
361
|
+
log=log,
|
362
|
+
)
|
363
|
+
.tag("control")
|
364
|
+
)
|
365
|
+
|
366
|
+
# NOTE: Checking zombie task with schedule job will start every 5 minute at
|
367
|
+
# :10 seconds.
|
368
|
+
(
|
369
|
+
scheduler.every(5)
|
370
|
+
.minutes.at(":10")
|
371
|
+
.do(
|
372
|
+
monitor,
|
373
|
+
threads=threads,
|
374
|
+
)
|
375
|
+
.tag("monitor")
|
376
|
+
)
|
377
|
+
|
378
|
+
# NOTE: Start running schedule
|
379
|
+
logger.info(
|
380
|
+
f"[SCHEDULE]: Schedule with stopper: {stop_date:%Y-%m-%d %H:%M:%S}"
|
381
|
+
)
|
382
|
+
|
383
|
+
while True:
|
384
|
+
scheduler.run_pending()
|
385
|
+
time.sleep(1)
|
386
|
+
|
387
|
+
# NOTE: Break the scheduler when the control job does not exist.
|
388
|
+
if not scheduler.get_jobs("control"):
|
389
|
+
scheduler.clear("monitor")
|
390
|
+
|
391
|
+
while len(threads) > 0:
|
392
|
+
logger.warning(
|
393
|
+
"[SCHEDULE]: Waiting schedule release thread that still "
|
394
|
+
"running in background."
|
395
|
+
)
|
396
|
+
delay(10)
|
397
|
+
monitor(threads)
|
398
|
+
|
399
|
+
break
|
400
|
+
|
401
|
+
logger.warning(
|
402
|
+
f"[SCHEDULE]: Queue: {[list(queue[wf].queue) for wf in queue]}"
|
403
|
+
)
|
404
|
+
|
342
405
|
|
343
406
|
ResultOrCancelJob = Union[type[CancelJob], Result]
|
344
407
|
ReturnCancelJob = Callable[P, ResultOrCancelJob]
|
@@ -388,13 +451,13 @@ def schedule_task(
|
|
388
451
|
stop: datetime,
|
389
452
|
queue: dict[str, ReleaseQueue],
|
390
453
|
threads: ReleaseThreads,
|
391
|
-
log: type[
|
454
|
+
log: type[Audit],
|
392
455
|
) -> type[CancelJob] | None:
|
393
456
|
"""Schedule task function that generate thread of workflow task release
|
394
457
|
method in background. This function do the same logic as the workflow poke
|
395
458
|
method, but it runs with map of schedules and the on values.
|
396
459
|
|
397
|
-
This schedule task start runs every minute at ':02' second and it does
|
460
|
+
This schedule task start runs every minute at ':02' second, and it does
|
398
461
|
not allow you to run with offset time.
|
399
462
|
|
400
463
|
:param tasks: A list of WorkflowTask object.
|
@@ -414,15 +477,16 @@ def schedule_task(
|
|
414
477
|
# function. It will deplicate running with different schedule value
|
415
478
|
# because I use current time in this condition.
|
416
479
|
#
|
417
|
-
# For example, if a
|
418
|
-
#
|
419
|
-
# This condition
|
480
|
+
# For example, if a queue has a time release be '00:02:00' that should
|
481
|
+
# to run and its schedule has '*/2 * * * *' and '*/35 * * * *'.
|
482
|
+
# This condition make this function create 2 threading tasks.
|
420
483
|
#
|
421
|
-
#
|
422
|
-
#
|
484
|
+
# '00:02:00' --> '*/2 * * * *' --> run
|
485
|
+
# --> '*/35 * * * *' --> skip
|
423
486
|
#
|
424
487
|
for task in tasks:
|
425
488
|
|
489
|
+
# NOTE: Get the ReleaseQueue with an alias of the WorkflowTask.
|
426
490
|
q: ReleaseQueue = queue[task.alias]
|
427
491
|
|
428
492
|
# NOTE: Start adding queue and move the runner date in the WorkflowTask.
|
@@ -510,7 +574,7 @@ def schedule_control(
|
|
510
574
|
stop: datetime | None = None,
|
511
575
|
externals: DictData | None = None,
|
512
576
|
*,
|
513
|
-
log: type[
|
577
|
+
log: type[Audit] | None = None,
|
514
578
|
) -> list[str]: # pragma: no cov
|
515
579
|
"""Scheduler control function that run the chuck of schedules every minute
|
516
580
|
and this function release monitoring thread for tracking undead thread in
|
@@ -533,7 +597,7 @@ def schedule_control(
|
|
533
597
|
) from None
|
534
598
|
|
535
599
|
# NOTE: Get default logging.
|
536
|
-
log: type[
|
600
|
+
log: type[Audit] = log or get_audit()
|
537
601
|
scheduler: Scheduler = Scheduler()
|
538
602
|
|
539
603
|
# NOTE: Create the start and stop datetime.
|
@@ -548,15 +612,24 @@ def schedule_control(
|
|
548
612
|
second=0, microsecond=0
|
549
613
|
) + timedelta(minutes=1)
|
550
614
|
|
615
|
+
tasks: list[WorkflowTask] = []
|
616
|
+
for name in schedules:
|
617
|
+
schedule: Schedule = Schedule.from_loader(name, externals=externals)
|
618
|
+
tasks.extend(
|
619
|
+
schedule.tasks(
|
620
|
+
start_date_waiting,
|
621
|
+
queue=queue,
|
622
|
+
externals=externals,
|
623
|
+
),
|
624
|
+
)
|
625
|
+
|
551
626
|
# NOTE: This schedule job will start every minute at :02 seconds.
|
552
627
|
(
|
553
628
|
scheduler.every(1)
|
554
629
|
.minutes.at(":02")
|
555
630
|
.do(
|
556
631
|
schedule_task,
|
557
|
-
tasks=
|
558
|
-
schedules, start_date_waiting, queue, externals=externals
|
559
|
-
),
|
632
|
+
tasks=tasks,
|
560
633
|
stop=stop_date,
|
561
634
|
queue=queue,
|
562
635
|
threads=threads,
|
@@ -596,7 +669,7 @@ def schedule_control(
|
|
596
669
|
"[SCHEDULE]: Waiting schedule release thread that still "
|
597
670
|
"running in background."
|
598
671
|
)
|
599
|
-
delay(
|
672
|
+
delay(10)
|
600
673
|
monitor(threads)
|
601
674
|
|
602
675
|
break
|
ddeutil/workflow/stage.py
CHANGED
@@ -45,7 +45,7 @@ from .__types import DictData, DictStr, TupleStr
|
|
45
45
|
from .conf import config, get_logger
|
46
46
|
from .exceptions import StageException
|
47
47
|
from .hook import TagFunc, extract_hook
|
48
|
-
from .result import Result
|
48
|
+
from .result import Result, Status
|
49
49
|
from .templates import not_in_template, param2template
|
50
50
|
from .utils import (
|
51
51
|
cut_id,
|
@@ -121,19 +121,26 @@ class BaseStage(BaseModel, ABC):
|
|
121
121
|
return self
|
122
122
|
|
123
123
|
@abstractmethod
|
124
|
-
def execute(
|
124
|
+
def execute(
|
125
|
+
self, params: DictData, *, result: Result | None = None
|
126
|
+
) -> Result:
|
125
127
|
"""Execute abstraction method that action something by sub-model class.
|
126
128
|
This is important method that make this class is able to be the stage.
|
127
129
|
|
128
130
|
:param params: A parameter data that want to use in this execution.
|
129
|
-
:param
|
131
|
+
:param result: (Result) A result object for keeping context and status
|
132
|
+
data.
|
130
133
|
|
131
134
|
:rtype: Result
|
132
135
|
"""
|
133
136
|
raise NotImplementedError("Stage should implement ``execute`` method.")
|
134
137
|
|
135
138
|
def handler_execute(
|
136
|
-
self,
|
139
|
+
self,
|
140
|
+
params: DictData,
|
141
|
+
*,
|
142
|
+
run_id: str | None = None,
|
143
|
+
result: Result | None = None,
|
137
144
|
) -> Result:
|
138
145
|
"""Handler result from the stage execution.
|
139
146
|
|
@@ -158,23 +165,25 @@ class BaseStage(BaseModel, ABC):
|
|
158
165
|
from current stage ID before release the final result.
|
159
166
|
|
160
167
|
:param params: A parameter data that want to use in this execution.
|
161
|
-
:param run_id: A running stage ID for this execution.
|
168
|
+
:param run_id: (str) A running stage ID for this execution.
|
169
|
+
:param result: (Result) A result object for keeping context and status
|
170
|
+
data.
|
162
171
|
|
163
172
|
:rtype: Result
|
164
173
|
"""
|
165
|
-
if
|
166
|
-
|
174
|
+
if result is None: # pragma: no cov
|
175
|
+
result: Result = Result(
|
176
|
+
run_id=(
|
177
|
+
run_id or gen_id(self.name + (self.id or ""), unique=True)
|
178
|
+
),
|
179
|
+
)
|
167
180
|
|
168
|
-
rs_raise: Result = Result(status=1, run_id=run_id)
|
169
181
|
try:
|
170
182
|
# NOTE: Start calling origin function with a passing args.
|
171
|
-
return self.execute(params,
|
183
|
+
return self.execute(params, result=result)
|
172
184
|
except Exception as err:
|
173
185
|
# NOTE: Start catching error from the stage execution.
|
174
|
-
|
175
|
-
f"({cut_id(run_id)}) [STAGE]: {err.__class__.__name__}: "
|
176
|
-
f"{err}"
|
177
|
-
)
|
186
|
+
result.trace.error(f"[STAGE]: {err.__class__.__name__}: {err}")
|
178
187
|
if config.stage_raise_error:
|
179
188
|
# NOTE: If error that raise from stage execution course by
|
180
189
|
# itself, it will return that error with previous
|
@@ -190,8 +199,8 @@ class BaseStage(BaseModel, ABC):
|
|
190
199
|
|
191
200
|
# NOTE: Catching exception error object to result with
|
192
201
|
# error_message and error keys.
|
193
|
-
return
|
194
|
-
status=
|
202
|
+
return result.catch(
|
203
|
+
status=Status.FAILED,
|
195
204
|
context={
|
196
205
|
"error": err,
|
197
206
|
"error_message": f"{err.__class__.__name__}: {err}",
|
@@ -295,7 +304,9 @@ class EmptyStage(BaseStage):
|
|
295
304
|
ge=0,
|
296
305
|
)
|
297
306
|
|
298
|
-
def execute(
|
307
|
+
def execute(
|
308
|
+
self, params: DictData, *, result: Result | None = None
|
309
|
+
) -> Result:
|
299
310
|
"""Execution method for the Empty stage that do only logging out to
|
300
311
|
stdout. This method does not use the `handler_result` decorator because
|
301
312
|
it does not get any error from logging function.
|
@@ -305,22 +316,21 @@ class EmptyStage(BaseStage):
|
|
305
316
|
|
306
317
|
:param params: A context data that want to add output result. But this
|
307
318
|
stage does not pass any output.
|
308
|
-
:param
|
319
|
+
:param result: (Result) A result object for keeping context and status
|
320
|
+
data.
|
309
321
|
|
310
322
|
:rtype: Result
|
311
323
|
"""
|
312
|
-
|
313
|
-
f"
|
324
|
+
result.trace.info(
|
325
|
+
f"[STAGE]: Empty-Execute: {self.name!r}: "
|
314
326
|
f"( {param2template(self.echo, params=params) or '...'} )"
|
315
327
|
)
|
316
328
|
if self.sleep > 0:
|
317
329
|
if self.sleep > 30:
|
318
|
-
|
319
|
-
f"({cut_id(run_id)}) [STAGE]: ... sleep "
|
320
|
-
f"({self.sleep} seconds)"
|
321
|
-
)
|
330
|
+
result.trace.info(f"[STAGE]: ... sleep ({self.sleep} seconds)")
|
322
331
|
time.sleep(self.sleep)
|
323
|
-
|
332
|
+
|
333
|
+
return result.catch(status=Status.SUCCESS)
|
324
334
|
|
325
335
|
|
326
336
|
class BashStage(BaseStage):
|
@@ -334,7 +344,7 @@ class BashStage(BaseStage):
|
|
334
344
|
|
335
345
|
Data Validate:
|
336
346
|
>>> stage = {
|
337
|
-
... "name": "Shell stage execution",
|
347
|
+
... "name": "The Shell stage execution",
|
338
348
|
... "bash": 'echo "Hello $FOO"',
|
339
349
|
... "env": {
|
340
350
|
... "FOO": "BAR",
|
@@ -391,20 +401,25 @@ class BashStage(BaseStage):
|
|
391
401
|
# Note: Remove .sh file that use to run bash.
|
392
402
|
Path(f"./{f_name}").unlink()
|
393
403
|
|
394
|
-
def execute(
|
404
|
+
def execute(
|
405
|
+
self, params: DictData, *, result: Result | None = None
|
406
|
+
) -> Result:
|
395
407
|
"""Execute the Bash statement with the Python build-in ``subprocess``
|
396
408
|
package.
|
397
409
|
|
398
410
|
:param params: A parameter data that want to use in this execution.
|
399
|
-
:param
|
411
|
+
:param result: (Result) A result object for keeping context and status
|
412
|
+
data.
|
400
413
|
|
401
414
|
:rtype: Result
|
402
415
|
"""
|
403
416
|
bash: str = param2template(dedent(self.bash), params)
|
404
417
|
|
405
|
-
|
418
|
+
result.trace.info(f"[STAGE]: Shell-Execute: {self.name}")
|
406
419
|
with self.create_sh_file(
|
407
|
-
bash=bash,
|
420
|
+
bash=bash,
|
421
|
+
env=param2template(self.env, params),
|
422
|
+
run_id=result.run_id,
|
408
423
|
) as sh:
|
409
424
|
rs: CompletedProcess = subprocess.run(
|
410
425
|
sh, shell=False, capture_output=True, text=True
|
@@ -420,14 +435,13 @@ class BashStage(BaseStage):
|
|
420
435
|
f"Subprocess: {err}\nRunning Statement:\n---\n"
|
421
436
|
f"```bash\n{bash}\n```"
|
422
437
|
)
|
423
|
-
return
|
424
|
-
status=
|
438
|
+
return result.catch(
|
439
|
+
status=Status.SUCCESS,
|
425
440
|
context={
|
426
441
|
"return_code": rs.returncode,
|
427
442
|
"stdout": rs.stdout.rstrip("\n") or None,
|
428
443
|
"stderr": rs.stderr.rstrip("\n") or None,
|
429
444
|
},
|
430
|
-
run_id=run_id,
|
431
445
|
)
|
432
446
|
|
433
447
|
|
@@ -492,12 +506,15 @@ class PyStage(BaseStage):
|
|
492
506
|
to.update({k: gb[k] for k in to if k in gb})
|
493
507
|
return to
|
494
508
|
|
495
|
-
def execute(
|
509
|
+
def execute(
|
510
|
+
self, params: DictData, *, result: Result | None = None
|
511
|
+
) -> Result:
|
496
512
|
"""Execute the Python statement that pass all globals and input params
|
497
513
|
to globals argument on ``exec`` build-in function.
|
498
514
|
|
499
515
|
:param params: A parameter that want to pass before run any statement.
|
500
|
-
:param
|
516
|
+
:param result: (Result) A result object for keeping context and status
|
517
|
+
data.
|
501
518
|
|
502
519
|
:rtype: Result
|
503
520
|
"""
|
@@ -511,16 +528,14 @@ class PyStage(BaseStage):
|
|
511
528
|
lc: DictData = {}
|
512
529
|
|
513
530
|
# NOTE: Start exec the run statement.
|
514
|
-
|
531
|
+
result.trace.info(f"[STAGE]: Py-Execute: {self.name}")
|
515
532
|
|
516
533
|
# WARNING: The exec build-in function is very dangerous. So, it
|
517
534
|
# should use the re module to validate exec-string before running.
|
518
535
|
exec(run, _globals, lc)
|
519
536
|
|
520
|
-
return
|
521
|
-
status=
|
522
|
-
context={"locals": lc, "globals": _globals},
|
523
|
-
run_id=run_id,
|
537
|
+
return result.catch(
|
538
|
+
status=Status.SUCCESS, context={"locals": lc, "globals": _globals}
|
524
539
|
)
|
525
540
|
|
526
541
|
|
@@ -552,7 +567,9 @@ class HookStage(BaseStage):
|
|
552
567
|
alias="with",
|
553
568
|
)
|
554
569
|
|
555
|
-
def execute(
|
570
|
+
def execute(
|
571
|
+
self, params: DictData, *, result: Result | None = None
|
572
|
+
) -> Result:
|
556
573
|
"""Execute the Hook function that already in the hook registry.
|
557
574
|
|
558
575
|
:raise ValueError: When the necessary arguments of hook function do not
|
@@ -562,7 +579,8 @@ class HookStage(BaseStage):
|
|
562
579
|
|
563
580
|
:param params: A parameter that want to pass before run any statement.
|
564
581
|
:type params: DictData
|
565
|
-
:param
|
582
|
+
:param result: (Result) A result object for keeping context and status
|
583
|
+
data.
|
566
584
|
:type: str | None
|
567
585
|
|
568
586
|
:rtype: Result
|
@@ -571,7 +589,7 @@ class HookStage(BaseStage):
|
|
571
589
|
|
572
590
|
# VALIDATE: check input task caller parameters that exists before
|
573
591
|
# calling.
|
574
|
-
args: DictData = param2template(self.args, params)
|
592
|
+
args: DictData = {"result": result} | param2template(self.args, params)
|
575
593
|
ips = inspect.signature(t_func)
|
576
594
|
if any(
|
577
595
|
(k.removeprefix("_") not in args and k not in args)
|
@@ -587,10 +605,10 @@ class HookStage(BaseStage):
|
|
587
605
|
if k.removeprefix("_") in args:
|
588
606
|
args[k] = args.pop(k.removeprefix("_"))
|
589
607
|
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
)
|
608
|
+
if "result" not in ips.parameters:
|
609
|
+
args.pop("result")
|
610
|
+
|
611
|
+
result.trace.info(f"[STAGE]: Hook-Execute: {t_func.name}@{t_func.tag}")
|
594
612
|
rs: DictData = t_func(**param2template(args, params))
|
595
613
|
|
596
614
|
# VALIDATE:
|
@@ -600,7 +618,7 @@ class HookStage(BaseStage):
|
|
600
618
|
f"Return type: '{t_func.name}@{t_func.tag}' does not serialize "
|
601
619
|
f"to result model, you change return type to `dict`."
|
602
620
|
)
|
603
|
-
return
|
621
|
+
return result.catch(status=Status.SUCCESS, context=rs)
|
604
622
|
|
605
623
|
|
606
624
|
class TriggerStage(BaseStage):
|
@@ -626,12 +644,15 @@ class TriggerStage(BaseStage):
|
|
626
644
|
description="A parameter that want to pass to workflow execution.",
|
627
645
|
)
|
628
646
|
|
629
|
-
def execute(
|
647
|
+
def execute(
|
648
|
+
self, params: DictData, *, result: Result | None = None
|
649
|
+
) -> Result:
|
630
650
|
"""Trigger another workflow execution. It will wait the trigger
|
631
651
|
workflow running complete before catching its result.
|
632
652
|
|
633
653
|
:param params: A parameter data that want to use in this execution.
|
634
|
-
:param
|
654
|
+
:param result: (Result) A result object for keeping context and status
|
655
|
+
data.
|
635
656
|
|
636
657
|
:rtype: Result
|
637
658
|
"""
|
@@ -644,13 +665,11 @@ class TriggerStage(BaseStage):
|
|
644
665
|
# NOTE: Set running workflow ID from running stage ID to external
|
645
666
|
# params on Loader object.
|
646
667
|
wf: Workflow = Workflow.from_loader(name=_trigger)
|
647
|
-
|
648
|
-
f"({cut_id(run_id)}) [STAGE]: Trigger-Execute: {_trigger!r}"
|
649
|
-
)
|
668
|
+
result.trace.info(f"[STAGE]: Trigger-Execute: {_trigger!r}")
|
650
669
|
return wf.execute(
|
651
670
|
params=param2template(self.params, params),
|
652
|
-
|
653
|
-
)
|
671
|
+
result=result,
|
672
|
+
)
|
654
673
|
|
655
674
|
|
656
675
|
# NOTE:
|
@@ -668,12 +687,12 @@ Stage = Union[
|
|
668
687
|
|
669
688
|
|
670
689
|
# TODO: Not implement this stages yet
|
671
|
-
class ParallelStage(BaseModel):
|
690
|
+
class ParallelStage(BaseModel): # pragma: no cov
|
672
691
|
parallel: list[Stage]
|
673
692
|
max_parallel_core: int = Field(default=2)
|
674
693
|
|
675
694
|
|
676
695
|
# TODO: Not implement this stages yet
|
677
|
-
class ForEachStage(BaseModel):
|
696
|
+
class ForEachStage(BaseModel): # pragma: no cov
|
678
697
|
foreach: list[str]
|
679
698
|
stages: list[Stage]
|
ddeutil/workflow/templates.py
CHANGED
@@ -79,7 +79,7 @@ def custom_filter(name: str) -> Callable[P, FilterFunc]:
|
|
79
79
|
def make_filter_registry() -> dict[str, FilterRegistry]:
|
80
80
|
"""Return registries of all functions that able to called with task.
|
81
81
|
|
82
|
-
:rtype: dict[str,
|
82
|
+
:rtype: dict[str, FilterRegistry]
|
83
83
|
"""
|
84
84
|
rs: dict[str, FilterRegistry] = {}
|
85
85
|
for module in config.regis_filter:
|
@@ -108,6 +108,8 @@ def get_args_const(
|
|
108
108
|
) -> tuple[str, list[Constant], dict[str, Constant]]:
|
109
109
|
"""Get arguments and keyword-arguments from function calling string.
|
110
110
|
|
111
|
+
:param expr: An expr string value.
|
112
|
+
|
111
113
|
:rtype: tuple[str, list[Constant], dict[str, Constant]]
|
112
114
|
"""
|
113
115
|
try:
|
@@ -150,6 +152,11 @@ def get_args_from_filter(
|
|
150
152
|
) -> tuple[str, FilterRegistry, list[Any], dict[Any, Any]]: # pragma: no cov
|
151
153
|
"""Get arguments and keyword-arguments from filter function calling string.
|
152
154
|
and validate it with the filter functions mapping dict.
|
155
|
+
|
156
|
+
:param ft:
|
157
|
+
:param filters:
|
158
|
+
|
159
|
+
:rtype: tuple[str, FilterRegistry, list[Any], dict[Any, Any]]
|
153
160
|
"""
|
154
161
|
func_name, _args, _kwargs = get_args_const(ft)
|
155
162
|
args: list[Any] = [arg.value for arg in _args]
|
@@ -243,7 +250,7 @@ def str2template(
|
|
243
250
|
params: DictData,
|
244
251
|
*,
|
245
252
|
filters: dict[str, FilterRegistry] | None = None,
|
246
|
-
) ->
|
253
|
+
) -> str:
|
247
254
|
"""(Sub-function) Pass param to template string that can search by
|
248
255
|
``RE_CALLER`` regular expression.
|
249
256
|
|
@@ -255,6 +262,8 @@ def str2template(
|
|
255
262
|
:param params: A parameter value that getting with matched regular
|
256
263
|
expression.
|
257
264
|
:param filters:
|
265
|
+
|
266
|
+
:rtype: str
|
258
267
|
"""
|
259
268
|
filters: dict[str, FilterRegistry] = filters or make_filter_registry()
|
260
269
|
|
@@ -295,7 +304,7 @@ def str2template(
|
|
295
304
|
return search_env_replace(value)
|
296
305
|
|
297
306
|
|
298
|
-
def param2template(value:
|
307
|
+
def param2template(value: T, params: DictData) -> T:
|
299
308
|
"""Pass param to template string that can search by ``RE_CALLER`` regular
|
300
309
|
expression.
|
301
310
|
|
@@ -303,7 +312,7 @@ def param2template(value: Any, params: DictData) -> Any:
|
|
303
312
|
:param params: A parameter value that getting with matched regular
|
304
313
|
expression.
|
305
314
|
|
306
|
-
:rtype:
|
315
|
+
:rtype: T
|
307
316
|
:returns: An any getter value from the params input.
|
308
317
|
"""
|
309
318
|
filters: dict[str, FilterRegistry] = make_filter_registry()
|