ddeutil-workflow 0.0.49__py3-none-any.whl → 0.0.50__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 -0
- ddeutil/workflow/conf.py +9 -9
- ddeutil/workflow/exceptions.py +3 -3
- ddeutil/workflow/job.py +122 -71
- ddeutil/workflow/logs.py +1 -1
- ddeutil/workflow/result.py +12 -4
- ddeutil/workflow/stages.py +315 -115
- ddeutil/workflow/utils.py +42 -38
- ddeutil/workflow/workflow.py +18 -25
- {ddeutil_workflow-0.0.49.dist-info → ddeutil_workflow-0.0.50.dist-info}/METADATA +3 -2
- {ddeutil_workflow-0.0.49.dist-info → ddeutil_workflow-0.0.50.dist-info}/RECORD +15 -15
- {ddeutil_workflow-0.0.49.dist-info → ddeutil_workflow-0.0.50.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.49.dist-info → ddeutil_workflow-0.0.50.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.49.dist-info → ddeutil_workflow-0.0.50.dist-info}/top_level.txt +0 -0
ddeutil/workflow/utils.py
CHANGED
@@ -26,9 +26,11 @@ T = TypeVar("T")
|
|
26
26
|
UTC: Final[ZoneInfo] = ZoneInfo("UTC")
|
27
27
|
|
28
28
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
29
|
+
def replace_sec(dt: datetime) -> datetime:
|
30
|
+
return dt.replace(second=0, microsecond=0)
|
31
|
+
|
32
|
+
|
33
|
+
def get_dt_now(tz: ZoneInfo | None = None, offset: float = 0.0) -> datetime:
|
32
34
|
"""Return the current datetime object.
|
33
35
|
|
34
36
|
:param tz: A ZoneInfo object for replace timezone of return datetime object.
|
@@ -54,42 +56,31 @@ def get_d_now(
|
|
54
56
|
return (datetime.now(tz=(tz or UTC)) - timedelta(seconds=offset)).date()
|
55
57
|
|
56
58
|
|
57
|
-
def get_diff_sec(
|
58
|
-
dt: datetime, tz: ZoneInfo | None = None, offset: float = 0.0
|
59
|
-
) -> int: # pragma: no cov
|
59
|
+
def get_diff_sec(dt: datetime, offset: float = 0.0) -> int:
|
60
60
|
"""Return second value that come from diff of an input datetime and the
|
61
61
|
current datetime with specific timezone.
|
62
62
|
|
63
|
-
:param dt:
|
64
|
-
:param
|
65
|
-
:param offset: An offset second value.
|
63
|
+
:param dt: (datetime) A datetime object that want to get different second value.
|
64
|
+
:param offset: (float) An offset second value.
|
66
65
|
|
67
66
|
:rtype: int
|
68
67
|
"""
|
69
68
|
return round(
|
70
69
|
(
|
71
|
-
dt - datetime.now(tz=
|
70
|
+
dt - datetime.now(tz=dt.tzinfo) - timedelta(seconds=offset)
|
72
71
|
).total_seconds()
|
73
72
|
)
|
74
73
|
|
75
74
|
|
76
|
-
def reach_next_minute(
|
77
|
-
dt: datetime, tz: ZoneInfo | None = None, offset: float = 0.0
|
78
|
-
) -> bool:
|
75
|
+
def reach_next_minute(dt: datetime, offset: float = 0.0) -> bool:
|
79
76
|
"""Check this datetime object is not in range of minute level on the current
|
80
77
|
datetime.
|
81
78
|
|
82
|
-
:param dt:
|
83
|
-
:param
|
84
|
-
:param offset: An offset second value.
|
79
|
+
:param dt: (datetime) A datetime object that want to check.
|
80
|
+
:param offset: (float) An offset second value.
|
85
81
|
"""
|
86
82
|
diff: float = (
|
87
|
-
dt
|
88
|
-
- (
|
89
|
-
get_dt_now(tz=(tz or UTC), offset=offset).replace(
|
90
|
-
second=0, microsecond=0
|
91
|
-
)
|
92
|
-
)
|
83
|
+
replace_sec(dt) - replace_sec(get_dt_now(tz=dt.tzinfo, offset=offset))
|
93
84
|
).total_seconds()
|
94
85
|
if diff >= 60:
|
95
86
|
return True
|
@@ -106,7 +97,7 @@ def wait_to_next_minute(
|
|
106
97
|
dt: datetime, second: float = 0
|
107
98
|
) -> None: # pragma: no cov
|
108
99
|
"""Wait with sleep to the next minute with an offset second value."""
|
109
|
-
future = dt
|
100
|
+
future: datetime = replace_sec(dt) + timedelta(minutes=1)
|
110
101
|
time.sleep((future - dt).total_seconds() + second)
|
111
102
|
|
112
103
|
|
@@ -114,7 +105,7 @@ def delay(second: float = 0) -> None: # pragma: no cov
|
|
114
105
|
"""Delay time that use time.sleep with random second value between
|
115
106
|
0.00 - 0.99 seconds.
|
116
107
|
|
117
|
-
:param second: A second number that want to adds-on random value.
|
108
|
+
:param second: (float) A second number that want to adds-on random value.
|
118
109
|
"""
|
119
110
|
time.sleep(second + randrange(0, 99, step=10) / 100)
|
120
111
|
|
@@ -124,32 +115,42 @@ def gen_id(
|
|
124
115
|
*,
|
125
116
|
sensitive: bool = True,
|
126
117
|
unique: bool = False,
|
118
|
+
simple_mode: bool | None = None,
|
119
|
+
extras: DictData | None = None,
|
127
120
|
) -> str:
|
128
|
-
"""Generate running ID for able to tracking. This generates process use
|
129
|
-
algorithm function if
|
130
|
-
false. But it will cut this hashing value length to 10 it the setting
|
131
|
-
set to true.
|
121
|
+
"""Generate running ID for able to tracking. This generates process use
|
122
|
+
`md5` algorithm function if `WORKFLOW_CORE_WORKFLOW_ID_SIMPLE_MODE` set
|
123
|
+
to false. But it will cut this hashing value length to 10 it the setting
|
124
|
+
value set to true.
|
125
|
+
|
126
|
+
Simple Mode:
|
127
|
+
|
128
|
+
... 0000 00 00 00 00 00 000000 T 0000000000
|
129
|
+
... year month day hour minute second microsecond sep simple-id
|
132
130
|
|
133
131
|
:param value: A value that want to add to prefix before hashing with md5.
|
134
132
|
:param sensitive: A flag that convert the value to lower case before hashing
|
135
133
|
:param unique: A flag that add timestamp at microsecond level to value
|
136
134
|
before hashing.
|
135
|
+
:param simple_mode: A flag for generate ID by simple mode.
|
136
|
+
:param extras: An extra parameter that use for override config value.
|
137
137
|
|
138
138
|
:rtype: str
|
139
139
|
"""
|
140
|
-
from .conf import
|
140
|
+
from .conf import dynamic
|
141
141
|
|
142
142
|
if not isinstance(value, str):
|
143
143
|
value: str = str(value)
|
144
144
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
145
|
+
dt: datetime = datetime.now(tz=dynamic("tz", extras=extras))
|
146
|
+
if dynamic("generate_id_simple_mode", f=simple_mode, extras=extras):
|
147
|
+
return (f"{dt:%Y%m%d%H%M%S%f}T" if unique else "") + hash_str(
|
148
|
+
f"{(value if sensitive else value.lower())}", n=10
|
149
|
+
)
|
149
150
|
|
150
151
|
return md5(
|
151
152
|
(
|
152
|
-
(f"{
|
153
|
+
(f"{dt}T" if unique else "")
|
153
154
|
+ f"{(value if sensitive else value.lower())}"
|
154
155
|
).encode()
|
155
156
|
).hexdigest()
|
@@ -243,12 +244,15 @@ def cut_id(run_id: str, *, num: int = 6) -> str:
|
|
243
244
|
"""Cutting running ID with length.
|
244
245
|
|
245
246
|
Example:
|
246
|
-
>>> cut_id(run_id='
|
247
|
-
'
|
247
|
+
>>> cut_id(run_id='20240101081330000000T1354680202')
|
248
|
+
'202401010813680202'
|
248
249
|
|
249
|
-
:param run_id:
|
250
|
+
:param run_id: A running ID That want to cut
|
250
251
|
:param num:
|
251
252
|
|
252
253
|
:rtype: str
|
253
254
|
"""
|
254
|
-
|
255
|
+
if "T" in run_id:
|
256
|
+
dt, simple = run_id.split("T", maxsplit=1)
|
257
|
+
return dt[:12] + simple[-num:]
|
258
|
+
return run_id[:12] + run_id[-num:]
|
ddeutil/workflow/workflow.py
CHANGED
@@ -923,15 +923,11 @@ class Workflow(BaseModel):
|
|
923
923
|
# NOTE: Pop the latest Release object from the release queue.
|
924
924
|
release: Release = heappop(q.queue)
|
925
925
|
|
926
|
-
if reach_next_minute(
|
927
|
-
release.date,
|
928
|
-
tz=dynamic("tz", extras=self.extras),
|
929
|
-
offset=offset,
|
930
|
-
):
|
926
|
+
if reach_next_minute(release.date, offset=offset):
|
931
927
|
result.trace.debug(
|
932
|
-
f"[POKING]:
|
933
|
-
f"{release.date:%Y-%m-%d %H:%M:%S},
|
934
|
-
f"
|
928
|
+
f"[POKING]: Latest Release, "
|
929
|
+
f"{release.date:%Y-%m-%d %H:%M:%S}, can not run on "
|
930
|
+
f"this time"
|
935
931
|
)
|
936
932
|
heappush(q.queue, release)
|
937
933
|
wait_to_next_minute(
|
@@ -976,7 +972,6 @@ class Workflow(BaseModel):
|
|
976
972
|
*,
|
977
973
|
result: Result | None = None,
|
978
974
|
event: Event | None = None,
|
979
|
-
raise_error: bool = True,
|
980
975
|
) -> Result:
|
981
976
|
"""Job execution with passing dynamic parameters from the main workflow
|
982
977
|
execution to the target job object via job's ID.
|
@@ -987,7 +982,6 @@ class Workflow(BaseModel):
|
|
987
982
|
|
988
983
|
:raise WorkflowException: If execute with not exist job's ID.
|
989
984
|
:raise WorkflowException: If the job execution raise JobException.
|
990
|
-
:raise NotImplementedError: If set raise_error argument to False.
|
991
985
|
|
992
986
|
:param job_id: A job ID that want to execute.
|
993
987
|
:param params: A params that was parameterized from workflow execution.
|
@@ -995,8 +989,6 @@ class Workflow(BaseModel):
|
|
995
989
|
data.
|
996
990
|
:param event: (Event) An event manager that pass to the
|
997
991
|
PoolThreadExecutor.
|
998
|
-
:param raise_error: A flag that raise error instead catching to result
|
999
|
-
if it gets exception from job execution.
|
1000
992
|
|
1001
993
|
:rtype: Result
|
1002
994
|
:return: Return the result object that receive the job execution result
|
@@ -1021,10 +1013,10 @@ class Workflow(BaseModel):
|
|
1021
1013
|
try:
|
1022
1014
|
job: Job = self.jobs[job_id]
|
1023
1015
|
if job.is_skipped(params=params):
|
1024
|
-
result.trace.info(f"[
|
1016
|
+
result.trace.info(f"[WORKFLOW]: Skip job: {job_id!r}")
|
1025
1017
|
job.set_outputs(output={"SKIP": {"skipped": True}}, to=params)
|
1026
1018
|
else:
|
1027
|
-
result.trace.info(f"[
|
1019
|
+
result.trace.info(f"[WORKFLOW]: Execute: {job_id!r}")
|
1028
1020
|
job.set_outputs(
|
1029
1021
|
job.execute(
|
1030
1022
|
params=params,
|
@@ -1036,12 +1028,8 @@ class Workflow(BaseModel):
|
|
1036
1028
|
)
|
1037
1029
|
except JobException as e:
|
1038
1030
|
result.trace.error(f"[WORKFLOW]: {e.__class__.__name__}: {e}")
|
1039
|
-
|
1040
|
-
|
1041
|
-
f"Get job execution error {job_id}: JobException: {e}"
|
1042
|
-
) from None
|
1043
|
-
raise NotImplementedError(
|
1044
|
-
"Handle error from the job execution does not support yet."
|
1031
|
+
raise WorkflowException(
|
1032
|
+
f"Get job execution error {job_id}: JobException: {e}"
|
1045
1033
|
) from None
|
1046
1034
|
|
1047
1035
|
return result.catch(status=SUCCESS, context=params)
|
@@ -1276,7 +1264,7 @@ class Workflow(BaseModel):
|
|
1276
1264
|
max_workers=1,
|
1277
1265
|
thread_name_prefix="wf_exec_non_threading_",
|
1278
1266
|
) as executor:
|
1279
|
-
future: Future
|
1267
|
+
future: Optional[Future] = None
|
1280
1268
|
|
1281
1269
|
while not job_queue.empty() and (
|
1282
1270
|
not_timeout_flag := ((time.monotonic() - ts) < timeout)
|
@@ -1316,14 +1304,13 @@ class Workflow(BaseModel):
|
|
1316
1304
|
|
1317
1305
|
future = None
|
1318
1306
|
job_queue.put(job_id)
|
1319
|
-
elif future.running():
|
1307
|
+
elif future.running() or "state=pending" in str(future):
|
1320
1308
|
time.sleep(0.075)
|
1321
1309
|
job_queue.put(job_id)
|
1322
1310
|
else: # pragma: no cov
|
1323
1311
|
job_queue.put(job_id)
|
1324
|
-
result.trace.
|
1325
|
-
f"Execution non-threading
|
1326
|
-
f"that not running."
|
1312
|
+
result.trace.warning(
|
1313
|
+
f"... Execution non-threading not handle: {future}."
|
1327
1314
|
)
|
1328
1315
|
|
1329
1316
|
job_queue.task_done()
|
@@ -1352,6 +1339,12 @@ class WorkflowTask:
|
|
1352
1339
|
|
1353
1340
|
This dataclass has the release method for itself that prepare necessary
|
1354
1341
|
arguments before passing to the parent release method.
|
1342
|
+
|
1343
|
+
:param alias: (str) An alias name of Workflow model.
|
1344
|
+
:param workflow: (Workflow) A Workflow model instance.
|
1345
|
+
:param runner: (CronRunner)
|
1346
|
+
:param values:
|
1347
|
+
:param extras:
|
1355
1348
|
"""
|
1356
1349
|
|
1357
1350
|
alias: str
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ddeutil-workflow
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.50
|
4
4
|
Summary: Lightweight workflow orchestration
|
5
5
|
Author-email: ddeutils <korawich.anu@gmail.com>
|
6
6
|
License: MIT
|
@@ -39,6 +39,8 @@ Requires-Dist: ujson; extra == "api"
|
|
39
39
|
Provides-Extra: async
|
40
40
|
Requires-Dist: aiofiles; extra == "async"
|
41
41
|
Requires-Dist: aiohttp; extra == "async"
|
42
|
+
Provides-Extra: docker
|
43
|
+
Requires-Dist: docker==7.1.0; extra == "docker"
|
42
44
|
Dynamic: license-file
|
43
45
|
|
44
46
|
# Workflow Orchestration
|
@@ -268,7 +270,6 @@ it will use default value and do not raise any error to you.
|
|
268
270
|
| **TIMEZONE** | Core | `Asia/Bangkok` | A Timezone string value that will pass to `ZoneInfo` object. |
|
269
271
|
| **STAGE_DEFAULT_ID** | Core | `false` | A flag that enable default stage ID that use for catch an execution output. |
|
270
272
|
| **STAGE_RAISE_ERROR** | Core | `false` | A flag that all stage raise StageException from stage execution. |
|
271
|
-
| **JOB_RAISE_ERROR** | Core | `true` | A flag that all job raise JobException from job strategy execution. |
|
272
273
|
| **MAX_CRON_PER_WORKFLOW** | Core | `5` | |
|
273
274
|
| **MAX_QUEUE_COMPLETE_HIST** | Core | `16` | |
|
274
275
|
| **GENERATE_ID_SIMPLE_MODE** | Core | `true` | A flog that enable generating ID with `md5` algorithm. |
|
@@ -1,20 +1,20 @@
|
|
1
|
-
ddeutil/workflow/__about__.py,sha256=
|
1
|
+
ddeutil/workflow/__about__.py,sha256=K4g7cm4iInR43bd_K3fsuGDTVpz7fbAASmKh5_jH8_U,28
|
2
2
|
ddeutil/workflow/__cron.py,sha256=h8rLeIUAAEB2SdZ4Jhch7LU1Yl3bbJ-iNNJ3tQ0eYVM,28095
|
3
|
-
ddeutil/workflow/__init__.py,sha256=
|
3
|
+
ddeutil/workflow/__init__.py,sha256=3u-yGnTyfY4BFrKqA5UGaMVe_Q4cZNODuC9qZ5meOXo,2048
|
4
4
|
ddeutil/workflow/__main__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
5
|
ddeutil/workflow/__types.py,sha256=8jBdbfb3aZSetjz0mvNrpGHwwxJff7mK8_4v41cLqlc,4316
|
6
|
-
ddeutil/workflow/conf.py,sha256=
|
6
|
+
ddeutil/workflow/conf.py,sha256=o6RyqcjjeQXdPBZi3lMs5sSQ5aYvsUgMdJMoRYMWcN0,12492
|
7
7
|
ddeutil/workflow/cron.py,sha256=80SijzMdDOBxTWRsiF-Fmuz7Ym7leY0XT2lzRAPGdXc,8781
|
8
|
-
ddeutil/workflow/exceptions.py,sha256=
|
9
|
-
ddeutil/workflow/job.py,sha256
|
10
|
-
ddeutil/workflow/logs.py,sha256=
|
8
|
+
ddeutil/workflow/exceptions.py,sha256=r4Jrf9qtVPALU4wh4bnb_OYqC-StqSQJEmFC-_QK934,1408
|
9
|
+
ddeutil/workflow/job.py,sha256=-VUsv6ub3T199GyugplRjI8Vs6CKQ9QHY6yhlzvUF9w,32495
|
10
|
+
ddeutil/workflow/logs.py,sha256=GG8tqs2BQv-iXSPWqxfrRJvKwJBvXn86Uq2lW1HrM9U,26455
|
11
11
|
ddeutil/workflow/params.py,sha256=xCtFEh0-G-G-f8y_SXxyf31bU6Ox5p5Z-WbBFXrjy8M,9960
|
12
|
-
ddeutil/workflow/result.py,sha256=
|
12
|
+
ddeutil/workflow/result.py,sha256=27nPQq9CETLCVczv4vvFEF9w2TllHZ_ROfyDoLFxRWM,5647
|
13
13
|
ddeutil/workflow/reusables.py,sha256=hIpehea6J4OWeXX55kjYzo-c9-_Cc0YRwLRRbcaUkZs,17539
|
14
14
|
ddeutil/workflow/scheduler.py,sha256=F783QaJfPg8tvYyvJvkwl8Sa42vsJzj6BzzROZFvm9I,28153
|
15
|
-
ddeutil/workflow/stages.py,sha256=
|
16
|
-
ddeutil/workflow/utils.py,sha256=
|
17
|
-
ddeutil/workflow/workflow.py,sha256=
|
15
|
+
ddeutil/workflow/stages.py,sha256=rQtW8W8btMavJxnseusHOH4HAv7SA_WLm9zQsCK22f8,63237
|
16
|
+
ddeutil/workflow/utils.py,sha256=zbVttaMFMRLuuBJdSJf7D9qtz8bOnQIBq-rHI3Eqy4M,7821
|
17
|
+
ddeutil/workflow/workflow.py,sha256=4jp7wm8TkSv8CXOKrCC-dlFgTP2d0OXgRHimoXnjSvY,50430
|
18
18
|
ddeutil/workflow/api/__init__.py,sha256=F53NMBWtb9IKaDWkPU5KvybGGfKAcbehgn6TLBwHuuM,21
|
19
19
|
ddeutil/workflow/api/api.py,sha256=CWtPLgOv2Jus9E7nzG5mG2Z32ZEkUK3JWQ2htZyMRpA,5244
|
20
20
|
ddeutil/workflow/api/log.py,sha256=NMTnOnsBrDB5129329xF2myLdrb-z9k1MQrmrP7qXJw,1818
|
@@ -24,8 +24,8 @@ ddeutil/workflow/api/routes/job.py,sha256=oPwBVP0Mxwxv-bGPlfmxQQ9PcVl0ev9HoPzndp
|
|
24
24
|
ddeutil/workflow/api/routes/logs.py,sha256=U6vOni3wd-ZTOwd3yVdSOpgyRmNdcgfngU5KlLM3Cww,5383
|
25
25
|
ddeutil/workflow/api/routes/schedules.py,sha256=EgUjyRGhsm6UNaMj5luh6TcY6l571sCHcla-BL1iOfY,4829
|
26
26
|
ddeutil/workflow/api/routes/workflows.py,sha256=JcDOrn1deK8ztFRcMTNATQejG6KMA7JxZLVc4QeBsP4,4527
|
27
|
-
ddeutil_workflow-0.0.
|
28
|
-
ddeutil_workflow-0.0.
|
29
|
-
ddeutil_workflow-0.0.
|
30
|
-
ddeutil_workflow-0.0.
|
31
|
-
ddeutil_workflow-0.0.
|
27
|
+
ddeutil_workflow-0.0.50.dist-info/licenses/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
|
28
|
+
ddeutil_workflow-0.0.50.dist-info/METADATA,sha256=jh7H6NtEXQ5lKiuSpZ63LAwKDboVmV-AKGSZPiATwn4,18036
|
29
|
+
ddeutil_workflow-0.0.50.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
30
|
+
ddeutil_workflow-0.0.50.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
|
31
|
+
ddeutil_workflow-0.0.50.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|