ddeutil-workflow 0.0.23__py3-none-any.whl → 0.0.25__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 +6 -0
- ddeutil/workflow/__init__.py +8 -7
- ddeutil/workflow/api/__init__.py +1 -0
- ddeutil/workflow/{api.py → api/api.py} +43 -21
- ddeutil/workflow/{repeat.py → api/repeat.py} +2 -2
- ddeutil/workflow/{route.py → api/route.py} +81 -62
- ddeutil/workflow/cli.py +33 -55
- ddeutil/workflow/conf.py +38 -45
- ddeutil/workflow/{on.py → cron.py} +1 -1
- ddeutil/workflow/exceptions.py +3 -0
- ddeutil/workflow/scheduler.py +212 -165
- ddeutil/workflow/stage.py +5 -0
- ddeutil/workflow/utils.py +7 -5
- ddeutil/workflow/workflow.py +149 -149
- {ddeutil_workflow-0.0.23.dist-info → ddeutil_workflow-0.0.25.dist-info}/METADATA +33 -35
- ddeutil_workflow-0.0.25.dist-info/RECORD +25 -0
- ddeutil_workflow-0.0.23.dist-info/RECORD +0 -24
- {ddeutil_workflow-0.0.23.dist-info → ddeutil_workflow-0.0.25.dist-info}/LICENSE +0 -0
- {ddeutil_workflow-0.0.23.dist-info → ddeutil_workflow-0.0.25.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.23.dist-info → ddeutil_workflow-0.0.25.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.23.dist-info → ddeutil_workflow-0.0.25.dist-info}/top_level.txt +0 -0
ddeutil/workflow/workflow.py
CHANGED
@@ -43,19 +43,18 @@ from typing_extensions import Self
|
|
43
43
|
from .__cron import CronJob, CronRunner
|
44
44
|
from .__types import DictData, TupleStr
|
45
45
|
from .conf import FileLog, Loader, Log, config, get_logger
|
46
|
+
from .cron import On
|
46
47
|
from .exceptions import JobException, WorkflowException
|
47
48
|
from .job import Job
|
48
|
-
from .on import On
|
49
49
|
from .params import Param
|
50
50
|
from .result import Result
|
51
51
|
from .utils import (
|
52
52
|
cut_id,
|
53
|
-
delay,
|
54
53
|
gen_id,
|
55
|
-
get_diff_sec,
|
56
54
|
get_dt_now,
|
57
55
|
has_template,
|
58
56
|
param2template,
|
57
|
+
wait_a_minute,
|
59
58
|
)
|
60
59
|
|
61
60
|
logger = get_logger("ddeutil.workflow")
|
@@ -64,7 +63,7 @@ __all__: TupleStr = (
|
|
64
63
|
"Workflow",
|
65
64
|
"WorkflowRelease",
|
66
65
|
"WorkflowQueue",
|
67
|
-
"
|
66
|
+
"WorkflowTask",
|
68
67
|
)
|
69
68
|
|
70
69
|
|
@@ -168,7 +167,23 @@ class WorkflowQueue:
|
|
168
167
|
"""
|
169
168
|
return len(self.queue) > 0
|
170
169
|
|
171
|
-
|
170
|
+
@property
|
171
|
+
def first_queue(self) -> WorkflowRelease:
|
172
|
+
"""Check an input WorkflowRelease object is the first value of the
|
173
|
+
waiting queue.
|
174
|
+
|
175
|
+
:rtype: bool
|
176
|
+
"""
|
177
|
+
# NOTE: Old logic to peeking the first release from waiting queue.
|
178
|
+
#
|
179
|
+
# first_value: WorkflowRelease = heappop(self.queue)
|
180
|
+
# heappush(self.queue, first_value)
|
181
|
+
#
|
182
|
+
# return first_value
|
183
|
+
#
|
184
|
+
return self.queue[0]
|
185
|
+
|
186
|
+
def check_queue(self, value: WorkflowRelease | datetime) -> bool:
|
172
187
|
"""Check a WorkflowRelease value already exists in list of tracking
|
173
188
|
queues.
|
174
189
|
|
@@ -177,27 +192,37 @@ class WorkflowQueue:
|
|
177
192
|
|
178
193
|
:rtype: bool
|
179
194
|
"""
|
195
|
+
if isinstance(value, datetime):
|
196
|
+
value = WorkflowRelease.from_dt(value)
|
197
|
+
|
180
198
|
return (
|
181
199
|
(value in self.queue)
|
182
200
|
or (value in self.running)
|
183
201
|
or (value in self.complete)
|
184
202
|
)
|
185
203
|
|
186
|
-
def push_queue(self, value: WorkflowRelease) -> Self:
|
187
|
-
"""Push data to the queue."""
|
188
|
-
heappush(self.queue, value)
|
189
|
-
return self
|
190
|
-
|
191
|
-
def push_running(self, value: WorkflowRelease) -> Self:
|
192
|
-
"""Push data to the running."""
|
193
|
-
heappush(self.running, value)
|
194
|
-
return self
|
195
|
-
|
196
204
|
def remove_running(self, value: WorkflowRelease) -> Self:
|
197
|
-
"""Remove
|
205
|
+
"""Remove WorkflowRelease in the running queue if it exists."""
|
198
206
|
if value in self.running:
|
199
207
|
self.running.remove(value)
|
200
208
|
|
209
|
+
def mark_complete(self, value: WorkflowRelease) -> Self:
|
210
|
+
"""Push WorkflowRelease to the complete queue."""
|
211
|
+
heappush(self.complete, value)
|
212
|
+
|
213
|
+
# NOTE: Remove complete queue on workflow that keep more than the
|
214
|
+
# maximum config.
|
215
|
+
num_complete_delete: int = (
|
216
|
+
len(self.complete) - config.max_queue_complete_hist
|
217
|
+
)
|
218
|
+
|
219
|
+
if num_complete_delete > 0:
|
220
|
+
print(num_complete_delete)
|
221
|
+
for _ in range(num_complete_delete):
|
222
|
+
heappop(self.complete)
|
223
|
+
|
224
|
+
return self
|
225
|
+
|
201
226
|
|
202
227
|
class Workflow(BaseModel):
|
203
228
|
"""Workflow Pydantic model.
|
@@ -249,7 +274,7 @@ class Workflow(BaseModel):
|
|
249
274
|
loader: Loader = Loader(name, externals=(externals or {}))
|
250
275
|
|
251
276
|
# NOTE: Validate the config type match with current connection model
|
252
|
-
if loader.type != cls:
|
277
|
+
if loader.type != cls.__name__:
|
253
278
|
raise ValueError(f"Type {loader.type} does not match with {cls}")
|
254
279
|
|
255
280
|
loader_data: DictData = copy.deepcopy(loader.data)
|
@@ -448,6 +473,7 @@ class Workflow(BaseModel):
|
|
448
473
|
queue: (
|
449
474
|
WorkflowQueue | list[datetime] | list[WorkflowRelease] | None
|
450
475
|
) = None,
|
476
|
+
override_log_name: str | None = None,
|
451
477
|
) -> Result:
|
452
478
|
"""Release the workflow execution with overriding parameter with the
|
453
479
|
release templating that include logical date (release date), execution
|
@@ -456,18 +482,30 @@ class Workflow(BaseModel):
|
|
456
482
|
This method allow workflow use log object to save the execution
|
457
483
|
result to log destination like file log to the local `/logs` directory.
|
458
484
|
|
485
|
+
:Steps:
|
486
|
+
- Initialize WorkflowQueue and WorkflowRelease if they do not pass.
|
487
|
+
- Create release data for pass to parameter templating function.
|
488
|
+
- Execute this workflow with mapping release data to its parameters.
|
489
|
+
- Writing log
|
490
|
+
- Remove this release on the running queue
|
491
|
+
- Push this release to complete queue
|
492
|
+
|
459
493
|
:param release: A release datetime or WorkflowRelease object.
|
460
494
|
:param params: A workflow parameter that pass to execute method.
|
461
495
|
:param queue: A list of release time that already queue.
|
462
496
|
:param run_id: A workflow running ID for this release.
|
463
497
|
:param log: A log class that want to save the execution result.
|
464
498
|
:param queue: A WorkflowQueue object.
|
499
|
+
:param override_log_name: An override logging name that use instead
|
500
|
+
the workflow name.
|
465
501
|
|
466
502
|
:rtype: Result
|
467
503
|
"""
|
468
504
|
log: type[Log] = log or FileLog
|
469
|
-
|
505
|
+
name: str = override_log_name or self.name
|
506
|
+
run_id: str = run_id or gen_id(name, unique=True)
|
470
507
|
rs_release: Result = Result(run_id=run_id)
|
508
|
+
rs_release_type: str = "release"
|
471
509
|
|
472
510
|
# VALIDATE: Change queue value to WorkflowQueue object.
|
473
511
|
if queue is None or isinstance(queue, list):
|
@@ -475,10 +513,11 @@ class Workflow(BaseModel):
|
|
475
513
|
|
476
514
|
# VALIDATE: Change release value to WorkflowRelease object.
|
477
515
|
if isinstance(release, datetime):
|
516
|
+
rs_release_type: str = "datetime"
|
478
517
|
release: WorkflowRelease = WorkflowRelease.from_dt(release)
|
479
518
|
|
480
519
|
logger.debug(
|
481
|
-
f"({cut_id(run_id)}) [RELEASE]: {
|
520
|
+
f"({cut_id(run_id)}) [RELEASE]: Start release - {name!r} : "
|
482
521
|
f"{release.date:%Y-%m-%d %H:%M:%S}"
|
483
522
|
)
|
484
523
|
|
@@ -499,14 +538,14 @@ class Workflow(BaseModel):
|
|
499
538
|
run_id=run_id,
|
500
539
|
)
|
501
540
|
logger.debug(
|
502
|
-
f"({cut_id(run_id)}) [RELEASE]: {
|
541
|
+
f"({cut_id(run_id)}) [RELEASE]: End release - {name!r} : "
|
503
542
|
f"{release.date:%Y-%m-%d %H:%M:%S}"
|
504
543
|
)
|
505
544
|
|
506
545
|
rs.set_parent_run_id(run_id)
|
507
546
|
rs_log: Log = log.model_validate(
|
508
547
|
{
|
509
|
-
"name":
|
548
|
+
"name": name,
|
510
549
|
"release": release.date,
|
511
550
|
"type": release.type,
|
512
551
|
"context": rs.context,
|
@@ -516,12 +555,14 @@ class Workflow(BaseModel):
|
|
516
555
|
)
|
517
556
|
|
518
557
|
# NOTE: Saving execution result to destination of the input log object.
|
558
|
+
logger.debug(f"({cut_id(run_id)}) [LOG]: Writing log: {name!r}.")
|
519
559
|
rs_log.save(excluded=None)
|
520
560
|
|
521
561
|
# NOTE: Remove this release from running.
|
522
562
|
queue.remove_running(release)
|
523
|
-
|
563
|
+
queue.mark_complete(release)
|
524
564
|
|
565
|
+
# NOTE: Remove the params key from the result context for deduplicate.
|
525
566
|
context: dict[str, Any] = rs.context
|
526
567
|
context.pop("params")
|
527
568
|
|
@@ -529,12 +570,17 @@ class Workflow(BaseModel):
|
|
529
570
|
status=0,
|
530
571
|
context={
|
531
572
|
"params": params,
|
532
|
-
"release": {
|
573
|
+
"release": {
|
574
|
+
"status": "success",
|
575
|
+
"type": rs_release_type,
|
576
|
+
"logical_date": release.date,
|
577
|
+
"release": release,
|
578
|
+
},
|
533
579
|
"outputs": context,
|
534
580
|
},
|
535
581
|
)
|
536
582
|
|
537
|
-
def
|
583
|
+
def queue(
|
538
584
|
self,
|
539
585
|
offset: float,
|
540
586
|
end_date: datetime,
|
@@ -589,7 +635,7 @@ class Workflow(BaseModel):
|
|
589
635
|
continue
|
590
636
|
|
591
637
|
# NOTE: Push the WorkflowRelease object to queue.
|
592
|
-
queue.
|
638
|
+
heappush(queue.queue, workflow_release)
|
593
639
|
|
594
640
|
return queue
|
595
641
|
|
@@ -667,7 +713,7 @@ class Workflow(BaseModel):
|
|
667
713
|
wf_queue: WorkflowQueue = WorkflowQueue()
|
668
714
|
|
669
715
|
# NOTE: Make queue to the workflow queue object.
|
670
|
-
self.
|
716
|
+
self.queue(
|
671
717
|
offset,
|
672
718
|
end_date=end_date,
|
673
719
|
queue=wf_queue,
|
@@ -703,11 +749,11 @@ class Workflow(BaseModel):
|
|
703
749
|
f"release has diff time more than 60 seconds ..."
|
704
750
|
)
|
705
751
|
heappush(wf_queue.queue, release)
|
706
|
-
|
752
|
+
wait_a_minute(get_dt_now(tz=config.tz, offset=offset))
|
707
753
|
|
708
754
|
# WARNING: I already call queue poking again because issue
|
709
755
|
# about the every minute crontab.
|
710
|
-
self.
|
756
|
+
self.queue(
|
711
757
|
offset,
|
712
758
|
end_date,
|
713
759
|
queue=wf_queue,
|
@@ -717,7 +763,7 @@ class Workflow(BaseModel):
|
|
717
763
|
continue
|
718
764
|
|
719
765
|
# NOTE: Push the latest WorkflowRelease to the running queue.
|
720
|
-
wf_queue.
|
766
|
+
heappush(wf_queue.running, release)
|
721
767
|
|
722
768
|
futures.append(
|
723
769
|
executor.submit(
|
@@ -729,7 +775,7 @@ class Workflow(BaseModel):
|
|
729
775
|
)
|
730
776
|
)
|
731
777
|
|
732
|
-
self.
|
778
|
+
self.queue(
|
733
779
|
offset,
|
734
780
|
end_date,
|
735
781
|
queue=wf_queue,
|
@@ -1074,7 +1120,7 @@ class Workflow(BaseModel):
|
|
1074
1120
|
|
1075
1121
|
|
1076
1122
|
@dataclass(config=ConfigDict(arbitrary_types_allowed=True))
|
1077
|
-
class
|
1123
|
+
class WorkflowTask:
|
1078
1124
|
"""Workflow task Pydantic dataclass object that use to keep mapping data and
|
1079
1125
|
workflow model for passing to the multithreading task.
|
1080
1126
|
|
@@ -1085,151 +1131,105 @@ class WorkflowTaskData:
|
|
1085
1131
|
alias: str
|
1086
1132
|
workflow: Workflow
|
1087
1133
|
runner: CronRunner
|
1088
|
-
|
1134
|
+
values: DictData = field(default_factory=dict)
|
1089
1135
|
|
1090
1136
|
def release(
|
1091
1137
|
self,
|
1092
|
-
|
1093
|
-
log: Log | None = None,
|
1138
|
+
release: datetime | WorkflowRelease | None = None,
|
1094
1139
|
run_id: str | None = None,
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
:param queue: A mapping of alias name and list of release datetime.
|
1104
|
-
:param log: A log object for saving result logging from workflow
|
1105
|
-
execution process.
|
1140
|
+
log: type[Log] = None,
|
1141
|
+
queue: (
|
1142
|
+
WorkflowQueue | list[datetime] | list[WorkflowRelease] | None
|
1143
|
+
) = None,
|
1144
|
+
) -> Result:
|
1145
|
+
"""Release the workflow task data.
|
1146
|
+
|
1147
|
+
:param release: A release datetime or WorkflowRelease object.
|
1106
1148
|
:param run_id: A workflow running ID for this release.
|
1107
|
-
:param
|
1108
|
-
:param
|
1109
|
-
to execute.
|
1149
|
+
:param log: A log class that want to save the execution result.
|
1150
|
+
:param queue: A WorkflowQueue object.
|
1110
1151
|
|
1111
1152
|
:rtype: Result
|
1112
1153
|
"""
|
1113
1154
|
log: type[Log] = log or FileLog
|
1114
|
-
run_id: str = run_id or gen_id(self.alias, unique=True)
|
1115
|
-
rs_release: Result = Result(run_id=run_id)
|
1116
|
-
runner: CronRunner = self.runner
|
1117
1155
|
|
1118
|
-
|
1119
|
-
|
1156
|
+
if release is None:
|
1157
|
+
if queue.check_queue(self.runner.date):
|
1158
|
+
release = self.runner.next
|
1120
1159
|
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
next_time: datetime = runner.next
|
1160
|
+
while queue.check_queue(release):
|
1161
|
+
release = self.runner.next
|
1162
|
+
else:
|
1163
|
+
release = self.runner.date
|
1126
1164
|
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1165
|
+
return self.workflow.release(
|
1166
|
+
release=release,
|
1167
|
+
params=self.values,
|
1168
|
+
run_id=run_id,
|
1169
|
+
log=log,
|
1170
|
+
queue=queue,
|
1171
|
+
override_log_name=self.alias,
|
1130
1172
|
)
|
1131
|
-
heappush(queue[self.alias], next_time)
|
1132
|
-
start_sec: float = time.monotonic()
|
1133
|
-
|
1134
|
-
if get_diff_sec(next_time, tz=runner.tz) > waiting_sec:
|
1135
|
-
logger.debug(
|
1136
|
-
f"({cut_id(run_id)}) [WORKFLOW]: {self.workflow.name!r} : "
|
1137
|
-
f"{runner.cron} "
|
1138
|
-
f": Does not closely >> {next_time:%Y-%m-%d %H:%M:%S}"
|
1139
|
-
)
|
1140
1173
|
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1174
|
+
def queue(
|
1175
|
+
self,
|
1176
|
+
end_date: datetime,
|
1177
|
+
queue: WorkflowQueue,
|
1178
|
+
log: type[Log],
|
1179
|
+
*,
|
1180
|
+
force_run: bool = False,
|
1181
|
+
):
|
1182
|
+
"""Generate WorkflowRelease to WorkflowQueue object.
|
1183
|
+
|
1184
|
+
:param end_date: An end datetime object.
|
1185
|
+
:param queue: A workflow queue object.
|
1186
|
+
:param log: A log class that want to making log object.
|
1187
|
+
:param force_run: A flag that allow to release workflow if the log with
|
1188
|
+
that release was pointed.
|
1144
1189
|
|
1145
|
-
|
1146
|
-
|
1190
|
+
:rtype: WorkflowQueue
|
1191
|
+
"""
|
1192
|
+
if self.runner.date > end_date:
|
1193
|
+
return queue
|
1147
1194
|
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1195
|
+
workflow_release = WorkflowRelease(
|
1196
|
+
date=self.runner.date,
|
1197
|
+
offset=0,
|
1198
|
+
end_date=end_date,
|
1199
|
+
runner=self.runner,
|
1200
|
+
type="task",
|
1151
1201
|
)
|
1152
1202
|
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1203
|
+
while queue.check_queue(workflow_release) or (
|
1204
|
+
log.is_pointed(name=self.alias, release=workflow_release.date)
|
1205
|
+
and not force_run
|
1156
1206
|
):
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1207
|
+
workflow_release = WorkflowRelease(
|
1208
|
+
date=self.runner.next,
|
1209
|
+
offset=0,
|
1210
|
+
end_date=end_date,
|
1211
|
+
runner=self.runner,
|
1212
|
+
type="task",
|
1160
1213
|
)
|
1161
|
-
time.sleep(15)
|
1162
1214
|
|
1163
|
-
|
1215
|
+
if self.runner.date > end_date:
|
1216
|
+
return queue
|
1164
1217
|
|
1165
|
-
# NOTE:
|
1166
|
-
|
1167
|
-
release_params: DictData = {
|
1168
|
-
"release": {
|
1169
|
-
"logical_date": next_time,
|
1170
|
-
"execute_date": datetime.now(tz=config.tz),
|
1171
|
-
"run_id": run_id,
|
1172
|
-
"timezone": runner.tz,
|
1173
|
-
},
|
1174
|
-
}
|
1175
|
-
|
1176
|
-
# WARNING: Re-create workflow object that use new running workflow ID.
|
1177
|
-
rs: Result = self.workflow.execute(
|
1178
|
-
params=param2template(self.params, release_params),
|
1179
|
-
)
|
1180
|
-
logger.debug(
|
1181
|
-
f"({cut_id(run_id)}) [CORE]: {self.workflow.name!r} : "
|
1182
|
-
f"{runner.cron} : End release - {next_time:%Y-%m-%d %H:%M:%S}"
|
1183
|
-
)
|
1184
|
-
|
1185
|
-
# NOTE: Set parent ID on this result.
|
1186
|
-
rs.set_parent_run_id(run_id)
|
1218
|
+
# NOTE: Push the WorkflowRelease object to queue.
|
1219
|
+
heappush(queue.queue, workflow_release)
|
1187
1220
|
|
1188
|
-
|
1189
|
-
rs_log: Log = log.model_validate(
|
1190
|
-
{
|
1191
|
-
"name": self.workflow.name,
|
1192
|
-
"type": "schedule",
|
1193
|
-
"release": next_time,
|
1194
|
-
"context": rs.context,
|
1195
|
-
"parent_run_id": rs.run_id,
|
1196
|
-
"run_id": rs.run_id,
|
1197
|
-
}
|
1198
|
-
)
|
1199
|
-
rs_log.save(excluded=None)
|
1200
|
-
|
1201
|
-
# NOTE: Remove the current release date from the running.
|
1202
|
-
queue[self.alias].remove(next_time)
|
1203
|
-
total_sec: float = time.monotonic() - start_sec
|
1204
|
-
|
1205
|
-
# IMPORTANT:
|
1206
|
-
# Add the next running datetime to workflow task queue.
|
1207
|
-
future_running_time: datetime = runner.next
|
1208
|
-
|
1209
|
-
while (
|
1210
|
-
future_running_time in queue[self.alias]
|
1211
|
-
or (future_running_time - next_time).total_seconds() < total_sec
|
1212
|
-
): # pragma: no cov
|
1213
|
-
future_running_time: datetime = runner.next
|
1214
|
-
|
1215
|
-
# NOTE: Queue next release date.
|
1216
|
-
logger.debug(f"[CORE]: {'-' * 100}")
|
1217
|
-
|
1218
|
-
context: dict[str, Any] = rs.context
|
1219
|
-
context.pop("params")
|
1221
|
+
return queue
|
1220
1222
|
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
"outputs": context,
|
1227
|
-
},
|
1223
|
+
def __repr__(self) -> str:
|
1224
|
+
return (
|
1225
|
+
f"{self.__class__.__name__}(alias={self.alias!r}, "
|
1226
|
+
f"workflow={self.workflow.name!r}, runner={self.runner!r}, "
|
1227
|
+
f"values={self.values})"
|
1228
1228
|
)
|
1229
1229
|
|
1230
|
-
def __eq__(self, other:
|
1230
|
+
def __eq__(self, other: WorkflowTask) -> bool:
|
1231
1231
|
"""Override equal property that will compare only the same type."""
|
1232
|
-
if isinstance(other,
|
1232
|
+
if isinstance(other, WorkflowTask):
|
1233
1233
|
return (
|
1234
1234
|
self.workflow.name == other.workflow.name
|
1235
1235
|
and self.runner.cron == other.runner.cron
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ddeutil-workflow
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.25
|
4
4
|
Summary: Lightweight workflow orchestration with less dependencies
|
5
5
|
Author-email: ddeutils <korawich.anu@gmail.com>
|
6
6
|
License: MIT
|
@@ -24,7 +24,7 @@ Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
25
25
|
Requires-Dist: ddeutil>=0.4.3
|
26
26
|
Requires-Dist: ddeutil-io[toml,yaml]>=0.2.3
|
27
|
-
Requires-Dist: pydantic==2.10.
|
27
|
+
Requires-Dist: pydantic==2.10.4
|
28
28
|
Requires-Dist: python-dotenv==1.0.1
|
29
29
|
Requires-Dist: typer==0.15.1
|
30
30
|
Requires-Dist: schedule<2.0.0,==1.2.2
|
@@ -68,8 +68,8 @@ configuration. It called **Metadata Driven Data Workflow**.
|
|
68
68
|
> with `.yml` files and all of config file from several data orchestration framework
|
69
69
|
> tools from my experience on Data Engineer. :grimacing:
|
70
70
|
>
|
71
|
-
> Other workflow that I interest on them and pick some interested feature
|
72
|
-
> package:
|
71
|
+
> Other workflow tools that I interest on them and pick some interested feature
|
72
|
+
> implement to this package:
|
73
73
|
>
|
74
74
|
> - [Google **Workflows**](https://cloud.google.com/workflows)
|
75
75
|
> - [AWS **Step Functions**](https://aws.amazon.com/step-functions/)
|
@@ -85,9 +85,6 @@ If you want to install this package with application add-ons, you should add
|
|
85
85
|
| Python & CLI | `pip install ddeutil-workflow` | :heavy_check_mark: |
|
86
86
|
| FastAPI Server | `pip install ddeutil-workflow[api]` | :heavy_check_mark: |
|
87
87
|
|
88
|
-
|
89
|
-
> I added this feature to the main milestone.
|
90
|
-
>
|
91
88
|
> :egg: **Docker Images** supported:
|
92
89
|
>
|
93
90
|
> | Docker Image | Python Version | Support |
|
@@ -113,7 +110,7 @@ use-case.
|
|
113
110
|
run-py-local:
|
114
111
|
|
115
112
|
# Validate model that use to parsing exists for template file
|
116
|
-
type: ddeutil.workflow.Workflow
|
113
|
+
type: ddeutil.workflow.workflow.Workflow
|
117
114
|
on:
|
118
115
|
# If workflow deploy to schedule, it will running every 5 minutes
|
119
116
|
# with Asia/Bangkok timezone.
|
@@ -182,34 +179,35 @@ The main configuration that use to dynamic changing with your propose of this
|
|
182
179
|
application. If any configuration values do not set yet, it will use default value
|
183
180
|
and do not raise any error to you.
|
184
181
|
|
185
|
-
| Environment | Component
|
186
|
-
|
187
|
-
| `WORKFLOW_ROOT_PATH` |
|
188
|
-
| `WORKFLOW_CORE_REGISTRY` |
|
189
|
-
| `WORKFLOW_CORE_REGISTRY_FILTER` |
|
190
|
-
| `WORKFLOW_CORE_PATH_CONF` |
|
191
|
-
| `WORKFLOW_CORE_TIMEZONE` |
|
192
|
-
| `WORKFLOW_CORE_STAGE_DEFAULT_ID` |
|
193
|
-
| `WORKFLOW_CORE_STAGE_RAISE_ERROR` |
|
194
|
-
| `WORKFLOW_CORE_JOB_DEFAULT_ID` |
|
195
|
-
| `WORKFLOW_CORE_JOB_RAISE_ERROR` |
|
196
|
-
| `WORKFLOW_CORE_MAX_NUM_POKING` |
|
197
|
-
| `WORKFLOW_CORE_MAX_JOB_PARALLEL` |
|
198
|
-
| `WORKFLOW_CORE_MAX_JOB_EXEC_TIMEOUT` |
|
199
|
-
| `
|
200
|
-
| `
|
201
|
-
| `
|
202
|
-
| `
|
203
|
-
| `
|
204
|
-
| `
|
205
|
-
| `
|
182
|
+
| Environment | Component | Default | Description | Remark |
|
183
|
+
|:----------------------------------------|:----------:|:---------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------|--------|
|
184
|
+
| `WORKFLOW_ROOT_PATH` | Core | . | The root path of the workflow application. | |
|
185
|
+
| `WORKFLOW_CORE_REGISTRY` | Core | src,src.ddeutil.workflow,tests,tests.utils | List of importable string for the hook stage. | |
|
186
|
+
| `WORKFLOW_CORE_REGISTRY_FILTER` | Core | src.ddeutil.workflow.utils,ddeutil.workflow.utils | List of importable string for the filter template. | |
|
187
|
+
| `WORKFLOW_CORE_PATH_CONF` | Core | conf | The config path that keep all template `.yaml` files. | |
|
188
|
+
| `WORKFLOW_CORE_TIMEZONE` | Core | Asia/Bangkok | A Timezone string value that will pass to `ZoneInfo` object. | |
|
189
|
+
| `WORKFLOW_CORE_STAGE_DEFAULT_ID` | Core | true | A flag that enable default stage ID that use for catch an execution output. | |
|
190
|
+
| `WORKFLOW_CORE_STAGE_RAISE_ERROR` | Core | false | A flag that all stage raise StageException from stage execution. | |
|
191
|
+
| `WORKFLOW_CORE_JOB_DEFAULT_ID` | Core | false | A flag that enable default job ID that use for catch an execution output. The ID that use will be sequence number. | |
|
192
|
+
| `WORKFLOW_CORE_JOB_RAISE_ERROR` | Core | true | A flag that all job raise JobException from job strategy execution. | |
|
193
|
+
| `WORKFLOW_CORE_MAX_NUM_POKING` | Core | 4 | . | |
|
194
|
+
| `WORKFLOW_CORE_MAX_JOB_PARALLEL` | Core | 2 | The maximum job number that able to run parallel in workflow executor. | |
|
195
|
+
| `WORKFLOW_CORE_MAX_JOB_EXEC_TIMEOUT` | Core | 600 | | |
|
196
|
+
| `WORKFLOW_CORE_MAX_CRON_PER_WORKFLOW` | Core | 5 | | |
|
197
|
+
| `WORKFLOW_CORE_MAX_QUEUE_COMPLETE_HIST` | Core | 16 | | |
|
198
|
+
| `WORKFLOW_CORE_GENERATE_ID_SIMPLE_MODE` | Core | true | A flog that enable generating ID with `md5` algorithm. | |
|
199
|
+
| `WORKFLOW_LOG_DEBUG_MODE` | Log | true | A flag that enable logging with debug level mode. | |
|
200
|
+
| `WORKFLOW_LOG_ENABLE_WRITE` | Log | true | A flag that enable logging object saving log to its destination. | |
|
201
|
+
| `WORKFLOW_APP_MAX_PROCESS` | Schedule | 2 | The maximum process worker number that run in scheduler app module. | |
|
202
|
+
| `WORKFLOW_APP_MAX_SCHEDULE_PER_PROCESS` | Schedule | 100 | A schedule per process that run parallel. | |
|
203
|
+
| `WORKFLOW_APP_STOP_BOUNDARY_DELTA` | Schedule | '{"minutes": 5, "seconds": 20}' | A time delta value that use to stop scheduler app in json string format. | |
|
206
204
|
|
207
205
|
**API Application**:
|
208
206
|
|
209
|
-
| Environment |
|
210
|
-
|
211
|
-
| `WORKFLOW_API_ENABLE_ROUTE_WORKFLOW` |
|
212
|
-
| `WORKFLOW_API_ENABLE_ROUTE_SCHEDULE` |
|
207
|
+
| Environment | Component | Default | Description | Remark |
|
208
|
+
|:--------------------------------------|:-----------:|---------|------------------------------------------------------------------------------------|--------|
|
209
|
+
| `WORKFLOW_API_ENABLE_ROUTE_WORKFLOW` | API | true | A flag that enable workflow route to manage execute manually and workflow logging. | |
|
210
|
+
| `WORKFLOW_API_ENABLE_ROUTE_SCHEDULE` | API | true | A flag that enable run scheduler. | |
|
213
211
|
|
214
212
|
## :rocket: Deployment
|
215
213
|
|
@@ -217,7 +215,7 @@ This package able to run as a application service for receive manual trigger
|
|
217
215
|
from the master node via RestAPI or use to be Scheduler background service
|
218
216
|
like crontab job but via Python API.
|
219
217
|
|
220
|
-
###
|
218
|
+
### CLI
|
221
219
|
|
222
220
|
```shell
|
223
221
|
(venv) $ ddeutil-workflow schedule
|
@@ -230,7 +228,7 @@ like crontab job but via Python API.
|
|
230
228
|
```
|
231
229
|
|
232
230
|
> [!NOTE]
|
233
|
-
> If this package already deploy, it able to use
|
231
|
+
> If this package already deploy, it able to use multiprocess;
|
234
232
|
> `uvicorn ddeutil.workflow.api:app --host 127.0.0.1 --port 80 --workers 4`
|
235
233
|
|
236
234
|
### Docker Container
|
@@ -0,0 +1,25 @@
|
|
1
|
+
ddeutil/workflow/__about__.py,sha256=t4IIhCiioD7q4m2ksb5i3WtaGFGCRBYpETJrirYC7s8,28
|
2
|
+
ddeutil/workflow/__cron.py,sha256=uA8XcbY_GwA9rJSHaHUaXaJyGDObJN0ZeYlJSinL8y8,26880
|
3
|
+
ddeutil/workflow/__init__.py,sha256=49eGrCuchPVZKMybRouAviNhbulK_F6VwCmLm76hIss,1478
|
4
|
+
ddeutil/workflow/__types.py,sha256=Ia7f38kvL3NibwmRKi0wQ1ud_45Z-SojYGhNJwIqcu8,3713
|
5
|
+
ddeutil/workflow/cli.py,sha256=Ik14rFFxE9u20uOgCa4Vx7fNuKXVsWJg7gZb15sGans,2878
|
6
|
+
ddeutil/workflow/conf.py,sha256=YY2zZ_qv9JkTDs_73bkyrF1n1cqBINuxzMxbBjzYw-8,15361
|
7
|
+
ddeutil/workflow/cron.py,sha256=75A0hqevvouziKoLALncLJspVAeki9qCH3zniAJaxzY,7513
|
8
|
+
ddeutil/workflow/exceptions.py,sha256=P56K7VD3etGm9y-k_GXrzEyqsTCaz9EJazTIshZDf9g,943
|
9
|
+
ddeutil/workflow/job.py,sha256=cvSLMdc1sMl1MeU7so7Oe2SdRYxQwt6hm55mLV1iP-Y,24219
|
10
|
+
ddeutil/workflow/params.py,sha256=uPGkZx18E-iZ8BteqQ2ONgg0frhF3ZmP5cOyfK2j59U,5280
|
11
|
+
ddeutil/workflow/result.py,sha256=WIC8MsnfLiWNpZomT6jS4YCdYhlbIVVBjtGGe2dkoKk,3404
|
12
|
+
ddeutil/workflow/scheduler.py,sha256=_V812UlqcwfVF2Sl_45nIatMklioBXcXfGZSFoAAjwo,20452
|
13
|
+
ddeutil/workflow/stage.py,sha256=a2sngzs9DkP6GU2pgAD3QvGoijyBQTR_pOhyJUIuWAo,26692
|
14
|
+
ddeutil/workflow/utils.py,sha256=pucRnCi9aLJDptXhzzReHZd5d-S0o5oZif5tr6H4iy8,18736
|
15
|
+
ddeutil/workflow/workflow.py,sha256=AD0rs1tRT2EpvUyNVAEr2bBPgF6-KOzGmLedR3o4y0Q,42177
|
16
|
+
ddeutil/workflow/api/__init__.py,sha256=F53NMBWtb9IKaDWkPU5KvybGGfKAcbehgn6TLBwHuuM,21
|
17
|
+
ddeutil/workflow/api/api.py,sha256=Md1cz3Edc7_uz63s_L_i-R3IE4mkO3aTADrX8GOGU-Y,5644
|
18
|
+
ddeutil/workflow/api/repeat.py,sha256=zyvsrXKk-3-_N8ZRZSki0Mueshugum2jtqctEOp9QSc,4927
|
19
|
+
ddeutil/workflow/api/route.py,sha256=MQXtkF5uM_ZL1SGDuXFzgkNkbT5cpAXVNRp6mvewupM,7447
|
20
|
+
ddeutil_workflow-0.0.25.dist-info/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
|
21
|
+
ddeutil_workflow-0.0.25.dist-info/METADATA,sha256=Rt11UMGcXyYWwXcxb-p0LgelfUFi7DaM_lQwFVX1Mtw,14641
|
22
|
+
ddeutil_workflow-0.0.25.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
23
|
+
ddeutil_workflow-0.0.25.dist-info/entry_points.txt,sha256=0BVOgO3LdUdXVZ-CiHHDKxzEk2c8J30jEwHeKn2YCWI,62
|
24
|
+
ddeutil_workflow-0.0.25.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
|
25
|
+
ddeutil_workflow-0.0.25.dist-info/RECORD,,
|