ddeutil-workflow 0.0.50__py3-none-any.whl → 0.0.52__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.
@@ -6,6 +6,9 @@
6
6
  # [x] Use dynamic config
7
7
  """A Workflow module that is the core module of this package. It keeps Release
8
8
  and Workflow Pydantic models.
9
+
10
+ I will implement timeout on the workflow execution layer only because the
11
+ main propose of this package in Workflow model.
9
12
  """
10
13
  from __future__ import annotations
11
14
 
@@ -36,7 +39,7 @@ from .__cron import CronJob, CronRunner
36
39
  from .__types import DictData, TupleStr
37
40
  from .conf import Loader, SimLoad, dynamic
38
41
  from .cron import On
39
- from .exceptions import JobException, WorkflowException
42
+ from .exceptions import JobException, UtilException, WorkflowException
40
43
  from .job import Job
41
44
  from .logs import Audit, get_audit
42
45
  from .params import Param
@@ -368,7 +371,9 @@ class Workflow(BaseModel):
368
371
  def from_conf(
369
372
  cls,
370
373
  name: str,
374
+ *,
371
375
  extras: DictData | None = None,
376
+ loader: type[Loader] = None,
372
377
  ) -> Self:
373
378
  """Create Workflow instance from the Loader object that only receive
374
379
  an input workflow name. The loader object will use this workflow name to
@@ -377,12 +382,13 @@ class Workflow(BaseModel):
377
382
  :param name: A workflow name that want to pass to Loader object.
378
383
  :param extras: An extra parameters that want to pass to Loader
379
384
  object.
385
+ :param loader: A loader class for override default loader object.
380
386
 
381
387
  :raise ValueError: If the type does not match with current object.
382
388
 
383
389
  :rtype: Self
384
390
  """
385
- loader: Loader = Loader(name, externals=(extras or {}))
391
+ loader: Loader = (loader or Loader)(name, externals=(extras or {}))
386
392
 
387
393
  # NOTE: Validate the config type match with current connection model
388
394
  if loader.type != cls.__name__:
@@ -404,6 +410,7 @@ class Workflow(BaseModel):
404
410
  path: Path,
405
411
  *,
406
412
  extras: DictData | None = None,
413
+ loader: type[Loader] = None,
407
414
  ) -> Self:
408
415
  """Create Workflow instance from the specific path. The loader object
409
416
  will use this workflow name and path to searching configuration data of
@@ -413,12 +420,13 @@ class Workflow(BaseModel):
413
420
  :param path: (Path) A config path that want to search.
414
421
  :param extras: (DictData) An extra parameters that want to override core
415
422
  config values.
423
+ :param loader: A loader class for override default loader object.
416
424
 
417
425
  :raise ValueError: If the type does not match with current object.
418
426
 
419
427
  :rtype: Self
420
428
  """
421
- loader: SimLoad = SimLoad(
429
+ loader: SimLoad = (loader or SimLoad)(
422
430
  name, conf_path=path, externals=(extras or {})
423
431
  )
424
432
  # NOTE: Validate the config type match with current connection model
@@ -636,20 +644,20 @@ class Workflow(BaseModel):
636
644
  run_id: str | None = None,
637
645
  parent_run_id: str | None = None,
638
646
  audit: type[Audit] = None,
639
- queue: ReleaseQueue | None = None,
647
+ queue: Optional[ReleaseQueue] = None,
640
648
  override_log_name: str | None = None,
641
- result: Result | None = None,
649
+ result: Optional[Result] = None,
650
+ timeout: int = 600,
642
651
  ) -> Result:
