ddeutil-workflow 0.0.49__tar.gz → 0.0.51__tar.gz
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-0.0.49/src/ddeutil_workflow.egg-info → ddeutil_workflow-0.0.51}/PKG-INFO +71 -14
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/README.md +66 -11
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/pyproject.toml +5 -2
- ddeutil_workflow-0.0.51/src/ddeutil/workflow/__about__.py +1 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/__init__.py +8 -26
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/conf.py +11 -11
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/cron.py +46 -20
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/exceptions.py +3 -3
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/job.py +269 -145
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/logs.py +23 -19
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/params.py +56 -16
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/result.py +12 -4
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/reusables.py +4 -2
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/scheduler.py +5 -1
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/stages.py +580 -217
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/utils.py +42 -38
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/workflow.py +92 -95
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51/src/ddeutil_workflow.egg-info}/PKG-INFO +71 -14
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil_workflow.egg-info/requires.txt +5 -2
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_conf.py +4 -1
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_job.py +23 -15
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_job_exec.py +0 -6
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_job_exec_strategy.py +9 -11
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_params.py +17 -0
- ddeutil_workflow-0.0.51/tests/test_result.py +105 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_stage.py +29 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_stage_handler_exec.py +224 -17
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_utils.py +23 -1
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_workflow_exec.py +9 -4
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_workflow_exec_job.py +0 -5
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_workflow_exec_release.py +4 -4
- ddeutil_workflow-0.0.49/src/ddeutil/workflow/__about__.py +0 -1
- ddeutil_workflow-0.0.49/tests/test_result.py +0 -59
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/LICENSE +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/setup.cfg +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/__cron.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/__main__.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/__types.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/__init__.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/api.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/log.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/repeat.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/routes/__init__.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/routes/job.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/routes/logs.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/routes/schedules.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/routes/workflows.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil_workflow.egg-info/SOURCES.txt +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil_workflow.egg-info/dependency_links.txt +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil_workflow.egg-info/top_level.txt +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test__cron.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test__regex.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_cron_on.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_job_strategy.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_logs_audit.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_logs_trace.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_release.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_release_queue.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_reusables_call_tag.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_reusables_template.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_reusables_template_filter.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_schedule.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_schedule_pending.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_schedule_tasks.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_schedule_workflow.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_scheduler_control.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_workflow.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_workflow_exec_poke.py +0 -0
- {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_workflow_task.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ddeutil-workflow
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.51
|
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.
|
26
|
-
Requires-Dist: ddeutil-io[toml,yaml]>=0.2.
|
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
|
@@ -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
|
@@ -137,7 +139,8 @@ flowchart LR
|
|
137
139
|
|
138
140
|
## 📦 Installation
|
139
141
|
|
140
|
-
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.
|
141
144
|
If you want to install this package with application add-ons, you should add
|
142
145
|
`app` in installation;
|
143
146
|
|
@@ -146,7 +149,7 @@ If you want to install this package with application add-ons, you should add
|
|
146
149
|
| Python | `ddeutil-workflow` | :heavy_check_mark: |
|
147
150
|
| FastAPI Server | `ddeutil-workflow[api]` | :heavy_check_mark: |
|
148
151
|
|
149
|
-
##
|
152
|
+
## 🎯 Usage
|
150
153
|
|
151
154
|
This is examples that use workflow file for running common Data Engineering
|
152
155
|
use-case.
|
@@ -196,12 +199,54 @@ run-py-local:
|
|
196
199
|
|
197
200
|
# Arguments of target data that want to land.
|
198
201
|
writing_mode: flatten
|
199
|
-
|
202
|
+
aws:
|
203
|
+
path: my-data/open-data/${{ params.source-extract }}
|
200
204
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
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}
|
205
250
|
```
|
206
251
|
|
207
252
|
The above workflow template is main executor pipeline that you want to do. If you
|
@@ -268,7 +313,6 @@ it will use default value and do not raise any error to you.
|
|
268
313
|
| **TIMEZONE** | Core | `Asia/Bangkok` | A Timezone string value that will pass to `ZoneInfo` object. |
|
269
314
|
| **STAGE_DEFAULT_ID** | Core | `false` | A flag that enable default stage ID that use for catch an execution output. |
|
270
315
|
| **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
316
|
| **MAX_CRON_PER_WORKFLOW** | Core | `5` | |
|
273
317
|
| **MAX_QUEUE_COMPLETE_HIST** | Core | `16` | |
|
274
318
|
| **GENERATE_ID_SIMPLE_MODE** | Core | `true` | A flog that enable generating ID with `md5` algorithm. |
|
@@ -297,13 +341,15 @@ only.
|
|
297
341
|
## :rocket: Deployment
|
298
342
|
|
299
343
|
This package able to run as an application service for receive manual trigger
|
300
|
-
from
|
301
|
-
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.
|
302
346
|
|
303
347
|
### API Server
|
304
348
|
|
349
|
+
This server use FastAPI package to be the base application.
|
350
|
+
|
305
351
|
```shell
|
306
|
-
(venv) $ uvicorn ddeutil.workflow.api:app \
|
352
|
+
(.venv) $ uvicorn ddeutil.workflow.api:app \
|
307
353
|
--host 127.0.0.1 \
|
308
354
|
--port 80 \
|
309
355
|
--no-access-log
|
@@ -313,8 +359,19 @@ like crontab job but via Python API.
|
|
313
359
|
> If this package already deploy, it is able to use multiprocess;
|
314
360
|
> `uvicorn ddeutil.workflow.api:app --host 127.0.0.1 --port 80 --workers 4`
|
315
361
|
|
362
|
+
### Local Schedule
|
363
|
+
|
364
|
+
> [!WARNING]
|
365
|
+
> This CLI does not implement yet.
|
366
|
+
|
367
|
+
```shell
|
368
|
+
(.venv) $ ddeutil-workflow schedule
|
369
|
+
```
|
370
|
+
|
316
371
|
### Docker Container
|
317
372
|
|
373
|
+
Build a Docker container from this package.
|
374
|
+
|
318
375
|
```shell
|
319
376
|
$ docker build -t ddeutil-workflow:latest -f .container/Dockerfile .
|
320
377
|
$ docker run -i ddeutil-workflow:latest ddeutil-workflow
|
@@ -94,7 +94,8 @@ flowchart LR
|
|
94
94
|
|
95
95
|
## 📦 Installation
|
96
96
|
|
97
|
-
This project need `ddeutil` and `ddeutil-io` extension namespace packages
|
97
|
+
This project need `ddeutil` and `ddeutil-io` extension namespace packages to be
|
98
|
+
the base deps.
|
98
99
|
If you want to install this package with application add-ons, you should add
|
99
100
|
`app` in installation;
|
100
101
|
|
@@ -103,7 +104,7 @@ If you want to install this package with application add-ons, you should add
|
|
103
104
|
| Python | `ddeutil-workflow` | :heavy_check_mark: |
|
104
105
|
| FastAPI Server | `ddeutil-workflow[api]` | :heavy_check_mark: |
|
105
106
|
|
106
|
-
##
|
107
|
+
## 🎯 Usage
|
107
108
|
|
108
109
|
This is examples that use workflow file for running common Data Engineering
|
109
110
|
use-case.
|
@@ -153,12 +154,54 @@ run-py-local:
|
|
153
154
|
|
154
155
|
# Arguments of target data that want to land.
|
155
156
|
writing_mode: flatten
|
156
|
-
|
157
|
+
aws:
|
158
|
+
path: my-data/open-data/${{ params.source-extract }}
|
157
159
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
160
|
+
# This Authentication code should implement with your custom call
|
161
|
+
# function. The template allow you to use environment variable.
|
162
|
+
access_client_id: ${AWS_ACCESS_CLIENT_ID}
|
163
|
+
access_client_secret: ${AWS_ACCESS_CLIENT_SECRET}
|
164
|
+
```
|
165
|
+
|
166
|
+
Before execute this workflow, you should implement caller function first.
|
167
|
+
|
168
|
+
```text
|
169
|
+
registry-caller/
|
170
|
+
╰─ tasks.py
|
171
|
+
```
|
172
|
+
|
173
|
+
This function will store as module that will import from `WORKFLOW_CORE_REGISTRY_CALLER`
|
174
|
+
value (This config can override by extra parameters with `registry_caller` key).
|
175
|
+
|
176
|
+
```python
|
177
|
+
from ddeutil.workflow import Result, tag
|
178
|
+
from ddeutil.workflow.exceptions import StageException
|
179
|
+
from pydantic import BaseModel, SecretStr
|
180
|
+
|
181
|
+
class AwsCredential(BaseModel):
|
182
|
+
path: str
|
183
|
+
access_client_id: str
|
184
|
+
access_client_secret: SecretStr
|
185
|
+
|
186
|
+
class RestAuth(BaseModel):
|
187
|
+
type: str
|
188
|
+
keys: SecretStr
|
189
|
+
|
190
|
+
@tag("requests", alias="get-api-with-oauth-to-s3")
|
191
|
+
def get_api_with_oauth_to_s3(
|
192
|
+
method: str,
|
193
|
+
url: str,
|
194
|
+
body: dict[str, str],
|
195
|
+
auth: RestAuth,
|
196
|
+
writing_node: str,
|
197
|
+
aws: AwsCredential,
|
198
|
+
result: Result,
|
199
|
+
) -> dict[str, int]:
|
200
|
+
result.trace.info("[CALLER]: Start get data via RestAPI to S3.")
|
201
|
+
result.trace.info(f"... {method}: {url}")
|
202
|
+
if method != "post":
|
203
|
+
raise StageException(f"RestAPI does not support for {method} action.")
|
204
|
+
return {"records": 1000}
|
162
205
|
```
|
163
206
|
|
164
207
|
The above workflow template is main executor pipeline that you want to do. If you
|
@@ -225,7 +268,6 @@ it will use default value and do not raise any error to you.
|
|
225
268
|
| **TIMEZONE** | Core | `Asia/Bangkok` | A Timezone string value that will pass to `ZoneInfo` object. |
|
226
269
|
| **STAGE_DEFAULT_ID** | Core | `false` | A flag that enable default stage ID that use for catch an execution output. |
|
227
270
|
| **STAGE_RAISE_ERROR** | Core | `false` | A flag that all stage raise StageException from stage execution. |
|
228
|
-
| **JOB_RAISE_ERROR** | Core | `true` | A flag that all job raise JobException from job strategy execution. |
|
229
271
|
| **MAX_CRON_PER_WORKFLOW** | Core | `5` | |
|
230
272
|
| **MAX_QUEUE_COMPLETE_HIST** | Core | `16` | |
|
231
273
|
| **GENERATE_ID_SIMPLE_MODE** | Core | `true` | A flog that enable generating ID with `md5` algorithm. |
|
@@ -254,13 +296,15 @@ only.
|
|
254
296
|
## :rocket: Deployment
|
255
297
|
|
256
298
|
This package able to run as an application service for receive manual trigger
|
257
|
-
from
|
258
|
-
like crontab job but via Python API.
|
299
|
+
from any node via RestAPI or use to be Scheduler background application
|
300
|
+
like crontab job but via Python API or FastAPI app.
|
259
301
|
|
260
302
|
### API Server
|
261
303
|
|
304
|
+
This server use FastAPI package to be the base application.
|
305
|
+
|
262
306
|
```shell
|
263
|
-
(venv) $ uvicorn ddeutil.workflow.api:app \
|
307
|
+
(.venv) $ uvicorn ddeutil.workflow.api:app \
|
264
308
|
--host 127.0.0.1 \
|
265
309
|
--port 80 \
|
266
310
|
--no-access-log
|
@@ -270,8 +314,19 @@ like crontab job but via Python API.
|
|
270
314
|
> If this package already deploy, it is able to use multiprocess;
|
271
315
|
> `uvicorn ddeutil.workflow.api:app --host 127.0.0.1 --port 80 --workers 4`
|
272
316
|
|
317
|
+
### Local Schedule
|
318
|
+
|
319
|
+
> [!WARNING]
|
320
|
+
> This CLI does not implement yet.
|
321
|
+
|
322
|
+
```shell
|
323
|
+
(.venv) $ ddeutil-workflow schedule
|
324
|
+
```
|
325
|
+
|
273
326
|
### Docker Container
|
274
327
|
|
328
|
+
Build a Docker container from this package.
|
329
|
+
|
275
330
|
```shell
|
276
331
|
$ docker build -t ddeutil-workflow:latest -f .container/Dockerfile .
|
277
332
|
$ docker run -i ddeutil-workflow:latest ddeutil-workflow
|
@@ -25,8 +25,8 @@ classifiers = [
|
|
25
25
|
]
|
26
26
|
requires-python = ">=3.9.13"
|
27
27
|
dependencies = [
|
28
|
-
"ddeutil[checksum]>=0.4.
|
29
|
-
"ddeutil-io[yaml,toml]>=0.2.
|
28
|
+
"ddeutil[checksum]>=0.4.7",
|
29
|
+
"ddeutil-io[yaml,toml]>=0.2.11",
|
30
30
|
"pydantic==2.11.1",
|
31
31
|
"python-dotenv==1.1.0",
|
32
32
|
"schedule==1.2.2,<2.0.0",
|
@@ -49,6 +49,9 @@ async = [
|
|
49
49
|
"aiofiles",
|
50
50
|
"aiohttp",
|
51
51
|
]
|
52
|
+
docker = [
|
53
|
+
"docker==7.1.0"
|
54
|
+
]
|
52
55
|
|
53
56
|
[project.urls]
|
54
57
|
Homepage = "https://github.com/ddeutils/ddeutil-workflow/"
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__: str = "0.0.51"
|
@@ -11,25 +11,9 @@ from .conf import (
|
|
11
11
|
config,
|
12
12
|
env,
|
13
13
|
)
|
14
|
-
from .cron import
|
15
|
-
|
16
|
-
|
17
|
-
interval2crontab,
|
18
|
-
)
|
19
|
-
from .exceptions import (
|
20
|
-
JobException,
|
21
|
-
ParamValueException,
|
22
|
-
StageException,
|
23
|
-
UtilException,
|
24
|
-
WorkflowException,
|
25
|
-
)
|
26
|
-
from .job import (
|
27
|
-
Job,
|
28
|
-
RunsOn,
|
29
|
-
Strategy,
|
30
|
-
local_execute,
|
31
|
-
local_execute_strategy,
|
32
|
-
)
|
14
|
+
from .cron import *
|
15
|
+
from .exceptions import *
|
16
|
+
from .job import *
|
33
17
|
from .logs import (
|
34
18
|
Audit,
|
35
19
|
AuditModel,
|
@@ -41,14 +25,9 @@ from .logs import (
|
|
41
25
|
get_dt_tznow,
|
42
26
|
get_trace,
|
43
27
|
)
|
44
|
-
from .params import
|
45
|
-
ChoiceParam,
|
46
|
-
DatetimeParam,
|
47
|
-
IntParam,
|
48
|
-
Param,
|
49
|
-
StrParam,
|
50
|
-
)
|
28
|
+
from .params import *
|
51
29
|
from .result import (
|
30
|
+
CANCEL,
|
52
31
|
FAILED,
|
53
32
|
SKIP,
|
54
33
|
SUCCESS,
|
@@ -101,6 +80,9 @@ from .utils import (
|
|
101
80
|
get_diff_sec,
|
102
81
|
get_dt_now,
|
103
82
|
make_exec,
|
83
|
+
reach_next_minute,
|
84
|
+
replace_sec,
|
85
|
+
wait_to_next_minute,
|
104
86
|
)
|
105
87
|
from .workflow import (
|
106
88
|
Release,
|
@@ -53,7 +53,6 @@ class Config: # pragma: no cov
|
|
53
53
|
The config value can change when you call that config property again.
|
54
54
|
"""
|
55
55
|
|
56
|
-
# NOTE: Core
|
57
56
|
@property
|
58
57
|
def conf_path(self) -> Path:
|
59
58
|
"""Config path that keep all workflow template YAML files.
|
@@ -73,9 +72,13 @@ class Config: # pragma: no cov
|
|
73
72
|
|
74
73
|
@property
|
75
74
|
def generate_id_simple_mode(self) -> bool:
|
75
|
+
"""Flag for generate running ID with simple mode. That does not use
|
76
|
+
`md5` function after generate simple mode.
|
77
|
+
|
78
|
+
:rtype: bool
|
79
|
+
"""
|
76
80
|
return str2bool(env("CORE_GENERATE_ID_SIMPLE_MODE", "true"))
|
77
81
|
|
78
|
-
# NOTE: Register
|
79
82
|
@property
|
80
83
|
def registry_caller(self) -> list[str]:
|
81
84
|
"""Register Caller that is a list of importable string for the call
|
@@ -98,13 +101,16 @@ class Config: # pragma: no cov
|
|
98
101
|
)
|
99
102
|
return [r.strip() for r in regis_filter_str.split(",")]
|
100
103
|
|
101
|
-
# NOTE: Log
|
102
104
|
@property
|
103
105
|
def trace_path(self) -> Path:
|
104
106
|
return Path(env("LOG_TRACE_PATH", "./logs"))
|
105
107
|
|
106
108
|
@property
|
107
109
|
def debug(self) -> bool:
|
110
|
+
"""Debug flag for echo log that use DEBUG mode.
|
111
|
+
|
112
|
+
:rtype: bool
|
113
|
+
"""
|
108
114
|
return str2bool(env("LOG_DEBUG_MODE", "true"))
|
109
115
|
|
110
116
|
@property
|
@@ -144,7 +150,6 @@ class Config: # pragma: no cov
|
|
144
150
|
def log_datetime_format(self) -> str:
|
145
151
|
return env("LOG_DATETIME_FORMAT", "%Y-%m-%d %H:%M:%S")
|
146
152
|
|
147
|
-
# NOTE: Stage
|
148
153
|
@property
|
149
154
|
def stage_raise_error(self) -> bool:
|
150
155
|
return str2bool(env("CORE_STAGE_RAISE_ERROR", "false"))
|
@@ -153,11 +158,6 @@ class Config: # pragma: no cov
|
|
153
158
|
def stage_default_id(self) -> bool:
|
154
159
|
return str2bool(env("CORE_STAGE_DEFAULT_ID", "false"))
|
155
160
|
|
156
|
-
# NOTE: Job
|
157
|
-
@property
|
158
|
-
def job_raise_error(self) -> bool:
|
159
|
-
return str2bool(env("CORE_JOB_RAISE_ERROR", "true"))
|
160
|
-
|
161
161
|
@property
|
162
162
|
def max_cron_per_workflow(self) -> int:
|
163
163
|
"""The maximum on value that store in workflow model.
|
@@ -360,13 +360,13 @@ def dynamic(
|
|
360
360
|
:param extras: An extra values that pass at run-time.
|
361
361
|
"""
|
362
362
|
rsx: Optional[T] = extras[key] if extras and key in extras else None
|
363
|
-
rs: Optional[T] =
|
363
|
+
rs: Optional[T] = getattr(config, key, None) if f is None else f
|
364
364
|
if rsx is not None and not isinstance(rsx, type(rs)):
|
365
365
|
raise TypeError(
|
366
366
|
f"Type of config {key!r} from extras: {rsx!r} does not valid "
|
367
367
|
f"as config {type(rs)}."
|
368
368
|
)
|
369
|
-
return rsx
|
369
|
+
return rsx if rsx is not None else rs
|
370
370
|
|
371
371
|
|
372
372
|
class Loader(SimLoad):
|
@@ -16,18 +16,13 @@ from pydantic.functional_validators import field_validator, model_validator
|
|
16
16
|
from typing_extensions import Self
|
17
17
|
|
18
18
|
from .__cron import WEEKDAYS, CronJob, CronJobYear, CronRunner, Options
|
19
|
-
from .__types import DictData, DictStr
|
19
|
+
from .__types import DictData, DictStr
|
20
20
|
from .conf import Loader
|
21
21
|
|
22
|
-
__all__: TupleStr = (
|
23
|
-
"On",
|
24
|
-
"YearOn",
|
25
|
-
"interval2crontab",
|
26
|
-
)
|
27
|
-
|
28
22
|
|
29
23
|
def interval2crontab(
|
30
24
|
interval: Literal["daily", "weekly", "monthly"],
|
25
|
+
*,
|
31
26
|
day: str | None = None,
|
32
27
|
time: str = "00:00",
|
33
28
|
) -> str:
|
@@ -67,7 +62,7 @@ class On(BaseModel):
|
|
67
62
|
"""On Pydantic model (Warped crontab object by model).
|
68
63
|
|
69
64
|
See Also:
|
70
|
-
*
|
65
|
+
* `generate()` is the main use-case of this schedule object.
|
71
66
|
"""
|
72
67
|
|
73
68
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
@@ -76,12 +71,18 @@ class On(BaseModel):
|
|
76
71
|
DictData,
|
77
72
|
Field(
|
78
73
|
default_factory=dict,
|
79
|
-
description=
|
74
|
+
description=(
|
75
|
+
"An extras parameters that want to pass to the CronJob field."
|
76
|
+
),
|
80
77
|
),
|
81
78
|
]
|
82
79
|
cronjob: Annotated[
|
83
80
|
CronJob,
|
84
|
-
Field(
|
81
|
+
Field(
|
82
|
+
description=(
|
83
|
+
"A Cronjob object that use for validate and generate datetime.",
|
84
|
+
),
|
85
|
+
),
|
85
86
|
]
|
86
87
|
tz: Annotated[
|
87
88
|
str,
|
@@ -118,6 +119,8 @@ class On(BaseModel):
|
|
118
119
|
|
119
120
|
:param name: A name of config that will get from loader.
|
120
121
|
:param extras: An extra parameter that will keep in extras.
|
122
|
+
|
123
|
+
:rtype: Self
|
121
124
|
"""
|
122
125
|
extras: DictData = extras or {}
|
123
126
|
loader: Loader = Loader(name, externals=extras)
|
@@ -142,9 +145,7 @@ class On(BaseModel):
|
|
142
145
|
)
|
143
146
|
)
|
144
147
|
if "cronjob" not in loader_data:
|
145
|
-
raise ValueError(
|
146
|
-
"Config does not set ``cronjob`` or ``interval`` keys"
|
147
|
-
)
|
148
|
+
raise ValueError("Config does not set `cronjob` or `interval` keys")
|
148
149
|
return cls.model_validate(
|
149
150
|
obj=dict(
|
150
151
|
cronjob=loader_data.pop("cronjob"),
|
@@ -155,7 +156,13 @@ class On(BaseModel):
|
|
155
156
|
|
156
157
|
@model_validator(mode="before")
|
157
158
|
def __prepare_values(cls, values: DictData) -> DictData:
|
158
|
-
"""Extract tz key from value and change name to timezone key.
|
159
|
+
"""Extract tz key from value and change name to timezone key.
|
160
|
+
|
161
|
+
:param values: (DictData) A data that want to pass for create an On
|
162
|
+
model.
|
163
|
+
|
164
|
+
:rtype: DictData
|
165
|
+
"""
|
159
166
|
if tz := values.pop("tz", None):
|
160
167
|
values["timezone"] = tz
|
161
168
|
return values
|
@@ -170,8 +177,8 @@ class On(BaseModel):
|
|
170
177
|
try:
|
171
178
|
_ = ZoneInfo(value)
|
172
179
|
return value
|
173
|
-
except ZoneInfoNotFoundError as
|
174
|
-
raise ValueError(f"Invalid timezone: {value}") from
|
180
|
+
except ZoneInfoNotFoundError as e:
|
181
|
+
raise ValueError(f"Invalid timezone: {value}") from e
|
175
182
|
|
176
183
|
@field_validator(
|
177
184
|
"cronjob", mode="before", json_schema_input_type=Union[CronJob, str]
|
@@ -180,9 +187,13 @@ class On(BaseModel):
|
|
180
187
|
cls, value: str | CronJob, info: ValidationInfo
|
181
188
|
) -> CronJob:
|
182
189
|
"""Prepare crontab value that able to receive with string type.
|
183
|
-
This step will get options kwargs from extras and pass to the
|
190
|
+
This step will get options kwargs from extras field and pass to the
|
184
191
|
CronJob object.
|
185
192
|
|
193
|
+
:param value: (str | CronJobYear) A cronjob value that want to create.
|
194
|
+
:param info: (ValidationInfo) A validation info object that use to get
|
195
|
+
the extra parameters for create cronjob.
|
196
|
+
|
186
197
|
:rtype: CronJob
|
187
198
|
"""
|
188
199
|
extras: DictData = info.data.get("extras", {})
|
@@ -203,12 +214,17 @@ class On(BaseModel):
|
|
203
214
|
def __serialize_cronjob(self, value: CronJob) -> str:
|
204
215
|
"""Serialize the cronjob field that store with CronJob object.
|
205
216
|
|
217
|
+
:param value: (CronJob) The CronJob field.
|
218
|
+
|
206
219
|
:rtype: str
|
207
220
|
"""
|
208
221
|
return str(value)
|
209
222
|
|
210
223
|
def generate(self, start: str | datetime) -> CronRunner:
|
211
|
-
"""Return
|
224
|
+
"""Return CronRunner object from an initial datetime.
|
225
|
+
|
226
|
+
:param start: (str | datetime) A string or datetime for generate the
|
227
|
+
CronRunner object.
|
212
228
|
|
213
229
|
:rtype: CronRunner
|
214
230
|
"""
|
@@ -242,16 +258,26 @@ class YearOn(On):
|
|
242
258
|
# NOTE: This is fields of the base schedule.
|
243
259
|
cronjob: Annotated[
|
244
260
|
CronJobYear,
|
245
|
-
Field(
|
261
|
+
Field(
|
262
|
+
description=(
|
263
|
+
"A Cronjob object that use for validate and generate datetime.",
|
264
|
+
),
|
265
|
+
),
|
246
266
|
]
|
247
267
|
|
248
268
|
@field_validator(
|
249
|
-
"cronjob", mode="before", json_schema_input_type=Union[
|
269
|
+
"cronjob", mode="before", json_schema_input_type=Union[CronJobYear, str]
|
250
270
|
)
|
251
271
|
def __prepare_cronjob(
|
252
272
|
cls, value: str | CronJobYear, info: ValidationInfo
|
253
273
|
) -> CronJobYear:
|
254
274
|
"""Prepare crontab value that able to receive with string type.
|
275
|
+
This step will get options kwargs from extras field and pass to the
|
276
|
+
CronJobYear object.
|
277
|
+
|
278
|
+
:param value: (str | CronJobYear) A cronjob value that want to create.
|
279
|
+
:param info: (ValidationInfo) A validation info object that use to get
|
280
|
+
the extra parameters for create cronjob.
|
255
281
|
|
256
282
|
:rtype: CronJobYear
|
257
283
|
"""
|
@@ -39,6 +39,9 @@ class BaseWorkflowException(Exception):
|
|
39
39
|
class UtilException(BaseWorkflowException): ...
|
40
40
|
|
41
41
|
|
42
|
+
class ResultException(UtilException): ...
|
43
|
+
|
44
|
+
|
42
45
|
class StageException(BaseWorkflowException): ...
|
43
46
|
|
44
47
|
|
@@ -48,9 +51,6 @@ class JobException(BaseWorkflowException): ...
|
|
48
51
|
class WorkflowException(BaseWorkflowException): ...
|
49
52
|
|
50
53
|
|
51
|
-
class WorkflowFailException(WorkflowException): ...
|
52
|
-
|
53
|
-
|
54
54
|
class ParamValueException(WorkflowException): ...
|
55
55
|
|
56
56
|
|