ddeutil-workflow 0.0.34__py3-none-any.whl → 0.0.36__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.
@@ -14,8 +14,8 @@ use-case, and it does not worry when I want to create a new one.
14
14
 
15
15
  Execution --> Ok --> Result with 0
16
16
 
17
- --> Error --> Result with 1 (if env var was set)
18
- --> Raise StageException(...)
17
+ --> Error ┬-> Result with 1 (if env var was set)
18
+ ╰-> Raise StageException(...)
19
19
 
20
20
  On the context I/O that pass to a stage object at execute process. The
21
21
  execute method receives a `params={"params": {...}}` value for mapping to
@@ -42,7 +42,7 @@ from pydantic.functional_validators import model_validator
42
42
  from typing_extensions import Self
43
43
 
44
44
  from .__types import DictData, DictStr, TupleStr
45
- from .call import TagFunc, extract_call
45
+ from .caller import TagFunc, extract_call
46
46
  from .conf import config, get_logger
47
47
  from .exceptions import StageException
48
48
  from .result import Result, Status
@@ -174,15 +174,12 @@ class BaseStage(BaseModel, ABC):
174
174
 
175
175
  :rtype: Result
176
176
  """
177
- if result is None:
178
- result: Result = Result(
179
- run_id=(
180
- run_id or gen_id(self.name + (self.id or ""), unique=True)
181
- ),
182
- parent_run_id=parent_run_id,
183
- )
184
- elif parent_run_id:
185
- result.set_parent_run_id(parent_run_id)
177
+ result: Result = Result.construct_with_rs_or_id(
178
+ result,
179
+ run_id=run_id,
180
+ parent_run_id=parent_run_id,
181
+ id_logic=(self.name + (self.id or "")),
182
+ )
186
183
 
187
184
  try:
188
185
  return self.execute(params, result=result)
@@ -736,6 +733,26 @@ Stage = Union[
736
733
 
737
734
  # TODO: Not implement this stages yet
738
735
  class ParallelStage(BaseStage): # pragma: no cov
736
+ """Parallel execution stage that execute child stages with parallel.
737
+
738
+ Data Validate:
739
+ >>> stage = {
740
+ ... "name": "Parallel stage execution.",
741
+ ... "parallel": [
742
+ ... {
743
+ ... "name": "Echo first stage",
744
+ ... "echo": "Start run with branch 1",
745
+ ... "sleep": 3,
746
+ ... },
747
+ ... {
748
+ ... "name": "Echo second stage",
749
+ ... "echo": "Start run with branch 2",
750
+ ... "sleep": 1,
751
+ ... },
752
+ ... ]
753
+ ... }
754
+ """
755
+
739
756
  parallel: list[Stage]
740
757
  max_parallel_core: int = Field(default=2)
741
758
 
@@ -746,6 +763,22 @@ class ParallelStage(BaseStage): # pragma: no cov
746
763
 
747
764
  # TODO: Not implement this stages yet
748
765
  class ForEachStage(BaseStage): # pragma: no cov
766
+ """For-Each execution stage that execute child stages with an item in list of
767
+ item values.
768
+
769
+ Data Validate:
770
+ >>> stage = {
771
+ ... "name": "For-each stage execution",
772
+ ... "foreach": [1, 2, 3]
773
+ ... "stages": [
774
+ ... {
775
+ ... "name": "Echo stage",
776
+ ... "echo": "Start run with item {{ item }}"
777
+ ... },
778
+ ... ],
779
+ ... }
780
+ """
781
+
749
782
  foreach: list[str]
750
783
  stages: list[Stage]
751
784
 
@@ -756,9 +789,30 @@ class ForEachStage(BaseStage): # pragma: no cov
756
789
 
757
790
  # TODO: Not implement this stages yet
758
791
  class HookStage(BaseStage): # pragma: no cov
759
- foreach: list[str]
760
- stages: list[Stage]
792
+ hook: str
793
+ args: DictData
794
+ callback: str
795
+
796
+ def execute(
797
+ self, params: DictData, *, result: Result | None = None
798
+ ) -> Result: ...
799
+
800
+
801
+ # TODO: Not implement this stages yet
802
+ class DockerStage(BaseStage): # pragma: no cov
803
+ image: str
804
+ env: DictData = Field(default_factory=dict)
761
805
 
762
806
  def execute(
763
807
  self, params: DictData, *, result: Result | None = None
764
808
  ) -> Result: ...
809
+
810
+
811
+ # TODO: Not implement this stages yet
812
+ class VirtualPyStage(PyStage): # pragma: no cov
813
+ """Python Virtual Environment stage execution."""
814
+
815
+ run: str
816
+ vars: DictData
817
+
818
+ def create_py_file(self, py: str, run_id: str | None): ...
ddeutil/workflow/utils.py CHANGED
@@ -9,7 +9,7 @@ import logging
9
9
  import stat
10
10
  import time
11
11
  from collections.abc import Iterator, Mapping
12
- from datetime import datetime, timedelta
12
+ from datetime import date, datetime, timedelta
13
13
  from hashlib import md5
14
14
  from inspect import isfunction
15
15
  from itertools import chain, islice, product
@@ -39,6 +39,12 @@ def get_dt_now(
39
39
  return datetime.now(tz=(tz or UTC)) - timedelta(seconds=offset)
40
40
 
41
41
 
42
+ def get_d_now(
43
+ tz: ZoneInfo | None = None, offset: float = 0.0
44
+ ) -> date: # pragma: no cov
45
+ return (datetime.now(tz=(tz or UTC)) - timedelta(seconds=offset)).date()
46
+
47
+
42
48
  def get_diff_sec(
43
49
  dt: datetime, tz: ZoneInfo | None = None, offset: float = 0.0
44
50
  ) -> int: # pragma: no cov
@@ -3,21 +3,7 @@
3
3
  # Licensed under the MIT License. See LICENSE in the project root for
4
4
  # license information.
5
5
  # ------------------------------------------------------------------------------
6
- """The main schedule running is ``workflow_runner`` function that trigger the
7
- multiprocess of ``workflow_control`` function for listing schedules on the
8
- config by ``Loader.finds(Schedule)``.
9
-
10
- The ``workflow_control`` is the scheduler function that release 2 schedule
11
- functions; ``workflow_task``, and ``workflow_monitor``.
12
-
13
- ``workflow_control`` --- Every minute at :02 --> ``workflow_task``
14
-
15
- --- Every 5 minutes --> ``workflow_monitor``
16
-
17
- The ``workflow_task`` will run ``task.release`` method in threading object
18
- for multithreading strategy. This ``release`` method will run only one crontab
19
- value with the on field.
20
- """
6
+ """A Workflow module."""
21
7
  from __future__ import annotations
22
8
 
23
9
  import copy
@@ -529,7 +515,7 @@ class Workflow(BaseModel):
529
515
  run_id=(run_id or gen_id(name, unique=True)),
530
516
  parent_run_id=parent_run_id,
531
517
  )
532
- elif parent_run_id:
518
+ elif parent_run_id: # pragma: no cov
533
519
  result.set_parent_run_id(parent_run_id)
534
520
 
535
521
  if queue is not None and not isinstance(queue, ReleaseQueue):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ddeutil-workflow
3
- Version: 0.0.34
3
+ Version: 0.0.36
4
4
  Summary: Lightweight workflow orchestration
5
5
  Author-email: ddeutils <korawich.anu@gmail.com>
6
6
  License: MIT
@@ -29,6 +29,8 @@ Requires-Dist: python-dotenv==1.0.1
29
29
  Requires-Dist: schedule<2.0.0,==1.2.2
30
30
  Provides-Extra: api
31
31
  Requires-Dist: fastapi<1.0.0,>=0.115.0; extra == "api"
32
+ Requires-Dist: httpx; extra == "api"
33
+ Requires-Dist: ujson; extra == "api"
32
34
 
33
35
  # Workflow Orchestration
34
36
 
@@ -62,7 +64,7 @@ configuration. It called **Metadata Driven Data Workflow**.
62
64
  1. The Minimum frequency unit of scheduling is **1 minute** :warning:
63
65
  2. Can not re-run only failed stage and its pending downstream :rotating_light:
64
66
  3. All parallel tasks inside workflow engine use Multi-Threading
65
- (Python 3.13 unlock GIL :unlock:)
67
+ (🐍 Python 3.13 unlock GIL :unlock:)
66
68
 
67
69
  ---
68
70
 
@@ -78,45 +80,48 @@ flowchart LR
78
80
 
79
81
  subgraph Docker Container
80
82
  direction TB
81
- G@{ shape: rounded, label: "Observe<br>Application" }
83
+ G@{ shape: rounded, label: "📡Observe<br>Application" }
82
84
  end
83
85
 
84
86
  subgraph Docker Container
85
87
  direction TB
86
- B@{ shape: rounded, label: "Workflow<br>Application" }
88
+ B@{ shape: rounded, label: "🏃Workflow<br>Application" }
87
89
  end
88
90
 
89
- A <--->|action &<br>response| B
90
- B -....-> |response| G
91
- G -....-> |request| B
91
+ A <-->|action &<br>response| B
92
+ B -...-> |response| G
93
+ G -...-> |request| B
92
94
 
93
95
  subgraph Data Context
94
- D@{ shape: processes, label: "Logs" }
95
- E@{ shape: lin-cyl, label: "Audit<br>Logs" }
96
+ D@{ shape: processes, label: "Logs" }
97
+ E@{ shape: lin-cyl, label: "Audit<br>Logs" }
96
98
  end
97
99
 
98
- subgraph Git Context
99
- F@{ shape: tag-rect, label: "YAML<br>files" }
100
+ subgraph Config Context
101
+ F@{ shape: tag-rect, label: "YAML<br>files" }
100
102
  end
101
103
 
102
- B --->|disable| F
103
- F --->|read| B
104
+ A ---> |push| H(Repo)
105
+ H -.-> |pull| F
104
106
 
105
- B --->|write| E
106
- E --->|read| B
107
- B --->|write| D
107
+ B <-->|disable &<br>read| F
108
+
109
+ B <-->|read &<br>write| E
110
+
111
+ B -->|write| D
108
112
 
109
113
  D -.->|read| G
110
114
  E -.->|read| G
111
115
  ```
112
116
 
113
117
  > [!WARNING]
114
- > _Disclaimer_: I inspire the dynamic statement from the [**GitHub Action**](https://github.com/features/actions)
115
- > with `.yml` files and all configs file from several data orchestration framework
116
- > tools from my experience on Data Engineer. :grimacing:
117
- >
118
- > Other workflow tools that I interest on them and pick some interested feature
119
- > implement to this package:
118
+ > _**Disclaimer**_: I inspire the dynamic YAML statement from the [**GitHub Action**](https://github.com/features/actions),
119
+ > and all configs pattern from several data orchestration framework tools from
120
+ > my data engineering experience. :grimacing:
121
+
122
+ > [!NOTE]
123
+ > Other workflow orchestration tools that I interest and pick them to be inspiration
124
+ > some for this package:
120
125
  >
121
126
  > - [Google **Workflows**](https://cloud.google.com/workflows)
122
127
  > - [AWS **Step Functions**](https://aws.amazon.com/step-functions/)
@@ -127,10 +132,10 @@ This project need `ddeutil` and `ddeutil-io` extension namespace packages.
127
132
  If you want to install this package with application add-ons, you should add
128
133
  `app` in installation;
129
134
 
130
- | Use-case | Install Optional | Support |
131
- |----------------|--------------------------|--------------------|
132
- | Python | `ddeutil-workflow` | :heavy_check_mark: |
133
- | FastAPI Server | `ddeutil-workflow[api]` | :heavy_check_mark: |
135
+ | Use-case | Install Optional | Support |
136
+ |----------------|--------------------------|:-------------------:|
137
+ | Python | `ddeutil-workflow` | :heavy_check_mark: |
138
+ | FastAPI Server | `ddeutil-workflow[api]` | :heavy_check_mark: |
134
139
 
135
140
  ## :beers: Usage
136
141
 
@@ -292,7 +297,7 @@ like crontab job but via Python API.
292
297
  ### API Server
293
298
 
294
299
  ```shell
295
- (venv) $ uvicorn src.ddeutil.workflow.api:app \
300
+ (venv) $ uvicorn ddeutil.workflow.api:app \
296
301
  --host 127.0.0.1 \
297
302
  --port 80 \
298
303
  --no-access-log
@@ -0,0 +1,31 @@
1
+ ddeutil/workflow/__about__.py,sha256=sUrITqcQ8T6XPgdiHuk6l8YqDYHRQk2RjtNlY0SjLcs,28
2
+ ddeutil/workflow/__cron.py,sha256=3i-wmjTlh0ADCzN9pLKaWHzJkXzC72aIBmVEQSbyCCE,26895
3
+ ddeutil/workflow/__init__.py,sha256=d643WDkk93MjCt9ujD46hX07Mb7yc9eTzKV3QwnEZQg,1845
4
+ ddeutil/workflow/__types.py,sha256=CK1jfzyHP9P-MB0ElhpJZ59ZFGJC9MkQuAop5739_9k,4304
5
+ ddeutil/workflow/audit.py,sha256=wx70RKRdHj1d2431ilpt9OPTInMByjqXkYff7l5pvF4,8230
6
+ ddeutil/workflow/caller.py,sha256=qNfrr0B2ykLm6Y8JfhU1rnf5XGmp-usXrPmXM5uGRnM,5473
7
+ ddeutil/workflow/conf.py,sha256=cFc2cd_SGXg9PMrkvCT7WWE85a5UN-DdH53_JIbFyzs,14031
8
+ ddeutil/workflow/cron.py,sha256=j8EeoHst70toRfnD_frix41vrI-eLYVJkZ9yeJtpfnI,8871
9
+ ddeutil/workflow/exceptions.py,sha256=5ghT443VLq0IeU87loHNEqqrrrctklP7YfxwJ51ImWU,949
10
+ ddeutil/workflow/job.py,sha256=lpuWQdAFlFUmP0H7ha217sDwbvFlqSuqyAo4Q1yhAa8,24527
11
+ ddeutil/workflow/logs.py,sha256=EJDb9Xt3XWjTGE8CeEvw0eDU8kyaeStALQNAtTl5HQw,10027
12
+ ddeutil/workflow/params.py,sha256=qw9XJyjh2ocf9pf6h_XiYHLOvQN4R5TMqPElmItKnRM,8019
13
+ ddeutil/workflow/result.py,sha256=fbM2An3VyweMjAy4Iw7h8H-KkoQsDrZe_KjGztXAFkE,4319
14
+ ddeutil/workflow/scheduler.py,sha256=6KozyZZSXtDqQxpUwS4o-J1h_HhGbwFV5Abul3_I4W4,25940
15
+ ddeutil/workflow/stages.py,sha256=euT_v_xk0iqxWOFVZCHuFN8zREgc3-d06j5LMY8AJaE,28020
16
+ ddeutil/workflow/templates.py,sha256=A0JgZFGkBv-AX-EskZj656nG5zFd3j1PpLpyXihf6Xg,10967
17
+ ddeutil/workflow/utils.py,sha256=Djzd7Bz5QYAw0GqTFv6a0yWm_D8Md0XDxjWudmscQI0,7406
18
+ ddeutil/workflow/workflow.py,sha256=_LYfs15AcXWVpM8CaO4oH6SWoJnqzF5FU08QTDoHT5w,44529
19
+ ddeutil/workflow/api/__init__.py,sha256=F53NMBWtb9IKaDWkPU5KvybGGfKAcbehgn6TLBwHuuM,21
20
+ ddeutil/workflow/api/api.py,sha256=fBFfJtpf7CL4Vym4iiZtWsrrnICGnwYKHBTwLoatPB4,5151
21
+ ddeutil/workflow/api/repeat.py,sha256=g7VAP4O8ocKj5Uts5Q9P-OAfeDhKk7a4VWQDlOttTXA,5280
22
+ ddeutil/workflow/api/routes/__init__.py,sha256=qoGtOMyVgQ5nTUc8J8wH27A8isaxl3IFCX8qoyibeCY,484
23
+ ddeutil/workflow/api/routes/job.py,sha256=BXh8ikYKicCgzt13MysbtGgLrAPuts4dpgkD7iHTjrs,1901
24
+ ddeutil/workflow/api/routes/logs.py,sha256=sHhQuigOQ6YQ34V56d9Is6eHbV-zE45v6gPntD7lgS8,1909
25
+ ddeutil/workflow/api/routes/schedules.py,sha256=Oj8QLJJ852Lv2y40wGhuXjY2IEdhjAoGBxSWCkxe3YY,4675
26
+ ddeutil/workflow/api/routes/workflows.py,sha256=LQiLlB3tESUtOZWcuLUKIHKvnit_yX3fKzYB4FqhavI,4354
27
+ ddeutil_workflow-0.0.36.dist-info/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
28
+ ddeutil_workflow-0.0.36.dist-info/METADATA,sha256=OwDce5Gz0qVtJLQbmvbgthMfH6bD68Xaqm-B8RfLeRg,19354
29
+ ddeutil_workflow-0.0.36.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
30
+ ddeutil_workflow-0.0.36.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
31
+ ddeutil_workflow-0.0.36.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,26 +0,0 @@
1
- ddeutil/workflow/__about__.py,sha256=lXPz0LJuQJgNzG0V-1UIfdYsebVctya1i8YqUhNgkXU,28
2
- ddeutil/workflow/__cron.py,sha256=3i-wmjTlh0ADCzN9pLKaWHzJkXzC72aIBmVEQSbyCCE,26895
3
- ddeutil/workflow/__init__.py,sha256=ucsWC4fWdD3ZJu3mDLwe1BCS7wHR0-43jtxm8eZe53o,1779
4
- ddeutil/workflow/__types.py,sha256=CK1jfzyHP9P-MB0ElhpJZ59ZFGJC9MkQuAop5739_9k,4304
5
- ddeutil/workflow/audit.py,sha256=LZDgZbWioy0ctEWBmTUXJY_cAdFEMxvkvamJbkVAhdc,8066
6
- ddeutil/workflow/call.py,sha256=gkEmrYZT1KFVMVy6uyXcl2wBPQXyg1onPLkXN3q9JJk,5469
7
- ddeutil/workflow/conf.py,sha256=cFc2cd_SGXg9PMrkvCT7WWE85a5UN-DdH53_JIbFyzs,14031
8
- ddeutil/workflow/cron.py,sha256=j8EeoHst70toRfnD_frix41vrI-eLYVJkZ9yeJtpfnI,8871
9
- ddeutil/workflow/exceptions.py,sha256=5ghT443VLq0IeU87loHNEqqrrrctklP7YfxwJ51ImWU,949
10
- ddeutil/workflow/job.py,sha256=cm4u_l3fopL6tgUZYy-apZMGektaFyB5qu9uja3Nups,23776
11
- ddeutil/workflow/params.py,sha256=LKR7jXyxTb5NVrFav_fl2y9xo3p7qL1S9h-i6CtvNwE,5851
12
- ddeutil/workflow/result.py,sha256=z3C1V9C8D736gqTefBH0uUFr40pNU5Z2TwV2Pndoxmw,7800
13
- ddeutil/workflow/scheduler.py,sha256=5xlgzunC-eDOc0uSxJZhphywVOwrtmuF0mT5YYc83BY,24943
14
- ddeutil/workflow/stages.py,sha256=5UhmcDgJ214NSNyTwgYcX2hvSW1Rcgk_Qq71n1VMdZ4,26555
15
- ddeutil/workflow/templates.py,sha256=A0JgZFGkBv-AX-EskZj656nG5zFd3j1PpLpyXihf6Xg,10967
16
- ddeutil/workflow/utils.py,sha256=MctJmvklTYtiqZ-nZ7fazQeDoe77UvU0YUEqQZSlbCs,7225
17
- ddeutil/workflow/workflow.py,sha256=7SvF80y4GrrwL-0X46wlct76qPp_fttZpEgxu5xjcBY,45148
18
- ddeutil/workflow/api/__init__.py,sha256=F53NMBWtb9IKaDWkPU5KvybGGfKAcbehgn6TLBwHuuM,21
19
- ddeutil/workflow/api/api.py,sha256=TfgJtu-yREsrRveLcqTjxoJszuhq21qKkl4oyQpSzvQ,3959
20
- ddeutil/workflow/api/repeat.py,sha256=zyvsrXKk-3-_N8ZRZSki0Mueshugum2jtqctEOp9QSc,4927
21
- ddeutil/workflow/api/route.py,sha256=fKs5zlG4prwL8HplXYjYsyuxelOKgV-54VBgBufIBcQ,8629
22
- ddeutil_workflow-0.0.34.dist-info/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
23
- ddeutil_workflow-0.0.34.dist-info/METADATA,sha256=J-Y3CGyv6r39SVYlasXC6-fVNFYhAtmWjM46CDoYz_0,19221
24
- ddeutil_workflow-0.0.34.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
25
- ddeutil_workflow-0.0.34.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
26
- ddeutil_workflow-0.0.34.dist-info/RECORD,,