643
652
  """Release the workflow execution with overriding parameter with the
644
653
  release templating that include logical date (release date), execution
645
654
  date, or running id to the params.
646
655
 
647
656
  This method allow workflow use audit object to save the execution
648
- result to audit destination like file audit to the local `/logs`
649
- directory.
657
+ result to audit destination like file audit to the local `./logs` path.
650
658
 
651
659
  Steps:
652
- - Initialize ReleaseQueue and Release if they do not pass.
660
+ - Initialize Release and validate ReleaseQueue.
653
661
  - Create release data for pass to parameter templating function.
654
662
  - Execute this workflow with mapping release data to its parameters.
655
663
  - Writing result audit
@@ -658,15 +666,15 @@ class Workflow(BaseModel):
658
666
 
659
667
  :param release: A release datetime or Release object.
660
668
  :param params: A workflow parameter that pass to execute method.
661
- :param queue: A ReleaseQueue that use for mark complete.
662
- :param run_id: A workflow running ID for this release.
663
- :param parent_run_id: A parent workflow running ID for this release.
669
+ :param run_id: (str) A workflow running ID.
670
+ :param parent_run_id: (str) A parent workflow running ID.
664
671
  :param audit: An audit class that want to save the execution result.
665
- :param queue: A ReleaseQueue object.
666
- :param override_log_name: An override logging name that use instead
667
- the workflow name.
672
+ :param queue: (ReleaseQueue) A ReleaseQueue object.
673
+ :param override_log_name: (str) An override logging name that use
674
+ instead the workflow name.
668
675
  :param result: (Result) A result object for keeping context and status
669
676
  data.
677
+ :param timeout: (int) A workflow execution time out in second unit.
670
678
 
671
679
  :raise TypeError: If a queue parameter does not match with ReleaseQueue
672
680
  type.
@@ -683,7 +691,8 @@ class Workflow(BaseModel):
683
691
  extras=self.extras,
684
692
  )
685
693
 
686
- if queue is not None and not isinstance(queue, ReleaseQueue):
694
+ # VALIDATE: check type of queue that valid with ReleaseQueue.
695
+ if queue and not isinstance(queue, ReleaseQueue):
687
696
  raise TypeError(
688
697
  "The queue argument should be ReleaseQueue object only."
689
698
  )
@@ -693,36 +702,29 @@ class Workflow(BaseModel):
693
702
  release: Release = Release.from_dt(release, extras=self.extras)
694
703
 
695
704
  result.trace.debug(
696
- f"[RELEASE]: Start release - {name!r} : "
697
- f"{release.date:%Y-%m-%d %H:%M:%S}"
705
+ f"[RELEASE]: Start {name!r} : {release.date:%Y-%m-%d %H:%M:%S}"
698
706
  )
699
-
700
- # NOTE: Release parameters that use to templating on the schedule
701
- # config data.
702
- release_params: DictData = {
703
- "release": {
704
- "logical_date": release.date,
705
- "execute_date": datetime.now(
706
- tz=dynamic("tz", extras=self.extras)
707
- ),
708
- "run_id": result.run_id,
709
- "timezone": dynamic("tz", extras=self.extras),
710
- }
711
- }
712
-
713
- # NOTE: Execute workflow with templating params from release mapping.
714
- # The result context that return from execution method is:
715
- #
716
- # ... {"params": ..., "jobs": ...}
717
- #
718
707
  self.execute(
719
- params=param2template(params, release_params, extras=self.extras),
708
+ params=param2template(
709
+ params,
710
+ params={
711
+ "release": {
712
+ "logical_date": release.date,
713
+ "execute_date": datetime.now(
714
+ tz=dynamic("tz", extras=self.extras)
715
+ ),
716
+ "run_id": result.run_id,
717
+ "timezone": dynamic("tz", extras=self.extras),
718
+ }
719
+ },
720
+ extras=self.extras,
721
+ ),
720
722
  result=result,
721
723
  parent_run_id=result.parent_run_id,
724
+ timeout=timeout,
722
725
  )
723
726
  result.trace.debug(
724
- f"[RELEASE]: End release - {name!r} : "
725
- f"{release.date:%Y-%m-%d %H:%M:%S}"
727
+ f"[RELEASE]: End {name!r} : {release.date:%Y-%m-%d %H:%M:%S}"
726
728
  )
727
729
 
728
730
  # NOTE: Saving execution result to destination of the input audit
@@ -741,19 +743,10 @@ class Workflow(BaseModel):
741
743
  ).save(excluded=None)
742
744
  )
743
745
 
744
- # NOTE: Remove this release from running.
745
- if queue is not None:
746
+ if queue:
746
747
  queue.remove_running(release)
747
748
  queue.mark_complete(release)
748
749
 
749
- # NOTE: Remove the params key from the result context for deduplicate.
750
- # This step is prepare result context for this release method.
751
- context: DictData = result.context
752
- jobs: DictData = context.pop("jobs", {})
753
- errors: DictData = (
754
- {"errors": context.pop("errors", {})} if "errors" in context else {}
755
- )
756
-
757
750
  return result.catch(
758
751
  status=SUCCESS,
759
752
  context={
@@ -763,8 +756,7 @@ class Workflow(BaseModel):
763
756
  "logical_date": release.date,
764
757
  "release": release,
765
758
  },
766
- "outputs": {"jobs": jobs},
767
- **errors,
759
+ "outputs": {"jobs": result.context.pop("jobs", {})},
768
760
  },
769
761
  )
770
762
 
@@ -1004,6 +996,12 @@ class Workflow(BaseModel):
1004
996
  f"workflow."
1005
997
  )
1006
998
 
999
+ job: Job = self.job(name=job_id)
1000
+ if job.is_skipped(params=params):
1001
+ result.trace.info(f"[WORKFLOW]: Skip job: {job_id!r}")
1002
+ job.set_outputs(output={"skipped": True}, to=params)
1003
+ return result.catch(status=SKIP, context=params)
1004
+
1007
1005
  if event and event.is_set(): # pragma: no cov
1008
1006
  raise WorkflowException(
1009
1007
  "Workflow job was canceled from event that had set before "
@@ -1011,27 +1009,31 @@ class Workflow(BaseModel):
1011
1009
  )
1012
1010
 
1013
1011
  try:
1014
- job: Job = self.jobs[job_id]
1015
- if job.is_skipped(params=params):
1016
- result.trace.info(f"[WORKFLOW]: Skip job: {job_id!r}")
1017
- job.set_outputs(output={"SKIP": {"skipped": True}}, to=params)
1018
- else:
1019
- result.trace.info(f"[WORKFLOW]: Execute: {job_id!r}")
1020
- job.set_outputs(
1021
- job.execute(
1022
- params=params,
1023
- run_id=result.run_id,
1024
- parent_run_id=result.parent_run_id,
1025
- event=event,
1026
- ).context,
1027
- to=params,
1028
- )
1029
- except JobException as e:
1012
+ result.trace.info(f"[WORKFLOW]: Execute Job: {job_id!r}")
1013
+ rs: Result = job.execute(
1014
+ params=params,
1015
+ run_id=result.run_id,
1016
+ parent_run_id=result.parent_run_id,
1017
+ event=event,
1018
+ )
1019
+ job.set_outputs(rs.context, to=params)
1020
+ except (JobException, UtilException) as e:
1030
1021
  result.trace.error(f"[WORKFLOW]: {e.__class__.__name__}: {e}")
1031
1022
  raise WorkflowException(
1032
1023
  f"Get job execution error {job_id}: JobException: {e}"
1033
1024
  ) from None
1034
1025
 
1026
+ if rs.status == FAILED:
1027
+ error_msg: str = (
1028
+ f"Workflow job, {job.id}, failed without raise error."
1029
+ )
1030
+ return result.catch(
1031
+ status=FAILED,
1032
+ context={
1033
+ "errors": WorkflowException(error_msg).to_dict(),
1034
+ **params,
1035
+ },
1036
+ )
1035
1037
  return result.catch(status=SUCCESS, context=params)
1036
1038
 
1037
1039
  def execute(
@@ -1083,7 +1085,7 @@ class Workflow(BaseModel):
1083
1085
  extras=self.extras,
1084
1086
  )
1085
1087
 
1086
- result.trace.info(f"[WORKFLOW]: Start Execute: {self.name!r} ...")
1088
+ result.trace.info(f"[WORKFLOW]: Execute: {self.name!r} ...")
1087
1089
  if not self.jobs:
1088
1090
  result.trace.warning(
1089
1091
  f"[WORKFLOW]: {self.name!r} does not have any jobs"
@@ -1128,7 +1130,7 @@ class Workflow(BaseModel):
1128
1130
  timeout=timeout,
1129
1131
  event=event,
1130
1132
  )
1131
- except WorkflowException as e:
1133
+ except (WorkflowException, JobException) as e:
1132
1134
  status: Status = FAILED
1133
1135
  context.update({"errors": e.to_dict()})
1134
1136
 
@@ -1167,7 +1169,7 @@ class Workflow(BaseModel):
1167
1169
  "max_job_exec_timeout", f=timeout, extras=self.extras
1168
1170
  )
1169
1171
  event: Event = event or Event()
1170
- result.trace.debug(f"[WORKFLOW]: Run {self.name!r} with threading.")
1172
+ result.trace.debug(f"... Run {self.name!r} with threading.")
1171
1173
  with ThreadPoolExecutor(
1172
1174
  max_workers=dynamic("max_job_parallel", extras=self.extras),
1173
1175
  thread_name_prefix="wf_exec_threading_",
@@ -1192,7 +1194,7 @@ class Workflow(BaseModel):
1192
1194
  )
1193
1195
  elif check == SKIP: # pragma: no cov
1194
1196
  result.trace.info(f"[JOB]: Skip job: {job_id!r}")
1195
- job.set_outputs({"SKIP": {"skipped": True}}, to=context)
1197
+ job.set_outputs(output={"skipped": True}, to=context)
1196
1198
  job_queue.task_done()
1197
1199
  continue
1198
1200
 
@@ -1259,7 +1261,7 @@ class Workflow(BaseModel):
1259
1261
  "max_job_exec_timeout", f=timeout, extras=self.extras
1260
1262
  )
1261
1263
  event: Event = event or Event()
1262
- result.trace.debug(f"[WORKFLOW]: Run {self.name!r} with non-threading.")
1264
+ result.trace.debug(f"... Run {self.name!r} with non-threading.")
1263
1265
  with ThreadPoolExecutor(
1264
1266
  max_workers=1,
1265
1267
  thread_name_prefix="wf_exec_non_threading_",
@@ -1284,7 +1286,7 @@ class Workflow(BaseModel):
1284
1286
  )
1285
1287
  elif check == SKIP: # pragma: no cov
1286
1288
  result.trace.info(f"[JOB]: Skip job: {job_id!r}")
1287
- job.set_outputs({"SKIP": {"skipped": True}}, to=context)
1289
+ job.set_outputs(output={"skipped": True}, to=context)
1288
1290
  job_queue.task_done()
1289
1291
  continue
1290
1292
 
@@ -1297,7 +1299,7 @@ class Workflow(BaseModel):
1297
1299
  event=event,
1298
1300
  )
1299
1301
  time.sleep(0.025)
1300
- elif future.done():
1302
+ elif future.done() or future.cancelled():
1301
1303
  if e := future.exception():
1302
1304
  result.trace.error(f"[WORKFLOW]: {e}")
1303
1305
  raise WorkflowException(str(e))
@@ -1317,6 +1319,13 @@ class Workflow(BaseModel):
1317
1319
 
1318
1320
  if not_timeout_flag:
1319
1321
  job_queue.join()
1322
+ if future: # pragma: no cov
1323
+ if e := future.exception():
1324
+ result.trace.error(f"[WORKFLOW]: {e}")
1325
+ raise WorkflowException(str(e))
1326
+
1327
+ future.result()
1328
+
1320
1329
  return context
1321
1330
 
1322
1331
  result.trace.error(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ddeutil-workflow
3
- Version: 0.0.50
3
+ Version: 0.0.52
4
4
  Summary: Lightweight workflow orchestration
5
5
  Author-email: ddeutils <korawich.anu@gmail.com>
6
6
  License: MIT
@@ -22,8 +22,8 @@ Classifier: Programming Language :: Python :: 3.13
22
22
  Requires-Python: >=3.9.13
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
- Requires-Dist: ddeutil[checksum]>=0.4.6
26
- Requires-Dist: ddeutil-io[toml,yaml]>=0.2.10
25
+ Requires-Dist: ddeutil[checksum]>=0.4.7
26
+ Requires-Dist: ddeutil-io[toml,yaml]>=0.2.11
27
27
  Requires-Dist: pydantic==2.11.1
28
28
  Requires-Dist: python-dotenv==1.1.0
29
29
  Requires-Dist: schedule<2.0.0,==1.2.2
@@ -139,7 +139,8 @@ flowchart LR
139
139
 
140
140
  ## 📦 Installation
141
141
 
142
- This project need `ddeutil` and `ddeutil-io` extension namespace packages.
142
+ This project need `ddeutil` and `ddeutil-io` extension namespace packages to be
143
+ the base deps.
143
144
  If you want to install this package with application add-ons, you should add
144
145
  `app` in installation;
145
146
 
@@ -148,7 +149,7 @@ If you want to install this package with application add-ons, you should add
148
149
  | Python | `ddeutil-workflow` | :heavy_check_mark: |
149
150
  | FastAPI Server | `ddeutil-workflow[api]` | :heavy_check_mark: |
150
151
 
151
- ## :beers: Usage
152
+ ## 🎯 Usage
152
153
 
153
154
  This is examples that use workflow file for running common Data Engineering
154
155
  use-case.
@@ -198,12 +199,54 @@ run-py-local:
198
199
 
199
200
  # Arguments of target data that want to land.
200
201
  writing_mode: flatten
201
- aws_s3_path: my-data/open-data/${{ params.source-extract }}
202
+ aws:
203
+ path: my-data/open-data/${{ params.source-extract }}
202
204
 
203
- # This Authentication code should implement with your custom call
204
- # function. The template allow you to use environment variable.
205
- aws_access_client_id: ${AWS_ACCESS_CLIENT_ID}
206
- aws_access_client_secret: ${AWS_ACCESS_CLIENT_SECRET}
205
+ # This Authentication code should implement with your custom call
206
+ # function. The template allow you to use environment variable.
207
+ access_client_id: ${AWS_ACCESS_CLIENT_ID}
208
+ access_client_secret: ${AWS_ACCESS_CLIENT_SECRET}
209
+ ```
210
+
211
+ Before execute this workflow, you should implement caller function first.
212
+
213
+ ```text
214
+ registry-caller/
215
+ ╰─ tasks.py
216
+ ```
217
+
218
+ This function will store as module that will import from `WORKFLOW_CORE_REGISTRY_CALLER`
219
+ value (This config can override by extra parameters with `registry_caller` key).
220
+
221
+ ```python
222
+ from ddeutil.workflow import Result, tag
223
+ from ddeutil.workflow.exceptions import StageException
224
+ from pydantic import BaseModel, SecretStr
225
+
226
+ class AwsCredential(BaseModel):
227
+ path: str
228
+ access_client_id: str
229
+ access_client_secret: SecretStr
230
+
231
+ class RestAuth(BaseModel):
232
+ type: str
233
+ keys: SecretStr
234
+
235
+ @tag("requests", alias="get-api-with-oauth-to-s3")
236
+ def get_api_with_oauth_to_s3(
237
+ method: str,
238
+ url: str,
239
+ body: dict[str, str],
240
+ auth: RestAuth,
241
+ writing_node: str,
242
+ aws: AwsCredential,
243
+ result: Result,
244
+ ) -> dict[str, int]:
245
+ result.trace.info("[CALLER]: Start get data via RestAPI to S3.")
246
+ result.trace.info(f"... {method}: {url}")
247
+ if method != "post":
248
+ raise StageException(f"RestAPI does not support for {method} action.")
249
+ return {"records": 1000}
207
250
  ```
208
251
 
209
252
  The above workflow template is main executor pipeline that you want to do. If you
@@ -298,13 +341,15 @@ only.
298
341
  ## :rocket: Deployment
299
342
 
300
343
  This package able to run as an application service for receive manual trigger
301
- from the master node via RestAPI or use to be Scheduler background service
302
- like crontab job but via Python API.
344
+ from any node via RestAPI or use to be Scheduler background application
345
+ like crontab job but via Python API or FastAPI app.
303
346
 
304
347
  ### API Server
305
348
 
349
+ This server use FastAPI package to be the base application.
350
+
306
351
  ```shell
307
- (venv) $ uvicorn ddeutil.workflow.api:app \
352
+ (.venv) $ uvicorn ddeutil.workflow.api:app \
308
353
  --host 127.0.0.1 \
309
354
  --port 80 \
310
355
  --no-access-log
@@ -314,8 +359,19 @@ like crontab job but via Python API.
314
359
  > If this package already deploy, it is able to use multiprocess;
315
360
  > `uvicorn ddeutil.workflow.api:app --host 127.0.0.1 --port 80 --workers 4`
316
361
 
362
+ ### Local Schedule
363
+
364
+ > [!WARNING]
365
+ > This CLI does not implement yet.
366
+
367
+ ```shell
368
+ (.venv) $ ddeutil-workflow schedule
369
+ ```
370
+
317
371
  ### Docker Container
318
372
 
373
+ Build a Docker container from this package.
374
+
319
375
  ```shell
320
376
  $ docker build -t ddeutil-workflow:latest -f .container/Dockerfile .
321
377
  $ docker run -i ddeutil-workflow:latest ddeutil-workflow
@@ -1,20 +1,20 @@
1
- ddeutil/workflow/__about__.py,sha256=K4g7cm4iInR43bd_K3fsuGDTVpz7fbAASmKh5_jH8_U,28
1
+ ddeutil/workflow/__about__.py,sha256=MtoJJkgninIGWDAMMmKrIvqN_o2rzywSeEdJGILbStY,28
2
2
  ddeutil/workflow/__cron.py,sha256=h8rLeIUAAEB2SdZ4Jhch7LU1Yl3bbJ-iNNJ3tQ0eYVM,28095
3
- ddeutil/workflow/__init__.py,sha256=3u-yGnTyfY4BFrKqA5UGaMVe_Q4cZNODuC9qZ5meOXo,2048
3
+ ddeutil/workflow/__init__.py,sha256=noE8LNRcgq32m9OnIFcQqh0P7PXWdp-SGmvBCYIXgf4,1338
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=o6RyqcjjeQXdPBZi3lMs5sSQ5aYvsUgMdJMoRYMWcN0,12492
7
- ddeutil/workflow/cron.py,sha256=80SijzMdDOBxTWRsiF-Fmuz7Ym7leY0XT2lzRAPGdXc,8781
6
+ ddeutil/workflow/conf.py,sha256=80rgmJKFU7BlH5xTLnghGzGhE8C6LFAQykd9mjHSjo8,12528
7
+ ddeutil/workflow/cron.py,sha256=WS2MInn0Sp5DKlZDZH5VFZ5AA0Q3_AnBnYEU4lZSv4I,9779
8
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
- ddeutil/workflow/params.py,sha256=xCtFEh0-G-G-f8y_SXxyf31bU6Ox5p5Z-WbBFXrjy8M,9960
9
+ ddeutil/workflow/job.py,sha256=l2YpbUQoXYQ46S7Ne3Ttefc_jKBy14q3_PKf3k_cmNU,35213
10
+ ddeutil/workflow/logs.py,sha256=rsoBrUGQrooou18fg2yvPsB8NOaXnUA5ThQpBr_WVMg,26598
11
+ ddeutil/workflow/params.py,sha256=FKY4Oo1Ze4QZKRfAk7rqKsi44YaJQAbqAtXM6vlO2hI,11392
12
12
  ddeutil/workflow/result.py,sha256=27nPQq9CETLCVczv4vvFEF9w2TllHZ_ROfyDoLFxRWM,5647
13
- ddeutil/workflow/reusables.py,sha256=hIpehea6J4OWeXX55kjYzo-c9-_Cc0YRwLRRbcaUkZs,17539
14
- ddeutil/workflow/scheduler.py,sha256=F783QaJfPg8tvYyvJvkwl8Sa42vsJzj6BzzROZFvm9I,28153
15
- ddeutil/workflow/stages.py,sha256=rQtW8W8btMavJxnseusHOH4HAv7SA_WLm9zQsCK22f8,63237
13
+ ddeutil/workflow/reusables.py,sha256=iXcS7Gg-71qVX4ln0ILTDx03cTtUnj_rNoXHTVdVrxc,17636
14
+ ddeutil/workflow/scheduler.py,sha256=4G5AogkmnsTKe7jKYSfU35qjubR82WQ8CLtEe9kqPTE,28304
15
+ ddeutil/workflow/stages.py,sha256=R6yVp5-9xw6fslY7oE2pNzpMmgHiBN-2AVG2hCc5-QI,69241
16
16
  ddeutil/workflow/utils.py,sha256=zbVttaMFMRLuuBJdSJf7D9qtz8bOnQIBq-rHI3Eqy4M,7821
17
- ddeutil/workflow/workflow.py,sha256=4jp7wm8TkSv8CXOKrCC-dlFgTP2d0OXgRHimoXnjSvY,50430
17
+ ddeutil/workflow/workflow.py,sha256=2ZBNW3-vcP8bpKrK184wSCukq3wpT6G0z25Su5bapR0,50832
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.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,,
27
+ ddeutil_workflow-0.0.52.dist-info/licenses/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
28
+ ddeutil_workflow-0.0.52.dist-info/METADATA,sha256=DGXGAp4BlgOfkP-CWnMFkl8_ZwvoAtMZqB5uUpcoEBs,19425
29
+ ddeutil_workflow-0.0.52.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
30
+ ddeutil_workflow-0.0.52.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
31
+ ddeutil_workflow-0.0.52.dist-info/RECORD,,