ddeutil-workflow 0.0.16__py3-none-any.whl → 0.0.18__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 → __cron.py} +12 -6
- ddeutil/workflow/__init__.py +50 -2
- ddeutil/workflow/api.py +1 -2
- ddeutil/workflow/cli.py +1 -2
- ddeutil/workflow/conf.py +275 -110
- ddeutil/workflow/job.py +77 -39
- ddeutil/workflow/on.py +10 -7
- ddeutil/workflow/repeat.py +2 -3
- ddeutil/workflow/route.py +1 -2
- ddeutil/workflow/scheduler.py +38 -32
- ddeutil/workflow/stage.py +49 -19
- ddeutil/workflow/utils.py +22 -22
- {ddeutil_workflow-0.0.16.dist-info → ddeutil_workflow-0.0.18.dist-info}/METADATA +27 -23
- ddeutil_workflow-0.0.18.dist-info/RECORD +21 -0
- ddeutil/workflow/log.py +0 -195
- ddeutil_workflow-0.0.16.dist-info/RECORD +0 -22
- {ddeutil_workflow-0.0.16.dist-info → ddeutil_workflow-0.0.18.dist-info}/LICENSE +0 -0
- {ddeutil_workflow-0.0.16.dist-info → ddeutil_workflow-0.0.18.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.16.dist-info → ddeutil_workflow-0.0.18.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.16.dist-info → ddeutil_workflow-0.0.18.dist-info}/top_level.txt +0 -0
ddeutil/workflow/job.py
CHANGED
@@ -30,13 +30,12 @@ from pydantic.functional_validators import field_validator, model_validator
|
|
30
30
|
from typing_extensions import Self
|
31
31
|
|
32
32
|
from .__types import DictData, DictStr, Matrix, TupleStr
|
33
|
-
from .conf import config
|
33
|
+
from .conf import config, get_logger
|
34
34
|
from .exceptions import (
|
35
35
|
JobException,
|
36
36
|
StageException,
|
37
37
|
UtilException,
|
38
38
|
)
|
39
|
-
from .log import get_logger
|
40
39
|
from .stage import Stage
|
41
40
|
from .utils import (
|
42
41
|
Result,
|
@@ -111,6 +110,7 @@ def make(
|
|
111
110
|
all(inc.get(k) == v for k, v in m.items()) for m in [*final, *add]
|
112
111
|
):
|
113
112
|
continue
|
113
|
+
|
114
114
|
add.append(inc)
|
115
115
|
|
116
116
|
# NOTE: Merge all matrix together.
|
@@ -273,11 +273,32 @@ class Job(BaseModel):
|
|
273
273
|
|
274
274
|
@field_validator("desc", mode="after")
|
275
275
|
def ___prepare_desc__(cls, value: str) -> str:
|
276
|
-
"""Prepare description string that was created on a template.
|
276
|
+
"""Prepare description string that was created on a template.
|
277
|
+
|
278
|
+
:rtype: str
|
279
|
+
"""
|
277
280
|
return dedent(value)
|
278
281
|
|
282
|
+
@field_validator("stages", mode="after")
|
283
|
+
def __validate_stage_id__(cls, value: list[Stage]) -> list[Stage]:
|
284
|
+
"""Validate a stage ID of all stage in stages field should not be
|
285
|
+
duplicate.
|
286
|
+
|
287
|
+
:rtype: list[Stage]
|
288
|
+
"""
|
289
|
+
# VALIDATE: Validate stage id should not duplicate.
|
290
|
+
rs: list[str] = []
|
291
|
+
for stage in value:
|
292
|
+
name: str = stage.id or stage.name
|
293
|
+
if name in rs:
|
294
|
+
raise ValueError(
|
295
|
+
"Stage name in jobs object should not be duplicate."
|
296
|
+
)
|
297
|
+
rs.append(name)
|
298
|
+
return value
|
299
|
+
|
279
300
|
@model_validator(mode="after")
|
280
|
-
def
|
301
|
+
def __prepare_running_id_and_stage_name__(self) -> Self:
|
281
302
|
"""Prepare the job running ID.
|
282
303
|
|
283
304
|
:rtype: Self
|
@@ -355,7 +376,7 @@ class Job(BaseModel):
|
|
355
376
|
to["jobs"][_id] = (
|
356
377
|
{"strategies": output}
|
357
378
|
if self.strategy.is_set()
|
358
|
-
else output
|
379
|
+
else output.get(next(iter(output), "DUMMY"), {})
|
359
380
|
)
|
360
381
|
return to
|
361
382
|
|
@@ -365,7 +386,6 @@ class Job(BaseModel):
|
|
365
386
|
params: DictData,
|
366
387
|
*,
|
367
388
|
event: Event | None = None,
|
368
|
-
raise_error: bool = True,
|
369
389
|
) -> Result:
|
370
390
|
"""Job Strategy execution with passing dynamic parameters from the
|
371
391
|
workflow execution to strategy matrix.
|
@@ -374,19 +394,20 @@ class Job(BaseModel):
|
|
374
394
|
It different with ``self.execute`` because this method run only one
|
375
395
|
strategy and return with context of this strategy data.
|
376
396
|
|
377
|
-
:raise JobException: If it has any error from StageException or
|
378
|
-
UtilException
|
397
|
+
:raise JobException: If it has any error from ``StageException`` or
|
398
|
+
``UtilException``.
|
379
399
|
|
380
400
|
:param strategy: A metrix strategy value.
|
381
401
|
:param params: A dynamic parameters.
|
382
402
|
:param event: An manger event that pass to the PoolThreadExecutor.
|
383
|
-
|
384
|
-
if it get exception from stage execution.
|
403
|
+
|
385
404
|
:rtype: Result
|
386
405
|
"""
|
387
406
|
strategy_id: str = gen_id(strategy)
|
388
407
|
|
389
|
-
#
|
408
|
+
# PARAGRAPH:
|
409
|
+
#
|
410
|
+
# Create strategy execution context and update a matrix and copied
|
390
411
|
# of params. So, the context value will have structure like;
|
391
412
|
#
|
392
413
|
# {
|
@@ -405,14 +426,14 @@ class Job(BaseModel):
|
|
405
426
|
# IMPORTANT: Change any stage running IDs to this job running ID.
|
406
427
|
stage: Stage = stage.get_running_id(self.run_id)
|
407
428
|
|
408
|
-
|
429
|
+
name: str = stage.id or stage.name
|
409
430
|
|
410
431
|
if stage.is_skipped(params=context):
|
411
|
-
logger.info(f"({self.run_id}) [JOB]: Skip stage: {
|
432
|
+
logger.info(f"({self.run_id}) [JOB]: Skip stage: {name!r}")
|
412
433
|
continue
|
413
434
|
|
414
435
|
logger.info(
|
415
|
-
f"({self.run_id}) [JOB]: Start execute the stage: {
|
436
|
+
f"({self.run_id}) [JOB]: Start execute the stage: {name!r}"
|
416
437
|
)
|
417
438
|
|
418
439
|
# NOTE: Logging a matrix that pass on this stage execution.
|
@@ -432,20 +453,20 @@ class Job(BaseModel):
|
|
432
453
|
# ---
|
433
454
|
# "stages": filter_func(context.pop("stages", {})),
|
434
455
|
"stages": context.pop("stages", {}),
|
435
|
-
# NOTE: Set the error keys.
|
436
456
|
"error": JobException(
|
437
|
-
"
|
457
|
+
"Job strategy was canceled from trigger event "
|
458
|
+
"that had stopped before execution."
|
459
|
+
),
|
460
|
+
"error_message": (
|
461
|
+
"Job strategy was canceled from trigger event "
|
462
|
+
"that had stopped before execution."
|
438
463
|
),
|
439
|
-
"error_message": {
|
440
|
-
"message": (
|
441
|
-
"Process Event stopped before execution"
|
442
|
-
),
|
443
|
-
},
|
444
464
|
},
|
445
465
|
},
|
446
466
|
)
|
447
467
|
|
448
|
-
#
|
468
|
+
# PARAGRAPH:
|
469
|
+
#
|
449
470
|
# I do not use below syntax because `params` dict be the
|
450
471
|
# reference memory pointer and it was changed when I action
|
451
472
|
# anything like update or re-construct this.
|
@@ -471,16 +492,25 @@ class Job(BaseModel):
|
|
471
492
|
logger.error(
|
472
493
|
f"({self.run_id}) [JOB]: {err.__class__.__name__}: {err}"
|
473
494
|
)
|
474
|
-
if
|
495
|
+
if config.job_raise_error:
|
475
496
|
raise JobException(
|
476
497
|
f"Get stage execution error: {err.__class__.__name__}: "
|
477
498
|
f"{err}"
|
478
499
|
) from None
|
479
|
-
|
480
|
-
|
500
|
+
return Result(
|
501
|
+
status=1,
|
502
|
+
context={
|
503
|
+
strategy_id: {
|
504
|
+
"matrix": strategy,
|
505
|
+
"stages": context.pop("stages", {}),
|
506
|
+
"error": err,
|
507
|
+
"error_message": f"{err.__class__.__name__}: {err}",
|
508
|
+
},
|
509
|
+
},
|
510
|
+
)
|
481
511
|
|
482
|
-
# NOTE: Remove
|
483
|
-
# ``get_running_id`` method.
|
512
|
+
# NOTE: Remove the current stage object that was created from
|
513
|
+
# ``get_running_id`` method for saving memory.
|
484
514
|
del stage
|
485
515
|
|
486
516
|
return Result(
|
@@ -583,30 +613,34 @@ class Job(BaseModel):
|
|
583
613
|
)
|
584
614
|
logger.debug(f"({self.run_id}) [JOB]: Strategy is set Fail Fast{nd}")
|
585
615
|
|
586
|
-
# NOTE:
|
616
|
+
# NOTE:
|
617
|
+
# Stop all running tasks with setting the event manager and cancel
|
587
618
|
# any scheduled tasks.
|
619
|
+
#
|
588
620
|
if len(done) != len(futures):
|
589
621
|
event.set()
|
590
|
-
for future in
|
622
|
+
for future in not_done:
|
591
623
|
future.cancel()
|
592
624
|
|
593
|
-
|
594
|
-
|
625
|
+
future: Future
|
595
626
|
for future in done:
|
596
|
-
if future.exception():
|
597
|
-
status = 1
|
627
|
+
if err := future.exception():
|
628
|
+
status: int = 1
|
598
629
|
logger.error(
|
599
630
|
f"({self.run_id}) [JOB]: One stage failed with: "
|
600
631
|
f"{future.exception()}, shutting down this future."
|
601
632
|
)
|
602
|
-
|
633
|
+
context.update(
|
634
|
+
{
|
635
|
+
"error": err,
|
636
|
+
"error_message": f"{err.__class__.__name__}: {err}",
|
637
|
+
},
|
638
|
+
)
|
603
639
|
continue
|
604
640
|
|
605
641
|
# NOTE: Update the result context to main job context.
|
606
642
|
context.update(future.result(timeout=result_timeout).context)
|
607
643
|
|
608
|
-
del future
|
609
|
-
|
610
644
|
return rs_final.catch(status=status, context=context)
|
611
645
|
|
612
646
|
def __catch_all_completed(
|
@@ -631,7 +665,7 @@ class Job(BaseModel):
|
|
631
665
|
for future in as_completed(futures, timeout=timeout):
|
632
666
|
try:
|
633
667
|
context.update(future.result(timeout=result_timeout).context)
|
634
|
-
except TimeoutError:
|
668
|
+
except TimeoutError: # pragma: no cov
|
635
669
|
status = 1
|
636
670
|
logger.warning(
|
637
671
|
f"({self.run_id}) [JOB]: Task is hanging. Attempting to "
|
@@ -653,6 +687,10 @@ class Job(BaseModel):
|
|
653
687
|
f"fail-fast does not set;\n{err.__class__.__name__}:\n\t"
|
654
688
|
f"{err}"
|
655
689
|
)
|
656
|
-
|
657
|
-
|
690
|
+
context.update(
|
691
|
+
{
|
692
|
+
"error": err,
|
693
|
+
"error_message": f"{err.__class__.__name__}: {err}",
|
694
|
+
},
|
695
|
+
)
|
658
696
|
return rs_final.catch(status=status, context=context)
|
ddeutil/workflow/on.py
CHANGED
@@ -14,9 +14,9 @@ from pydantic.functional_serializers import field_serializer
|
|
14
14
|
from pydantic.functional_validators import field_validator, model_validator
|
15
15
|
from typing_extensions import Self
|
16
16
|
|
17
|
+
from .__cron import WEEKDAYS, CronJob, CronJobYear, CronRunner
|
17
18
|
from .__types import DictData, DictStr, TupleStr
|
18
19
|
from .conf import Loader
|
19
|
-
from .cron import WEEKDAYS, CronJob, CronJobYear, CronRunner
|
20
20
|
|
21
21
|
__all__: TupleStr = (
|
22
22
|
"On",
|
@@ -109,7 +109,7 @@ class On(BaseModel):
|
|
109
109
|
def from_loader(
|
110
110
|
cls,
|
111
111
|
name: str,
|
112
|
-
externals: DictData,
|
112
|
+
externals: DictData | None = None,
|
113
113
|
) -> Self:
|
114
114
|
"""Constructor from the name of config that will use loader object for
|
115
115
|
getting the data.
|
@@ -117,6 +117,7 @@ class On(BaseModel):
|
|
117
117
|
:param name: A name of config that will getting from loader.
|
118
118
|
:param externals: A extras external parameter that will keep in extras.
|
119
119
|
"""
|
120
|
+
externals: DictData = externals or {}
|
120
121
|
loader: Loader = Loader(name, externals=externals)
|
121
122
|
|
122
123
|
# NOTE: Validate the config type match with current connection model
|
@@ -139,7 +140,9 @@ class On(BaseModel):
|
|
139
140
|
)
|
140
141
|
)
|
141
142
|
if "cronjob" not in loader_data:
|
142
|
-
raise ValueError(
|
143
|
+
raise ValueError(
|
144
|
+
"Config does not set ``cronjob`` or ``interval`` keys"
|
145
|
+
)
|
143
146
|
return cls.model_validate(
|
144
147
|
obj=dict(
|
145
148
|
cronjob=loader_data.pop("cronjob"),
|
@@ -175,17 +178,17 @@ class On(BaseModel):
|
|
175
178
|
|
176
179
|
def generate(self, start: str | datetime) -> CronRunner:
|
177
180
|
"""Return Cron runner object."""
|
178
|
-
if
|
181
|
+
if isinstance(start, str):
|
179
182
|
start: datetime = datetime.fromisoformat(start)
|
183
|
+
elif not isinstance(start, datetime):
|
184
|
+
raise TypeError("start value should be str or datetime type.")
|
180
185
|
return self.cronjob.schedule(date=start, tz=self.tz)
|
181
186
|
|
182
187
|
def next(self, start: str | datetime) -> datetime:
|
183
188
|
"""Return a next datetime from Cron runner object that start with any
|
184
189
|
date that given from input.
|
185
190
|
"""
|
186
|
-
|
187
|
-
start: datetime = datetime.fromisoformat(start)
|
188
|
-
return self.cronjob.schedule(date=start, tz=self.tz).next
|
191
|
+
return self.generate(start=start).next
|
189
192
|
|
190
193
|
|
191
194
|
class YearOn(On):
|
ddeutil/workflow/repeat.py
CHANGED
@@ -12,9 +12,8 @@ from functools import wraps
|
|
12
12
|
|
13
13
|
from starlette.concurrency import run_in_threadpool
|
14
14
|
|
15
|
-
from .
|
16
|
-
from .
|
17
|
-
from .log import get_logger
|
15
|
+
from .__cron import CronJob
|
16
|
+
from .conf import config, get_logger
|
18
17
|
|
19
18
|
logger = get_logger("ddeutil.workflow")
|
20
19
|
|
ddeutil/workflow/route.py
CHANGED
@@ -16,8 +16,7 @@ from pydantic import BaseModel
|
|
16
16
|
|
17
17
|
from . import Workflow
|
18
18
|
from .__types import DictData
|
19
|
-
from .conf import Loader, config
|
20
|
-
from .log import get_logger
|
19
|
+
from .conf import Loader, config, get_logger
|
21
20
|
from .scheduler import Schedule
|
22
21
|
from .utils import Result
|
23
22
|
|
ddeutil/workflow/scheduler.py
CHANGED
@@ -52,15 +52,14 @@ except ImportError:
|
|
52
52
|
|
53
53
|
try:
|
54
54
|
from schedule import CancelJob
|
55
|
-
except ImportError:
|
55
|
+
except ImportError: # pragma: no cov
|
56
56
|
CancelJob = None
|
57
57
|
|
58
|
+
from .__cron import CronRunner
|
58
59
|
from .__types import DictData, TupleStr
|
59
|
-
from .conf import Loader, config
|
60
|
-
from .cron import CronRunner
|
60
|
+
from .conf import FileLog, Loader, Log, config, get_logger
|
61
61
|
from .exceptions import JobException, WorkflowException
|
62
62
|
from .job import Job
|
63
|
-
from .log import FileLog, Log, get_logger
|
64
63
|
from .on import On
|
65
64
|
from .utils import (
|
66
65
|
Param,
|
@@ -230,8 +229,8 @@ class Workflow(BaseModel):
|
|
230
229
|
need for need in self.jobs[job].needs if need not in self.jobs
|
231
230
|
]:
|
232
231
|
raise WorkflowException(
|
233
|
-
f"
|
234
|
-
f"
|
232
|
+
f"The needed jobs: {not_exist} do not found in "
|
233
|
+
f"{self.name!r}."
|
235
234
|
)
|
236
235
|
|
237
236
|
# NOTE: update a job id with its job id from workflow template
|
@@ -354,11 +353,11 @@ class Workflow(BaseModel):
|
|
354
353
|
# NOTE: get next schedule time that generate from now.
|
355
354
|
next_time: datetime = gen.next
|
356
355
|
|
357
|
-
# NOTE:
|
356
|
+
# NOTE: While-loop to getting next until it does not logger.
|
358
357
|
while log.is_pointed(self.name, next_time, queue=queue):
|
359
358
|
next_time: datetime = gen.next
|
360
359
|
|
361
|
-
# NOTE: push this next running time to log queue
|
360
|
+
# NOTE: Heap-push this next running time to log queue list.
|
362
361
|
heappush(queue, next_time)
|
363
362
|
|
364
363
|
# VALIDATE: Check the different time between the next schedule time and
|
@@ -377,7 +376,7 @@ class Workflow(BaseModel):
|
|
377
376
|
status=0,
|
378
377
|
context={
|
379
378
|
"params": params,
|
380
|
-
"
|
379
|
+
"release": {"status": "skipped", "cron": [str(on.cronjob)]},
|
381
380
|
},
|
382
381
|
)
|
383
382
|
|
@@ -389,7 +388,7 @@ class Workflow(BaseModel):
|
|
389
388
|
# NOTE: Release when the time is nearly to schedule time.
|
390
389
|
while (duration := get_diff_sec(next_time, tz=cron_tz)) > (
|
391
390
|
sleep_interval + 5
|
392
|
-
):
|
391
|
+
): # pragma: no cov
|
393
392
|
logger.debug(
|
394
393
|
f"({self.run_id}) [CORE]: {self.name!r} : {on.cronjob} : "
|
395
394
|
f"Sleep until: {duration}"
|
@@ -440,7 +439,7 @@ class Workflow(BaseModel):
|
|
440
439
|
status=0,
|
441
440
|
context={
|
442
441
|
"params": params,
|
443
|
-
"
|
442
|
+
"release": {"status": "run", "cron": [str(on.cronjob)]},
|
444
443
|
},
|
445
444
|
)
|
446
445
|
|
@@ -493,7 +492,7 @@ class Workflow(BaseModel):
|
|
493
492
|
for future in as_completed(futures):
|
494
493
|
results.append(future.result(timeout=60))
|
495
494
|
|
496
|
-
if len(queue) > 0:
|
495
|
+
if len(queue) > 0: # pragma: no cov
|
497
496
|
logger.error(
|
498
497
|
f"({self.run_id}) [POKING]: Log Queue does empty when poking "
|
499
498
|
f"process was finishing."
|
@@ -709,7 +708,7 @@ class Workflow(BaseModel):
|
|
709
708
|
raise WorkflowException(f"{err}")
|
710
709
|
try:
|
711
710
|
future.result(timeout=60)
|
712
|
-
except TimeoutError as err:
|
711
|
+
except TimeoutError as err: # pragma: no cove
|
713
712
|
raise WorkflowException(
|
714
713
|
"Timeout when getting result from future"
|
715
714
|
) from err
|
@@ -718,11 +717,11 @@ class Workflow(BaseModel):
|
|
718
717
|
return context
|
719
718
|
|
720
719
|
# NOTE: Raise timeout error.
|
721
|
-
logger.warning(
|
720
|
+
logger.warning( # pragma: no cov
|
722
721
|
f"({self.run_id}) [WORKFLOW]: Execution of workflow, {self.name!r} "
|
723
722
|
f", was timeout"
|
724
723
|
)
|
725
|
-
raise WorkflowException(
|
724
|
+
raise WorkflowException( # pragma: no cov
|
726
725
|
f"Execution of workflow: {self.name} was timeout"
|
727
726
|
)
|
728
727
|
|
@@ -766,7 +765,8 @@ class Workflow(BaseModel):
|
|
766
765
|
continue
|
767
766
|
|
768
767
|
# NOTE: Start workflow job execution with deep copy context data
|
769
|
-
# before release.
|
768
|
+
# before release. This job execution process will running until
|
769
|
+
# done before checking all execution timeout or not.
|
770
770
|
#
|
771
771
|
# {
|
772
772
|
# 'params': <input-params>,
|
@@ -784,10 +784,10 @@ class Workflow(BaseModel):
|
|
784
784
|
return context
|
785
785
|
|
786
786
|
# NOTE: Raise timeout error.
|
787
|
-
logger.warning(
|
787
|
+
logger.warning( # pragma: no cov
|
788
788
|
f"({self.run_id}) [WORKFLOW]: Execution of workflow was timeout"
|
789
789
|
)
|
790
|
-
raise WorkflowException(
|
790
|
+
raise WorkflowException( # pragma: no cov
|
791
791
|
f"Execution of workflow: {self.name} was timeout"
|
792
792
|
)
|
793
793
|
|
@@ -833,12 +833,13 @@ class ScheduleWorkflow(BaseModel):
|
|
833
833
|
if on := data.pop("on", []):
|
834
834
|
|
835
835
|
if isinstance(on, str):
|
836
|
-
on = [on]
|
836
|
+
on: list[str] = [on]
|
837
837
|
|
838
838
|
if any(not isinstance(n, (dict, str)) for n in on):
|
839
839
|
raise TypeError("The ``on`` key should be list of str or dict")
|
840
840
|
|
841
|
-
# NOTE: Pass on value to Loader and keep on model object to on
|
841
|
+
# NOTE: Pass on value to Loader and keep on model object to on
|
842
|
+
# field.
|
842
843
|
data["on"] = [
|
843
844
|
(
|
844
845
|
Loader(n, externals=(externals or {})).data
|
@@ -903,12 +904,14 @@ class Schedule(BaseModel):
|
|
903
904
|
*,
|
904
905
|
externals: DictData | None = None,
|
905
906
|
) -> list[WorkflowTaskData]:
|
906
|
-
"""
|
907
|
+
"""Return the list of WorkflowTaskData object from the specific input
|
908
|
+
datetime that mapping with the on field.
|
907
909
|
|
908
910
|
:param start_date: A start date that get from the workflow schedule.
|
909
911
|
:param queue: A mapping of name and list of datetime for queue.
|
910
912
|
:param running: A mapping of name and list of datetime for running.
|
911
913
|
:param externals: An external parameters that pass to the Loader object.
|
914
|
+
|
912
915
|
:rtype: list[WorkflowTaskData]
|
913
916
|
"""
|
914
917
|
|
@@ -923,12 +926,14 @@ class Schedule(BaseModel):
|
|
923
926
|
queue[wfs.name]: list[datetime] = []
|
924
927
|
running[wfs.name]: list[datetime] = []
|
925
928
|
|
926
|
-
# NOTE: Create default on if it does not passing on the
|
929
|
+
# NOTE: Create the default on value if it does not passing on the
|
930
|
+
# Schedule object.
|
927
931
|
_ons: list[On] = wf.on.copy() if len(wfs.on) == 0 else wfs.on
|
928
932
|
|
929
933
|
for on in _ons:
|
930
|
-
on_gen = on.generate(start_date)
|
934
|
+
on_gen: CronRunner = on.generate(start_date)
|
931
935
|
next_running_date = on_gen.next
|
936
|
+
|
932
937
|
while next_running_date in queue[wfs.name]:
|
933
938
|
next_running_date = on_gen.next
|
934
939
|
|
@@ -958,13 +963,14 @@ def catch_exceptions(cancel_on_failure: bool = False) -> DecoratorCancelJob:
|
|
958
963
|
|
959
964
|
:param cancel_on_failure: A flag that allow to return the CancelJob or not
|
960
965
|
it will raise.
|
961
|
-
|
966
|
+
|
967
|
+
:rtype: DecoratorCancelJob
|
962
968
|
"""
|
963
969
|
|
964
970
|
def decorator(func: ReturnCancelJob) -> ReturnCancelJob:
|
965
971
|
try:
|
966
972
|
# NOTE: Check the function that want to handle is method or not.
|
967
|
-
if inspect.ismethod(func):
|
973
|
+
if inspect.ismethod(func): # pragma: no cov
|
968
974
|
|
969
975
|
@wraps(func)
|
970
976
|
def wrapper(self, *args, **kwargs):
|
@@ -978,7 +984,7 @@ def catch_exceptions(cancel_on_failure: bool = False) -> DecoratorCancelJob:
|
|
978
984
|
|
979
985
|
return wrapper
|
980
986
|
|
981
|
-
except Exception as err:
|
987
|
+
except Exception as err: # pragma: no cov
|
982
988
|
logger.exception(err)
|
983
989
|
if cancel_on_failure:
|
984
990
|
return CancelJob
|
@@ -1006,7 +1012,7 @@ class WorkflowTaskData:
|
|
1006
1012
|
*,
|
1007
1013
|
waiting_sec: int = 60,
|
1008
1014
|
sleep_interval: int = 15,
|
1009
|
-
) -> None:
|
1015
|
+
) -> None: # pragma: no cov
|
1010
1016
|
"""Workflow release, it will use with the same logic of
|
1011
1017
|
`workflow.release` method.
|
1012
1018
|
|
@@ -1120,7 +1126,7 @@ class WorkflowTaskData:
|
|
1120
1126
|
future_running_time in self.running[wf.name]
|
1121
1127
|
or future_running_time in self.queue[wf.name]
|
1122
1128
|
or future_running_time < finish_time
|
1123
|
-
):
|
1129
|
+
): # pragma: no cov
|
1124
1130
|
future_running_time: datetime = gen.next
|
1125
1131
|
|
1126
1132
|
heappush(self.queue[wf.name], future_running_time)
|
@@ -1135,7 +1141,7 @@ class WorkflowTaskData:
|
|
1135
1141
|
return NotImplemented
|
1136
1142
|
|
1137
1143
|
|
1138
|
-
@catch_exceptions(cancel_on_failure=True)
|
1144
|
+
@catch_exceptions(cancel_on_failure=True) # pragma: no cov
|
1139
1145
|
def workflow_task(
|
1140
1146
|
workflow_tasks: list[WorkflowTaskData],
|
1141
1147
|
stop: datetime,
|
@@ -1234,7 +1240,7 @@ def workflow_task(
|
|
1234
1240
|
logger.debug(f"[WORKFLOW]: {'=' * 100}")
|
1235
1241
|
|
1236
1242
|
|
1237
|
-
def workflow_monitor(threads: dict[str, Thread]) -> None:
|
1243
|
+
def workflow_monitor(threads: dict[str, Thread]) -> None: # pragma: no cov
|
1238
1244
|
"""Workflow schedule for monitoring long running thread from the schedule
|
1239
1245
|
control.
|
1240
1246
|
|
@@ -1256,7 +1262,7 @@ def workflow_control(
|
|
1256
1262
|
schedules: list[str],
|
1257
1263
|
stop: datetime | None = None,
|
1258
1264
|
externals: DictData | None = None,
|
1259
|
-
) -> list[str]:
|
1265
|
+
) -> list[str]: # pragma: no cov
|
1260
1266
|
"""Workflow scheduler control.
|
1261
1267
|
|
1262
1268
|
:param schedules: A list of workflow names that want to schedule running.
|
@@ -1344,7 +1350,7 @@ def workflow_runner(
|
|
1344
1350
|
stop: datetime | None = None,
|
1345
1351
|
externals: DictData | None = None,
|
1346
1352
|
excluded: list[str] | None = None,
|
1347
|
-
) -> list[str]:
|
1353
|
+
) -> list[str]: # pragma: no cov
|
1348
1354
|
"""Workflow application that running multiprocessing schedule with chunk of
|
1349
1355
|
workflows that exists in config path.
|
1350
1356
|
|