ddeutil-workflow 0.0.30__py3-none-any.whl → 0.0.32__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.
@@ -29,7 +29,8 @@ from concurrent.futures import (
29
29
  )
30
30
  from dataclasses import field
31
31
  from datetime import datetime, timedelta
32
- from functools import total_ordering
32
+ from enum import Enum
33
+ from functools import partial, total_ordering
33
34
  from heapq import heappop, heappush
34
35
  from queue import Queue
35
36
  from textwrap import dedent
@@ -53,21 +54,33 @@ from .utils import (
53
54
  cut_id,
54
55
  gen_id,
55
56
  get_dt_now,
56
- wait_a_minute,
57
+ reach_next_minute,
58
+ wait_to_next_minute,
57
59
  )
58
60
 
59
61
  logger = get_logger("ddeutil.workflow")
60
62
 
61
63
  __all__: TupleStr = (
62
- "Workflow",
63
64
  "Release",
64
65
  "ReleaseQueue",
66
+ "ReleaseType",
67
+ "Workflow",
65
68
  "WorkflowTask",
66
69
  )
67
70
 
68
71
 
72
+ class ReleaseType(str, Enum):
73
+ """Release Type Enum support the type field on the Release dataclass."""
74
+
75
+ DEFAULT: str = "manual"
76
+ TASK: str = "task"
77
+ POKE: str = "poking"
78
+
79
+
69
80
  @total_ordering
70
- @dataclass(config=ConfigDict(arbitrary_types_allowed=True))
81
+ @dataclass(
82
+ config=ConfigDict(arbitrary_types_allowed=True, use_enum_values=True)
83
+ )
71
84
  class Release:
72
85
  """Release Pydantic dataclass object that use for represent
73
86
  the release data that use with the `workflow.release` method."""
@@ -76,7 +89,7 @@ class Release:
76
89
  offset: float
77
90
  end_date: datetime
78
91
  runner: CronRunner
79
- type: str
92
+ type: ReleaseType = field(default=ReleaseType.DEFAULT)
80
93
 
81
94
  def __repr__(self) -> str:
82
95
  return repr(f"{self.date:%Y-%m-%d %H:%M:%S}")
@@ -94,13 +107,16 @@ class Release:
94
107
  """
95
108
  if isinstance(dt, str):
96
109
  dt: datetime = datetime.fromisoformat(dt)
110
+ elif not isinstance(dt, datetime):
111
+ raise TypeError(
112
+ "The `from_dt` need argument type be str or datetime only."
113
+ )
97
114
 
98
115
  return cls(
99
116
  date=dt,
100
117
  offset=0,
101
118
  end_date=dt + timedelta(days=1),
102
119
  runner=CronJob("* * * * *").schedule(dt.replace(tzinfo=config.tz)),
103
- type="manual",
104
120
  )
105
121
 
106
122
  def __eq__(self, other: Release | datetime) -> bool:
@@ -174,13 +190,6 @@ class ReleaseQueue:
174
190
 
175
191
  :rtype: bool
176
192
  """
177
- # NOTE: Old logic to peeking the first release from waiting queue.
178
- #
179
- # first_value: Release = heappop(self.queue)
180
- # heappush(self.queue, first_value)
181
- #
182
- # return first_value
183
- #
184
193
  return self.queue[0]
185
194
 
186
195
  def check_queue(self, value: Release | datetime) -> bool:
@@ -477,7 +486,7 @@ class Workflow(BaseModel):
477
486
  *,
478
487
  run_id: str | None = None,
479
488
  log: type[Log] = None,
480
- queue: ReleaseQueue | list[datetime] | list[Release] | None = None,
489
+ queue: ReleaseQueue | None = None,
481
490
  override_log_name: str | None = None,
482
491
  ) -> Result:
