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/utils.py CHANGED
@@ -26,9 +26,11 @@ T = TypeVar("T")
26
26
  UTC: Final[ZoneInfo] = ZoneInfo("UTC")
27
27
 
28
28
 
29
- def get_dt_now(
30
- tz: ZoneInfo | None = None, offset: float = 0.0
31
- ) -> datetime: # pragma: no cov
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 tz: A ZoneInfo object for replace timezone of return datetime object.
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=(tz or UTC)) - timedelta(seconds=offset)
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 tz: A ZoneInfo object for replace timezone of return datetime object.
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.replace(second=0, microsecond=0)
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.replace(second=0, microsecond=0) + timedelta(minutes=1)
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 `md5`
129
- algorithm function if ``WORKFLOW_CORE_WORKFLOW_ID_SIMPLE_MODE`` set to
130
- false. But it will cut this hashing value length to 10 it the setting value
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 config
140
+ from .conf import dynamic
141
141
 
142
142
  if not isinstance(value, str):
143
143
  value: str = str(value)
144
144
 
145
- if config.generate_id_simple_mode:
146
- return (
147
- f"{datetime.now(tz=config.tz):%Y%m%d%H%M%S%f}T" if unique else ""
148
- ) + hash_str(f"{(value if sensitive else value.lower())}", n=10)
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"{datetime.now(tz=config.tz):%Y%m%d%H%M%S%f}T" if unique else "")
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='668931127320241228100331254567')
247
- '254567'
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
- return run_id[-num:]
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:]
@@ -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]: The latest release, "
933
- f"{release.date:%Y-%m-%d %H:%M:%S}, is not able to run "
934
- f"on this minute"
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"[JOB]: Skip job: {job_id!r}")
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"[JOB]: Start execute job: {job_id!r}")
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
- if raise_error:
1040
- raise WorkflowException(
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 | None = None
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.debug(
1325
- f"Execution non-threading does not handle case: {future} "
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.49
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=8c8KBEXeEOskazR5AlLYEjCpyi54xsoTaaqRY8pXUJY,28
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=EpqROKwAX76Wea-37hi0R_oVQ2jm2cc9ML9USxRAsak,1971
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=lviP7bFsOCJtD8S1VyJK6aaSL9Nj_vfC2Kkpe1z2Zec,12444
6
+ ddeutil/workflow/conf.py,sha256=o6RyqcjjeQXdPBZi3lMs5sSQ5aYvsUgMdJMoRYMWcN0,12492
7
7
  ddeutil/workflow/cron.py,sha256=80SijzMdDOBxTWRsiF-Fmuz7Ym7leY0XT2lzRAPGdXc,8781
8
- ddeutil/workflow/exceptions.py,sha256=uLNxzav3HRcr4vaZnvbUIF_eTR6UXXZNaxroMWFOUL4,1418
9
- ddeutil/workflow/job.py,sha256=y2q_md2nUp1jfgjaQdjDZqrHR541ENrWB__S1-Eoyss,30830
10
- ddeutil/workflow/logs.py,sha256=JghQawGd16ysf-5y7ZFtSGFY82uwgb9oMKNYy9eGI-o,26468
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=kizTEP6DY9ewDQQR17YgfrtMXRW-wF8vRzG26wzAqUM,5439
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=2vO5pdUgKHeGsS762fdYrBkWfvby_Q3Z71ptHajEp8k,55904
16
- ddeutil/workflow/utils.py,sha256=CtFUrP_4m6xaJooc9RbE4ulTBE-OkICg-MPHqzCuJ0I,7392
17
- ddeutil/workflow/workflow.py,sha256=mCpsAY0Su-pMJ_xZ-qF6lDEXn-Ih7myUODZ0ZEuyVew,50804
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.49.dist-info/licenses/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
28
- ddeutil_workflow-0.0.49.dist-info/METADATA,sha256=U847vy3oZ6ZEvngEe-9W3wA1vkKGr15UQa73v9Dsy7k,18257
29
- ddeutil_workflow-0.0.49.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
30
- ddeutil_workflow-0.0.49.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
31
- ddeutil_workflow-0.0.49.dist-info/RECORD,,
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,,