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.
Files changed (69) hide show
  1. {ddeutil_workflow-0.0.49/src/ddeutil_workflow.egg-info → ddeutil_workflow-0.0.51}/PKG-INFO +71 -14
  2. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/README.md +66 -11
  3. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/pyproject.toml +5 -2
  4. ddeutil_workflow-0.0.51/src/ddeutil/workflow/__about__.py +1 -0
  5. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/__init__.py +8 -26
  6. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/conf.py +11 -11
  7. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/cron.py +46 -20
  8. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/exceptions.py +3 -3
  9. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/job.py +269 -145
  10. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/logs.py +23 -19
  11. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/params.py +56 -16
  12. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/result.py +12 -4
  13. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/reusables.py +4 -2
  14. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/scheduler.py +5 -1
  15. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/stages.py +580 -217
  16. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/utils.py +42 -38
  17. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/workflow.py +92 -95
  18. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51/src/ddeutil_workflow.egg-info}/PKG-INFO +71 -14
  19. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil_workflow.egg-info/requires.txt +5 -2
  20. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_conf.py +4 -1
  21. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_job.py +23 -15
  22. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_job_exec.py +0 -6
  23. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_job_exec_strategy.py +9 -11
  24. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_params.py +17 -0
  25. ddeutil_workflow-0.0.51/tests/test_result.py +105 -0
  26. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_stage.py +29 -0
  27. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_stage_handler_exec.py +224 -17
  28. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_utils.py +23 -1
  29. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_workflow_exec.py +9 -4
  30. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_workflow_exec_job.py +0 -5
  31. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_workflow_exec_release.py +4 -4
  32. ddeutil_workflow-0.0.49/src/ddeutil/workflow/__about__.py +0 -1
  33. ddeutil_workflow-0.0.49/tests/test_result.py +0 -59
  34. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/LICENSE +0 -0
  35. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/setup.cfg +0 -0
  36. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/__cron.py +0 -0
  37. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/__main__.py +0 -0
  38. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/__types.py +0 -0
  39. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/__init__.py +0 -0
  40. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/api.py +0 -0
  41. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/log.py +0 -0
  42. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/repeat.py +0 -0
  43. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/routes/__init__.py +0 -0
  44. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/routes/job.py +0 -0
  45. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/routes/logs.py +0 -0
  46. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/routes/schedules.py +0 -0
  47. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil/workflow/api/routes/workflows.py +0 -0
  48. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil_workflow.egg-info/SOURCES.txt +0 -0
  49. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil_workflow.egg-info/dependency_links.txt +0 -0
  50. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/src/ddeutil_workflow.egg-info/top_level.txt +0 -0
  51. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test__cron.py +0 -0
  52. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test__regex.py +0 -0
  53. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_cron_on.py +0 -0
  54. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_job_strategy.py +0 -0
  55. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_logs_audit.py +0 -0
  56. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_logs_trace.py +0 -0
  57. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_release.py +0 -0
  58. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_release_queue.py +0 -0
  59. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_reusables_call_tag.py +0 -0
  60. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_reusables_template.py +0 -0
  61. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_reusables_template_filter.py +0 -0
  62. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_schedule.py +0 -0
  63. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_schedule_pending.py +0 -0
  64. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_schedule_tasks.py +0 -0
  65. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_schedule_workflow.py +0 -0
  66. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_scheduler_control.py +0 -0
  67. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_workflow.py +0 -0
  68. {ddeutil_workflow-0.0.49 → ddeutil_workflow-0.0.51}/tests/test_workflow_exec_poke.py +0 -0
  69. {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.49
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.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
@@ -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
- ## :beers: Usage
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
- aws_s3_path: my-data/open-data/${{ params.source-extract }}
202
+ aws:
203
+ path: my-data/open-data/${{ params.source-extract }}
200
204
 
201
- # This Authentication code should implement with your custom call
202
- # function. The template allow you to use environment variable.
203
- aws_access_client_id: ${AWS_ACCESS_CLIENT_ID}
204
- 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}
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 the master node via RestAPI or use to be Scheduler background service
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
- ## :beers: Usage
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
- aws_s3_path: my-data/open-data/${{ params.source-extract }}
157
+ aws:
158
+ path: my-data/open-data/${{ params.source-extract }}
157
159
 
158
- # This Authentication code should implement with your custom call
159
- # function. The template allow you to use environment variable.
160
- aws_access_client_id: ${AWS_ACCESS_CLIENT_ID}
161
- aws_access_client_secret: ${AWS_ACCESS_CLIENT_SECRET}
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 the master node via RestAPI or use to be Scheduler background service
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.6",
29
- "ddeutil-io[yaml,toml]>=0.2.10",
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
- On,
16
- YearOn,
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] = f or getattr(config, key, None)
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 or rs
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, TupleStr
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
- * ``generate()`` is the main use-case of this schedule object.
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="An extras mapping parameters.",
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(description="A Cronjob object of this schedule."),
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 err:
174
- raise ValueError(f"Invalid timezone: {value}") from err
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 Cron runner object.
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(description="Cron job of this schedule"),
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[CronJob, str]
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