483
492
  """Release the workflow execution with overriding parameter with the
@@ -487,7 +496,7 @@ class Workflow(BaseModel):
487
496
  This method allow workflow use log object to save the execution
488
497
  result to log destination like file log to the local `/logs` directory.
489
498
 
490
- :Steps:
499
+ Steps:
491
500
  - Initialize ReleaseQueue and Release if they do not pass.
492
501
  - Create release data for pass to parameter templating function.
493
502
  - Execute this workflow with mapping release data to its parameters.
@@ -497,7 +506,7 @@ class Workflow(BaseModel):
497
506
 
498
507
  :param release: A release datetime or Release object.
499
508
  :param params: A workflow parameter that pass to execute method.
500
- :param queue: A list of release time that already queue.
509
+ :param queue: A ReleaseQueue that use for mark complete.
501
510
  :param run_id: A workflow running ID for this release.
502
511
  :param log: A log class that want to save the execution result.
503
512
  :param queue: A ReleaseQueue object.
@@ -510,13 +519,14 @@ class Workflow(BaseModel):
510
519
  name: str = override_log_name or self.name
511
520
  run_id: str = run_id or gen_id(name, unique=True)
512
521
  rs_release: Result = Result(run_id=run_id)
513
- rs_release_type: str = "release"
514
522
 
515
- # VALIDATE: Change queue value to ReleaseQueue object.
516
- if queue is None or isinstance(queue, list):
517
- queue: ReleaseQueue = ReleaseQueue.from_list(queue)
523
+ if queue is not None and not isinstance(queue, ReleaseQueue):
524
+ raise TypeError(
525
+ "The queue argument should be ReleaseQueue object only."
526
+ )
518
527
 
519
528
  # VALIDATE: Change release value to Release object.
529
+ rs_release_type: str = "release"
520
530
  if isinstance(release, datetime):
521
531
  rs_release_type: str = "datetime"
522
532
  release: Release = Release.from_dt(release)
@@ -548,24 +558,26 @@ class Workflow(BaseModel):
548
558
  )
549
559
 
550
560
  rs.set_parent_run_id(run_id)
551
- rs_log: Log = log.model_validate(
552
- {
553
- "name": name,
554
- "release": release.date,
555
- "type": release.type,
556
- "context": rs.context,
557
- "parent_run_id": rs.parent_run_id,
558
- "run_id": rs.run_id,
559
- }
560
- )
561
561
 
562
562
  # NOTE: Saving execution result to destination of the input log object.
563
563
  logger.debug(f"({cut_id(run_id)}) [LOG]: Writing log: {name!r}.")
564
- rs_log.save(excluded=None)
564
+ (
565
+ log.model_validate(
566
+ {
567
+ "name": name,
568
+ "release": release.date,
569
+ "type": release.type,
570
+ "context": rs.context,
571
+ "parent_run_id": rs.parent_run_id,
572
+ "run_id": rs.run_id,
573
+ }
574
+ ).save(excluded=None)
575
+ )
565
576
 
566
577
  # NOTE: Remove this release from running.
567
- queue.remove_running(release)
568
- queue.mark_complete(release)
578
+ if queue is not None:
579
+ queue.remove_running(release)
580
+ queue.mark_complete(release)
569
581
 
570
582
  # NOTE: Remove the params key from the result context for deduplicate.
571
583
  context: dict[str, Any] = rs.context
@@ -594,8 +606,16 @@ class Workflow(BaseModel):
594
606
  *,
595
607
  force_run: bool = False,
596
608
  ) -> ReleaseQueue:
597
- """Generate queue of datetime from the cron runner that initialize from
598
- the on field. with offset value.
609
+ """Generate Release from all on values from the on field and store them
610
+ to the ReleaseQueue object.
611
+
612
+ Steps:
613
+ - For-loop all the on value in the on field.
614
+ - Create Release object from the current date that not reach the end
615
+ date.
616
+ - Check this release do not store on the release queue object.
617
+ Generate the next date if it exists.
618
+ - Push this release to the release queue
599
619
 
600
620
  :param offset: An offset in second unit for time travel.
601
621
  :param end_date: An end datetime object.
@@ -621,7 +641,7 @@ class Workflow(BaseModel):
621
641
  offset=offset,
622
642
  end_date=end_date,
623
643
  runner=runner,
624
- type="poking",
644
+ type=ReleaseType.POKE,
625
645
  )
626
646
 
627
647
  while queue.check_queue(workflow_release) or (
@@ -633,7 +653,7 @@ class Workflow(BaseModel):
633
653
  offset=offset,
634
654
  end_date=end_date,
635
655
  runner=runner,
636
- type="poking",
656
+ type=ReleaseType.POKE,
637
657
  )
638
658
 
639
659
  if runner.date > end_date:
@@ -662,6 +682,9 @@ class Workflow(BaseModel):
662
682
  This method will observe its schedule that nearing to run with the
663
683
  ``self.release()`` method.
664
684
 
685
+ The limitation of this method is not allow run a date that less
686
+ than the current date.
687
+
665
688
  :param start_date: A start datetime object.
666
689
  :param params: A parameters that want to pass to the release method.
667
690
  :param run_id: A workflow running ID for this poke.
@@ -678,6 +701,12 @@ class Workflow(BaseModel):
678
701
  log: type[Log] = log or get_log()
679
702
  run_id: str = run_id or gen_id(self.name, unique=True)
680
703
 
704
+ # VALIDATE: Check the periods value should gather than 0.
705
+ if periods <= 0:
706
+ raise WorkflowException(
707
+ "The period of poking should be int and grater or equal than 1."
708
+ )
709
+
681
710
  # NOTE: If this workflow does not set the on schedule, it will return
682
711
  # empty result.
683
712
  if len(self.on) == 0:
@@ -687,23 +716,25 @@ class Workflow(BaseModel):
687
716
  )
688
717
  return []
689
718
 
690
- if periods <= 0:
691
- raise WorkflowException(
692
- "The period of poking should be int and grater or equal than 1."
693
- )
719
+ # NOTE: Create the current date that change microsecond to 0
720
+ current_date: datetime = datetime.now(tz=config.tz).replace(
721
+ microsecond=0
722
+ )
694
723
 
695
724
  # NOTE: Create start_date and offset variables.
696
- current_date: datetime = datetime.now(tz=config.tz)
697
-
698
725
  if start_date and start_date <= current_date:
699
- start_date = start_date.replace(tzinfo=config.tz)
726
+ start_date = start_date.replace(tzinfo=config.tz).replace(
727
+ microsecond=0
728
+ )
700
729
  offset: float = (current_date - start_date).total_seconds()
701
730
  else:
731
+ # NOTE: Force change start date if it gathers than the current date,
732
+ # or it does not pass to this method.
702
733
  start_date: datetime = current_date
703
734
  offset: float = 0
704
735
 
705
- # NOTE: End date is using to stop generate queue with an input periods
706
- # value.
736
+ # NOTE: The end date is using to stop generate queue with an input
737
+ # periods value.
707
738
  end_date: datetime = start_date + timedelta(minutes=periods)
708
739
 
709
740
  logger.info(
@@ -715,17 +746,17 @@ class Workflow(BaseModel):
715
746
  results: list[Result] = []
716
747
 
717
748
  # NOTE: Create empty ReleaseQueue object.
718
- wf_queue: ReleaseQueue = ReleaseQueue()
749
+ q: ReleaseQueue = ReleaseQueue()
719
750
 
720
- # NOTE: Make queue to the workflow queue object.
721
- self.queue(
722
- offset,
723
- end_date=end_date,
724
- queue=wf_queue,
725
- log=log,
726
- force_run=force_run,
751
+ # NOTE: Create reusable partial function and add Release to the release
752
+ # queue object.
753
+ partial_queue = partial(
754
+ self.queue, offset, end_date, log=log, force_run=force_run
727
755
  )
728
- if not wf_queue.is_queued:
756
+ partial_queue(q)
757
+
758
+ # NOTE: Return the empty result if it does not have any Release.
759
+ if not q.is_queued:
729
760
  logger.info(
730
761
  f"({cut_id(run_id)}) [POKING]: {self.name!r} does not have "
731
762
  f"any queue."
@@ -741,34 +772,27 @@ class Workflow(BaseModel):
741
772
 
742
773
  futures: list[Future] = []
743
774
 
744
- while wf_queue.is_queued:
775
+ while q.is_queued:
745
776
 
746
- # NOTE: Pop the latest Release object from queue.
747
- release: Release = heappop(wf_queue.queue)
777
+ # NOTE: Pop the latest Release object from the release queue.
778
+ release: Release = heappop(q.queue)
748
779
 
749
- if (
750
- release.date - get_dt_now(tz=config.tz, offset=offset)
751
- ).total_seconds() > 60:
780
+ if reach_next_minute(release.date, tz=config.tz, offset=offset):
752
781
  logger.debug(
753
- f"({cut_id(run_id)}) [POKING]: Wait because the latest "
754
- f"release has diff time more than 60 seconds ..."
782
+ f"({cut_id(run_id)}) [POKING]: The latest release, "
783
+ f"{release.date:%Y-%m-%d %H:%M:%S}, is not able to run "
784
+ f"on this minute"
755
785
  )
756
- heappush(wf_queue.queue, release)
757
- wait_a_minute(get_dt_now(tz=config.tz, offset=offset))
786
+ heappush(q.queue, release)
787
+ wait_to_next_minute(get_dt_now(tz=config.tz, offset=offset))
758
788
 
759
789
  # WARNING: I already call queue poking again because issue
760
790
  # about the every minute crontab.
761
- self.queue(
762
- offset,
763
- end_date,
764
- queue=wf_queue,
765
- log=log,
766
- force_run=force_run,
767
- )
791
+ partial_queue(q)
768
792
  continue
769
793
 
770
794
  # NOTE: Push the latest Release to the running queue.
771
- heappush(wf_queue.running, release)
795
+ heappush(q.running, release)
772
796
 
773
797
  futures.append(
774
798
  executor.submit(
@@ -776,17 +800,11 @@ class Workflow(BaseModel):
776
800
  release=release,
777
801
  params=params,
778
802
  log=log,
779
- queue=wf_queue,
803
+ queue=q,
780
804
  )
781
805
  )
782
806
 
783
- self.queue(
784
- offset,
785
- end_date,
786
- queue=wf_queue,
787
- log=log,
788
- force_run=force_run,
789
- )
807
+ partial_queue(q)
790
808
 
791
809
  # WARNING: This poking method does not allow to use fail-fast
792
810
  # logic to catching parallel execution result.
@@ -1148,20 +1166,31 @@ class WorkflowTask:
1148
1166
  release: datetime | Release | None = None,
1149
1167
  run_id: str | None = None,
1150
1168
  log: type[Log] = None,
1151
- queue: ReleaseQueue | list[datetime] | list[Release] | None = None,
1169
+ queue: ReleaseQueue | None = None,
1152
1170
  ) -> Result:
1153
1171
  """Release the workflow task data.
1154
1172
 
1155
1173
  :param release: A release datetime or Release object.
1156
1174
  :param run_id: A workflow running ID for this release.
1157
1175
  :param log: A log class that want to save the execution result.
1158
- :param queue: A ReleaseQueue object.
1176
+ :param queue: A ReleaseQueue object that use to mark complete.
1159
1177
 
1160
1178
  :rtype: Result
1161
1179
  """
1162
1180
  log: type[Log] = log or get_log()
1163
1181
 
1164
1182
  if release is None:
1183
+
1184
+ if queue is None:
1185
+ raise ValueError(
1186
+ "If pass None release value, you should to pass the queue"
1187
+ "for generate this release."
1188
+ )
1189
+ elif not isinstance(queue, ReleaseQueue):
1190
+ raise TypeError(
1191
+ "The queue argument should be ReleaseQueue object only."
1192
+ )
1193
+
1165
1194
  if queue.check_queue(self.runner.date):
1166
1195
  release = self.runner.next
1167
1196
 
@@ -1170,6 +1199,7 @@ class WorkflowTask:
1170
1199
  else:
1171
1200
  release = self.runner.date
1172
1201
 
1202
+ # NOTE: Call the workflow release method.
1173
1203
  return self.workflow.release(
1174
1204
  release=release,
1175
1205
  params=self.values,
@@ -1186,8 +1216,9 @@ class WorkflowTask:
1186
1216
  log: type[Log],
1187
1217
  *,
1188
1218
  force_run: bool = False,
1189
- ):
1190
- """Generate Release to ReleaseQueue object.
1219
+ ) -> ReleaseQueue:
1220
+ """Generate Release from the runner field and store it to the
1221
+ ReleaseQueue object.
1191
1222
 
1192
1223
  :param end_date: An end datetime object.
1193
1224
  :param queue: A workflow queue object.
@@ -1205,7 +1236,7 @@ class WorkflowTask:
1205
1236
  offset=0,
1206
1237
  end_date=end_date,
1207
1238
  runner=self.runner,
1208
- type="task",
1239
+ type=ReleaseType.TASK,
1209
1240
  )
1210
1241
 
1211
1242
  while queue.check_queue(workflow_release) or (
@@ -1217,7 +1248,7 @@ class WorkflowTask:
1217
1248
  offset=0,
1218
1249
  end_date=end_date,
1219
1250
  runner=self.runner,
1220
- type="task",
1251
+ type=ReleaseType.TASK,
1221
1252
  )
1222
1253
 
1223
1254
  if self.runner.date > end_date:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ddeutil-workflow
3
- Version: 0.0.30
3
+ Version: 0.0.32
4
4
  Summary: Lightweight workflow orchestration
5
5
  Author-email: ddeutils <korawich.anu@gmail.com>
6
6
  License: MIT
@@ -110,7 +110,7 @@ flowchart LR
110
110
  E -.->|read| G
111
111
  ```
112
112
 
113
- > [!NOTE]
113
+ > [!WARNING]
114
114
  > _Disclaimer_: I inspire the dynamic statement from the [**GitHub Action**](https://github.com/features/actions)
115
115
  > with `.yml` files and all configs file from several data orchestration framework
116
116
  > tools from my experience on Data Engineer. :grimacing:
@@ -192,7 +192,17 @@ The above workflow template is main executor pipeline that you want to do. If yo
192
192
  want to schedule this workflow, you want to dynamic its parameters change base on
193
193
  execution time such as `run-date` should change base on that workflow running date.
194
194
 
195
- So, this package provide the `Schedule` template for this action.
195
+ ```python
196
+ from ddeutil.workflow import Workflow, Result
197
+
198
+ workflow: Workflow = Workflow.from_loader('run-py-local')
199
+ result: Result = workflow.execute(
200
+ params={"source-extract": "USD-THB", "asat-dt": "2024-01-01"}
201
+ )
202
+ ```
203
+
204
+ So, this package provide the `Schedule` template for this action, and you can dynamic
205
+ pass the parameters for changing align with that running time by the `release` prefix.
196
206
 
197
207
  ```yaml
198
208
  schedule-run-local-wf:
@@ -210,6 +220,20 @@ schedule-run-local-wf:
210
220
  asat-dt: "${{ release.logical_date }}"
211
221
  ```
212
222
 
223
+ The main method of the `Schedule` model that use to running is `pending`. If you
224
+ do not pass the `stop` date on this method, it will use config with `WORKFLOW_APP_STOP_BOUNDARY_DELTA`
225
+ key for generate this stop date.
226
+
227
+ ```python
228
+ from ddeutil.workflow import Schedule
229
+
230
+ (
231
+ Schedule
232
+ .from_loader("schedule-run-local-wf")
233
+ .pending(stop=None)
234
+ )
235
+ ```
236
+
213
237
  ## :cookie: Configuration
214
238
 
215
239
  The main configuration that use to dynamic changing this workflow engine for your
@@ -0,0 +1,25 @@
1
+ ddeutil/workflow/__about__.py,sha256=xnsfT_lwFz-qKzFIHbOUGMgvgpOAAANig-X3brDri44,28
2
+ ddeutil/workflow/__cron.py,sha256=3i-wmjTlh0ADCzN9pLKaWHzJkXzC72aIBmVEQSbyCCE,26895
3
+ ddeutil/workflow/__init__.py,sha256=pRIZIGwC7Xs8Ur7-jHPIAMLriD5If9zOPc-ZmKZS2XQ,1678
4
+ ddeutil/workflow/__types.py,sha256=CK1jfzyHP9P-MB0ElhpJZ59ZFGJC9MkQuAop5739_9k,4304
5
+ ddeutil/workflow/conf.py,sha256=6yGbSi69lsccYgnrwTzdjdPhU54hUop2e1GjBNres08,17663
6
+ ddeutil/workflow/cron.py,sha256=j8EeoHst70toRfnD_frix41vrI-eLYVJkZ9yeJtpfnI,8871
7
+ ddeutil/workflow/exceptions.py,sha256=5ghT443VLq0IeU87loHNEqqrrrctklP7YfxwJ51ImWU,949
8
+ ddeutil/workflow/hook.py,sha256=MgZFlTGvaRSBrTouZGlxwYpKQoKDOT26PNhESeL3LY0,5469
9
+ ddeutil/workflow/job.py,sha256=siph5FBU5Ah2CHWZyXotHJKMemFWQXSBqSdwk8b_6p8,24366
10
+ ddeutil/workflow/params.py,sha256=LKR7jXyxTb5NVrFav_fl2y9xo3p7qL1S9h-i6CtvNwE,5851
11
+ ddeutil/workflow/result.py,sha256=8LItqF-Xe6pAAWkAsY_QFkKBOA0fEBh97I2og3CZsPc,3409
12
+ ddeutil/workflow/scheduler.py,sha256=r26ZKpx0kuaL0hMJ39tIyZaeFMwhcwitUzBh-ge2ZJg,24394
13
+ ddeutil/workflow/stage.py,sha256=vUne9aUZX7G8K5BkBwirAjXSVwVvAoBt8HXbqUMEgns,24329
14
+ ddeutil/workflow/templates.py,sha256=A0JgZFGkBv-AX-EskZj656nG5zFd3j1PpLpyXihf6Xg,10967
15
+ ddeutil/workflow/utils.py,sha256=rTDQKaaber7cRqzJjWpCP9OTbarti1UMKdLgH6VRjFM,6709
16
+ ddeutil/workflow/workflow.py,sha256=ET1otR5VcfnOMoNiW7EMb1_wIaxNw9yWsBXS5kVWG9s,43428
17
+ ddeutil/workflow/api/__init__.py,sha256=F53NMBWtb9IKaDWkPU5KvybGGfKAcbehgn6TLBwHuuM,21
18
+ ddeutil/workflow/api/api.py,sha256=AwcUkE6S2Txz4hZOD10TsUkuFiKQ47rpMYMYpl7ssis,3954
19
+ ddeutil/workflow/api/repeat.py,sha256=zyvsrXKk-3-_N8ZRZSki0Mueshugum2jtqctEOp9QSc,4927
20
+ ddeutil/workflow/api/route.py,sha256=A9YPfchPNRyN6ev5sg1Z12zSi8JEQ6yZEea0EFPQp8k,8591
21
+ ddeutil_workflow-0.0.32.dist-info/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
22
+ ddeutil_workflow-0.0.32.dist-info/METADATA,sha256=FYn_uM88ehiyBBZto8ECxmWHwBPtFGHAOcBPkaRBNFU,15552
23
+ ddeutil_workflow-0.0.32.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
24
+ ddeutil_workflow-0.0.32.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
25
+ ddeutil_workflow-0.0.32.dist-info/RECORD,,
@@ -1,25 +0,0 @@
1
- ddeutil/workflow/__about__.py,sha256=zSy9Xk11PPZkgJ5Db1_kZp2yzt5inkEHVJWwFHPWlmk,28
2
- ddeutil/workflow/__cron.py,sha256=uA8XcbY_GwA9rJSHaHUaXaJyGDObJN0ZeYlJSinL8y8,26880
3
- ddeutil/workflow/__init__.py,sha256=dghn2lFl3Own4Pyq7SFHu-FMymOgLontJ6aCfxea9h4,1606
4
- ddeutil/workflow/__types.py,sha256=CK1jfzyHP9P-MB0ElhpJZ59ZFGJC9MkQuAop5739_9k,4304
5
- ddeutil/workflow/conf.py,sha256=7lj_Im9jsa95fWUo19Q4-ZAcHa8Pu1HW-vaLgvrjNUM,17559
6
- ddeutil/workflow/cron.py,sha256=OLgniUxmrn65gzckk-uTmE2Pk1enJJyjYUKVeBbDQz0,7522
7
- ddeutil/workflow/exceptions.py,sha256=XUnpJSuxOyataClP0w_gpYjzn-NIwZK2BHro-J7Yw24,895
8
- ddeutil/workflow/hook.py,sha256=MgZFlTGvaRSBrTouZGlxwYpKQoKDOT26PNhESeL3LY0,5469
9
- ddeutil/workflow/job.py,sha256=XcewyALsLYYq94ycF6mkj3Ydr6if683z7t1oBqEVInE,24290
10
- ddeutil/workflow/params.py,sha256=svCjmFgEhim8yFJVjZhFmKP8JqTDHQ5EPhwJHVuDGno,5289
11
- ddeutil/workflow/result.py,sha256=k4pcj5KjbEcEPymsEUXeGY4gyLMfPkMTO6YDrAtfk7Q,3408
12
- ddeutil/workflow/scheduler.py,sha256=f3d7c5SVgY5Q1JsHQ6cH513CJmJkh4l8YcKAGYudJRc,20426
13
- ddeutil/workflow/stage.py,sha256=wn8CARTvFJY4ZK1SwjzH8sKoMRz_eIeSGUMgnDWNi6g,24031
14
- ddeutil/workflow/templates.py,sha256=bVU_8gnMQmdhhw3W28ZqwmpEaOx10Nx_aauqiLS0lqg,10807
15
- ddeutil/workflow/utils.py,sha256=8LTqpvRPfrEYxsxhwszk6GKkyjrswxnwF3r_9vE8szw,6059
16
- ddeutil/workflow/workflow.py,sha256=ZLbG-K2gSNAsDGiHPjbtJd7rsEFf6jfVGAVB_9jpy84,42103
17
- ddeutil/workflow/api/__init__.py,sha256=F53NMBWtb9IKaDWkPU5KvybGGfKAcbehgn6TLBwHuuM,21
18
- ddeutil/workflow/api/api.py,sha256=Ma9R8yuQAhowG_hox-k53swFsf5IAvheEnSxNQ-8DaQ,4039
19
- ddeutil/workflow/api/repeat.py,sha256=zyvsrXKk-3-_N8ZRZSki0Mueshugum2jtqctEOp9QSc,4927
20
- ddeutil/workflow/api/route.py,sha256=v96jNbgjM1cJ2MpVSRWs2kgRqF8DQElEBdRZrVFEpEw,8578
21
- ddeutil_workflow-0.0.30.dist-info/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
22
- ddeutil_workflow-0.0.30.dist-info/METADATA,sha256=zbVHOL41qpFRG83SacZVYK9tS2JRTCM61cpnSXty9LU,14868
23
- ddeutil_workflow-0.0.30.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
24
- ddeutil_workflow-0.0.30.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
25
- ddeutil_workflow-0.0.30.dist-info/RECORD,,