ddeutil-workflow 0.0.28__tar.gz → 0.0.30__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.28 → ddeutil_workflow-0.0.30}/PKG-INFO +19 -15
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/README.md +17 -13
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/pyproject.toml +7 -1
- ddeutil_workflow-0.0.30/src/ddeutil/workflow/__about__.py +1 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/__init__.py +1 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/__types.py +11 -9
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/api/api.py +2 -2
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/conf.py +38 -10
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/hook.py +6 -2
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/scheduler.py +11 -11
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/templates.py +1 -4
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/workflow.py +76 -66
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil_workflow.egg-info/PKG-INFO +19 -15
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil_workflow.egg-info/SOURCES.txt +4 -9
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil_workflow.egg-info/requires.txt +1 -1
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_conf.py +4 -2
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_hook_tag.py +3 -2
- ddeutil_workflow-0.0.28/tests/test_workflow_release_and_queue.py → ddeutil_workflow-0.0.30/tests/test_release_and_queue.py +17 -19
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_schedule_tasks.py +4 -4
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_stage.py +1 -0
- ddeutil_workflow-0.0.30/tests/test_stage_handler_exec.py +209 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_workflow_exec.py +208 -2
- ddeutil_workflow-0.0.28/tests/test_workflow_poke.py → ddeutil_workflow-0.0.30/tests/test_workflow_exec_poke.py +5 -0
- ddeutil_workflow-0.0.28/tests/test_workflow_release.py → ddeutil_workflow-0.0.30/tests/test_workflow_exec_release.py +6 -6
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_workflow_schedule.py +7 -9
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_workflow_task.py +6 -6
- ddeutil_workflow-0.0.28/src/ddeutil/workflow/__about__.py +0 -1
- ddeutil_workflow-0.0.28/tests/test_stage_exec_bash.py +0 -34
- ddeutil_workflow-0.0.28/tests/test_stage_exec_hook.py +0 -46
- ddeutil_workflow-0.0.28/tests/test_stage_exec_py.py +0 -87
- ddeutil_workflow-0.0.28/tests/test_stage_exec_trigger.py +0 -30
- ddeutil_workflow-0.0.28/tests/test_workflow_exec_hook.py +0 -91
- ddeutil_workflow-0.0.28/tests/test_workflow_exec_needs.py +0 -74
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/LICENSE +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/setup.cfg +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/__cron.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/api/__init__.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/api/repeat.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/api/route.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/cron.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/exceptions.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/job.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/params.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/result.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/stage.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil/workflow/utils.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil_workflow.egg-info/dependency_links.txt +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/src/ddeutil_workflow.egg-info/top_level.txt +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test__cron.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test__regex.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_conf_log.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_cron_on.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_job.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_job_exec_py.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_job_exec_strategy.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_job_strategy.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_params.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_result.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_schedule.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_schedule_control.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_templates.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_templates_filter.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_utils.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_workflow.py +0 -0
- {ddeutil_workflow-0.0.28 → ddeutil_workflow-0.0.30}/tests/test_workflow_job_exec.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: ddeutil-workflow
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.30
|
4
4
|
Summary: Lightweight workflow orchestration
|
5
5
|
Author-email: ddeutils <korawich.anu@gmail.com>
|
6
6
|
License: MIT
|
@@ -22,7 +22,7 @@ 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
|
25
|
+
Requires-Dist: ddeutil>=0.4.6
|
26
26
|
Requires-Dist: ddeutil-io[toml,yaml]>=0.2.3
|
27
27
|
Requires-Dist: pydantic==2.10.6
|
28
28
|
Requires-Dist: python-dotenv==1.0.1
|
@@ -55,6 +55,8 @@ the input parameters per use-case instead.
|
|
55
55
|
This way I can handle a lot of logical workflows in our orgs with only metadata
|
56
56
|
configuration. It called **Metadata Driven Data Workflow**.
|
57
57
|
|
58
|
+
---
|
59
|
+
|
58
60
|
**:pushpin: <u>Rules of This Workflow engine</u>**:
|
59
61
|
|
60
62
|
1. The Minimum frequency unit of scheduling is **1 minute** :warning:
|
@@ -62,8 +64,14 @@ configuration. It called **Metadata Driven Data Workflow**.
|
|
62
64
|
3. All parallel tasks inside workflow engine use Multi-Threading
|
63
65
|
(Python 3.13 unlock GIL :unlock:)
|
64
66
|
|
67
|
+
---
|
68
|
+
|
65
69
|
**:memo: <u>Workflow Diagrams</u>**:
|
66
70
|
|
71
|
+
This diagram show where is this application run on the production infrastructure.
|
72
|
+
You will see that this application do only running code with stress-less which mean
|
73
|
+
you should to set the data layer separate this core program before run this application.
|
74
|
+
|
67
75
|
```mermaid
|
68
76
|
flowchart LR
|
69
77
|
subgraph Interface
|
@@ -204,9 +212,9 @@ schedule-run-local-wf:
|
|
204
212
|
|
205
213
|
## :cookie: Configuration
|
206
214
|
|
207
|
-
The main configuration that use to dynamic changing
|
208
|
-
|
209
|
-
and do not raise any error to you.
|
215
|
+
The main configuration that use to dynamic changing this workflow engine for your
|
216
|
+
objective use environment variable only. If any configuration values do not set yet,
|
217
|
+
it will use default value and do not raise any error to you.
|
210
218
|
|
211
219
|
> [!IMPORTANT]
|
212
220
|
> The config value that you will set on the environment should combine with
|
@@ -215,8 +223,8 @@ and do not raise any error to you.
|
|
215
223
|
| Name | Component | Default | Description |
|
216
224
|
|:-----------------------------|:---------:|:----------------------------------|:-------------------------------------------------------------------------------------------------------------------|
|
217
225
|
| **ROOT_PATH** | Core | `.` | The root path of the workflow application. |
|
218
|
-
| **REGISTRY** | Core |
|
219
|
-
| **REGISTRY_FILTER** | Core | `ddeutil.workflow.
|
226
|
+
| **REGISTRY** | Core | `.` | List of importable string for the hook stage. |
|
227
|
+
| **REGISTRY_FILTER** | Core | `ddeutil.workflow.templates` | List of importable string for the filter template. |
|
220
228
|
| **CONF_PATH** | Core | `conf` | The config path that keep all template `.yaml` files. |
|
221
229
|
| **TIMEZONE** | Core | `Asia/Bangkok` | A Timezone string value that will pass to `ZoneInfo` object. |
|
222
230
|
| **STAGE_DEFAULT_ID** | Core | `true` | A flag that enable default stage ID that use for catch an execution output. |
|
@@ -238,6 +246,9 @@ and do not raise any error to you.
|
|
238
246
|
|
239
247
|
**API Application**:
|
240
248
|
|
249
|
+
This config part use for the workflow application that build from the FastAPI
|
250
|
+
only.
|
251
|
+
|
241
252
|
| Environment | Component | Default | Description |
|
242
253
|
|:---------------------------|:-----------:|---------|------------------------------------------------------------------------------------|
|
243
254
|
| **ENABLE_ROUTE_WORKFLOW** | API | `true` | A flag that enable workflow route to manage execute manually and workflow logging. |
|
@@ -264,16 +275,9 @@ like crontab job but via Python API.
|
|
264
275
|
|
265
276
|
### Docker Container
|
266
277
|
|
267
|
-
Create Docker image;
|
268
|
-
|
269
278
|
```shell
|
270
279
|
$ docker build -t ddeutil-workflow:latest -f .container/Dockerfile .
|
271
|
-
|
272
|
-
|
273
|
-
Run the above Docker image;
|
274
|
-
|
275
|
-
```shell
|
276
|
-
$ docker run -i ddeutil-workflow:latest
|
280
|
+
$ docker run -i ddeutil-workflow:latest ddeutil-workflow
|
277
281
|
```
|
278
282
|
|
279
283
|
## :speech_balloon: Contribute
|
@@ -23,6 +23,8 @@ the input parameters per use-case instead.
|
|
23
23
|
This way I can handle a lot of logical workflows in our orgs with only metadata
|
24
24
|
configuration. It called **Metadata Driven Data Workflow**.
|
25
25
|
|
26
|
+
---
|
27
|
+
|
26
28
|
**:pushpin: <u>Rules of This Workflow engine</u>**:
|
27
29
|
|
28
30
|
1. The Minimum frequency unit of scheduling is **1 minute** :warning:
|
@@ -30,8 +32,14 @@ configuration. It called **Metadata Driven Data Workflow**.
|
|
30
32
|
3. All parallel tasks inside workflow engine use Multi-Threading
|
31
33
|
(Python 3.13 unlock GIL :unlock:)
|
32
34
|
|
35
|
+
---
|
36
|
+
|
33
37
|
**:memo: <u>Workflow Diagrams</u>**:
|
34
38
|
|
39
|
+
This diagram show where is this application run on the production infrastructure.
|
40
|
+
You will see that this application do only running code with stress-less which mean
|
41
|
+
you should to set the data layer separate this core program before run this application.
|
42
|
+
|
35
43
|
```mermaid
|
36
44
|
flowchart LR
|
37
45
|
subgraph Interface
|
@@ -172,9 +180,9 @@ schedule-run-local-wf:
|
|
172
180
|
|
173
181
|
## :cookie: Configuration
|
174
182
|
|
175
|
-
The main configuration that use to dynamic changing
|
176
|
-
|
177
|
-
and do not raise any error to you.
|
183
|
+
The main configuration that use to dynamic changing this workflow engine for your
|
184
|
+
objective use environment variable only. If any configuration values do not set yet,
|
185
|
+
it will use default value and do not raise any error to you.
|
178
186
|
|
179
187
|
> [!IMPORTANT]
|
180
188
|
> The config value that you will set on the environment should combine with
|
@@ -183,8 +191,8 @@ and do not raise any error to you.
|
|
183
191
|
| Name | Component | Default | Description |
|
184
192
|
|:-----------------------------|:---------:|:----------------------------------|:-------------------------------------------------------------------------------------------------------------------|
|
185
193
|
| **ROOT_PATH** | Core | `.` | The root path of the workflow application. |
|
186
|
-
| **REGISTRY** | Core |
|
187
|
-
| **REGISTRY_FILTER** | Core | `ddeutil.workflow.
|
194
|
+
| **REGISTRY** | Core | `.` | List of importable string for the hook stage. |
|
195
|
+
| **REGISTRY_FILTER** | Core | `ddeutil.workflow.templates` | List of importable string for the filter template. |
|
188
196
|
| **CONF_PATH** | Core | `conf` | The config path that keep all template `.yaml` files. |
|
189
197
|
| **TIMEZONE** | Core | `Asia/Bangkok` | A Timezone string value that will pass to `ZoneInfo` object. |
|
190
198
|
| **STAGE_DEFAULT_ID** | Core | `true` | A flag that enable default stage ID that use for catch an execution output. |
|
@@ -206,6 +214,9 @@ and do not raise any error to you.
|
|
206
214
|
|
207
215
|
**API Application**:
|
208
216
|
|
217
|
+
This config part use for the workflow application that build from the FastAPI
|
218
|
+
only.
|
219
|
+
|
209
220
|
| Environment | Component | Default | Description |
|
210
221
|
|:---------------------------|:-----------:|---------|------------------------------------------------------------------------------------|
|
211
222
|
| **ENABLE_ROUTE_WORKFLOW** | API | `true` | A flag that enable workflow route to manage execute manually and workflow logging. |
|
@@ -232,16 +243,9 @@ like crontab job but via Python API.
|
|
232
243
|
|
233
244
|
### Docker Container
|
234
245
|
|
235
|
-
Create Docker image;
|
236
|
-
|
237
246
|
```shell
|
238
247
|
$ docker build -t ddeutil-workflow:latest -f .container/Dockerfile .
|
239
|
-
|
240
|
-
|
241
|
-
Run the above Docker image;
|
242
|
-
|
243
|
-
```shell
|
244
|
-
$ docker run -i ddeutil-workflow:latest
|
248
|
+
$ docker run -i ddeutil-workflow:latest ddeutil-workflow
|
245
249
|
```
|
246
250
|
|
247
251
|
## :speech_balloon: Contribute
|
@@ -26,7 +26,7 @@ classifiers = [
|
|
26
26
|
]
|
27
27
|
requires-python = ">=3.9.13"
|
28
28
|
dependencies = [
|
29
|
-
"ddeutil
|
29
|
+
"ddeutil>=0.4.6",
|
30
30
|
"ddeutil-io[yaml,toml]>=0.2.3",
|
31
31
|
"pydantic==2.10.6",
|
32
32
|
"python-dotenv==1.0.1",
|
@@ -77,6 +77,12 @@ exclude_lines = [
|
|
77
77
|
|
78
78
|
[tool.pytest.ini_options]
|
79
79
|
pythonpath = ["src"]
|
80
|
+
# NOTE: You can deslect multiple markers by '-m "not (poke or api)"'
|
81
|
+
markers = [
|
82
|
+
"poke: marks tests as slow by poking (deselect with '-m \"not poke\"')",
|
83
|
+
"schedule: marks tests as schedule (deselect with '-m \"not schedule\"')",
|
84
|
+
"api: marks tests as api (deselect with '-m \"not api\"')",
|
85
|
+
]
|
80
86
|
console_output_style = "count"
|
81
87
|
addopts = [
|
82
88
|
"--strict-config",
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__: str = "0.0.30"
|
@@ -27,6 +27,8 @@ Matrix = dict[str, Union[list[str], list[int]]]
|
|
27
27
|
|
28
28
|
|
29
29
|
class Context(TypedDict):
|
30
|
+
"""TypeDict support the Context."""
|
31
|
+
|
30
32
|
params: dict[str, Any]
|
31
33
|
jobs: dict[str, Any]
|
32
34
|
|
@@ -71,14 +73,14 @@ class Re:
|
|
71
73
|
# - ${{ params.source?.schema }}
|
72
74
|
#
|
73
75
|
__re_caller: str = r"""
|
74
|
-
\$
|
75
|
-
{{
|
76
|
-
\s*
|
76
|
+
\$ # start with $
|
77
|
+
{{ # value open with {{
|
78
|
+
\s* # whitespace or not
|
77
79
|
(?P<caller>
|
78
80
|
(?P<caller_prefix>(?:[a-zA-Z_-]+\??\.)*)
|
79
81
|
(?P<caller_last>[a-zA-Z0-9_\-.'\"(\)[\]{}]+\??)
|
80
82
|
)
|
81
|
-
\s*
|
83
|
+
\s* # whitespace or not
|
82
84
|
(?P<post_filters>
|
83
85
|
(?:
|
84
86
|
\|\s*
|
@@ -88,7 +90,7 @@ class Re:
|
|
88
90
|
)\s*
|
89
91
|
)*
|
90
92
|
)
|
91
|
-
}}
|
93
|
+
}} # value close with }}
|
92
94
|
"""
|
93
95
|
RE_CALLER: Pattern = re.compile(
|
94
96
|
__re_caller, MULTILINE | IGNORECASE | UNICODE | VERBOSE
|
@@ -103,13 +105,13 @@ class Re:
|
|
103
105
|
# - tasks/function@dummy
|
104
106
|
#
|
105
107
|
__re_task_fmt: str = r"""
|
106
|
-
^
|
108
|
+
^ # start task format
|
107
109
|
(?P<path>[^/@]+)
|
108
|
-
/
|
110
|
+
/ # start get function with /
|
109
111
|
(?P<func>[^@]+)
|
110
|
-
@
|
112
|
+
@ # start tag with @
|
111
113
|
(?P<tag>.+)
|
112
|
-
$
|
114
|
+
$ # end task format
|
113
115
|
"""
|
114
116
|
RE_TASK_FMT: Pattern = re.compile(
|
115
117
|
__re_task_fmt, MULTILINE | IGNORECASE | UNICODE | VERBOSE
|
@@ -18,7 +18,7 @@ from fastapi.responses import UJSONResponse
|
|
18
18
|
from ..__about__ import __version__
|
19
19
|
from ..conf import config, get_logger
|
20
20
|
from ..scheduler import ReleaseThread, ReleaseThreads
|
21
|
-
from ..workflow import
|
21
|
+
from ..workflow import ReleaseQueue, WorkflowTask
|
22
22
|
from .repeat import repeat_at
|
23
23
|
|
24
24
|
load_dotenv()
|
@@ -31,7 +31,7 @@ class State(TypedDict):
|
|
31
31
|
scheduler: list[str]
|
32
32
|
workflow_threads: ReleaseThreads
|
33
33
|
workflow_tasks: list[WorkflowTask]
|
34
|
-
workflow_queue: dict[str,
|
34
|
+
workflow_queue: dict[str, ReleaseQueue]
|
35
35
|
|
36
36
|
|
37
37
|
@contextlib.asynccontextmanager
|
@@ -13,7 +13,7 @@ from collections.abc import Iterator
|
|
13
13
|
from datetime import datetime, timedelta
|
14
14
|
from functools import cached_property, lru_cache
|
15
15
|
from pathlib import Path
|
16
|
-
from typing import ClassVar, Optional, Union
|
16
|
+
from typing import ClassVar, Optional, TypeVar, Union
|
17
17
|
from zoneinfo import ZoneInfo
|
18
18
|
|
19
19
|
from ddeutil.core import str2bool
|
@@ -39,6 +39,7 @@ __all__: TupleStr = (
|
|
39
39
|
"env",
|
40
40
|
"get_logger",
|
41
41
|
"get_log",
|
42
|
+
"C",
|
42
43
|
"Config",
|
43
44
|
"SimLoad",
|
44
45
|
"Loader",
|
@@ -81,18 +82,42 @@ def get_logger(name: str):
|
|
81
82
|
return lg
|
82
83
|
|
83
84
|
|
84
|
-
class
|
85
|
+
class BaseConfig: # pragma: no cov
|
86
|
+
"""BaseConfig object inheritable."""
|
87
|
+
|
88
|
+
__slots__ = ()
|
89
|
+
|
90
|
+
@property
|
91
|
+
def root_path(self) -> Path:
|
92
|
+
"""Root path or the project path.
|
93
|
+
|
94
|
+
:rtype: Path
|
95
|
+
"""
|
96
|
+
return Path(os.getenv("ROOT_PATH", "."))
|
97
|
+
|
98
|
+
@property
|
99
|
+
def conf_path(self) -> Path:
|
100
|
+
"""Config path that use root_path class argument for this construction.
|
101
|
+
|
102
|
+
:rtype: Path
|
103
|
+
"""
|
104
|
+
return self.root_path / os.getenv("CONF_PATH", "conf")
|
105
|
+
|
106
|
+
|
107
|
+
class Config(BaseConfig): # pragma: no cov
|
85
108
|
"""Config object for keeping core configurations on the current session
|
86
109
|
without changing when if the application still running.
|
87
110
|
|
88
111
|
The config value can change when you call that config property again.
|
89
112
|
"""
|
90
113
|
|
91
|
-
__slots__ = ()
|
92
|
-
|
93
114
|
# NOTE: Core
|
94
115
|
@property
|
95
116
|
def root_path(self) -> Path:
|
117
|
+
"""Root path or the project path.
|
118
|
+
|
119
|
+
:rtype: Path
|
120
|
+
"""
|
96
121
|
return Path(env("CORE_ROOT_PATH", "."))
|
97
122
|
|
98
123
|
@property
|
@@ -114,7 +139,7 @@ class Config: # pragma: no cov
|
|
114
139
|
# NOTE: Register
|
115
140
|
@property
|
116
141
|
def regis_hook(self) -> list[str]:
|
117
|
-
regis_hook_str: str = env("CORE_REGISTRY", "
|
142
|
+
regis_hook_str: str = env("CORE_REGISTRY", ".")
|
118
143
|
return [r.strip() for r in regis_hook_str.split(",")]
|
119
144
|
|
120
145
|
@property
|
@@ -220,6 +245,9 @@ class Config: # pragma: no cov
|
|
220
245
|
return str2bool(env("API_ENABLE_ROUTE_SCHEDULE", "true"))
|
221
246
|
|
222
247
|
|
248
|
+
C = TypeVar("C", bound=BaseConfig)
|
249
|
+
|
250
|
+
|
223
251
|
class SimLoad:
|
224
252
|
"""Simple Load Object that will search config data by given some identity
|
225
253
|
value like name of workflow or on.
|
@@ -243,7 +271,7 @@ class SimLoad:
|
|
243
271
|
def __init__(
|
244
272
|
self,
|
245
273
|
name: str,
|
246
|
-
conf:
|
274
|
+
conf: C,
|
247
275
|
externals: DictData | None = None,
|
248
276
|
) -> None:
|
249
277
|
self.data: DictData = {}
|
@@ -256,7 +284,7 @@ class SimLoad:
|
|
256
284
|
if not self.data:
|
257
285
|
raise ValueError(f"Config {name!r} does not found on conf path")
|
258
286
|
|
259
|
-
self.conf:
|
287
|
+
self.conf: C = conf
|
260
288
|
self.externals: DictData = externals or {}
|
261
289
|
self.data.update(self.externals)
|
262
290
|
|
@@ -264,7 +292,7 @@ class SimLoad:
|
|
264
292
|
def finds(
|
265
293
|
cls,
|
266
294
|
obj: object,
|
267
|
-
conf:
|
295
|
+
conf: C,
|
268
296
|
*,
|
269
297
|
included: list[str] | None = None,
|
270
298
|
excluded: list[str] | None = None,
|
@@ -273,7 +301,7 @@ class SimLoad:
|
|
273
301
|
method can use include and exclude list of identity name for filter and
|
274
302
|
adds-on.
|
275
303
|
|
276
|
-
:param obj:
|
304
|
+
:param obj: An object that want to validate matching before return.
|
277
305
|
:param conf: A config object.
|
278
306
|
:param included:
|
279
307
|
:param excluded:
|
@@ -338,7 +366,7 @@ class Loader(SimLoad):
|
|
338
366
|
) -> Iterator[tuple[str, DictData]]:
|
339
367
|
"""Override the find class method from the Simple Loader object.
|
340
368
|
|
341
|
-
:param obj:
|
369
|
+
:param obj: An object that want to validate matching before return.
|
342
370
|
:param included:
|
343
371
|
:param excluded:
|
344
372
|
|
@@ -50,6 +50,7 @@ def tag(
|
|
50
50
|
:param: name: A tag name for make different use-case of a function.
|
51
51
|
:param: alias: A alias function name that keeping in registries. If this
|
52
52
|
value does not supply, it will use original function name from __name__.
|
53
|
+
|
53
54
|
:rtype: Callable[P, TagFunc]
|
54
55
|
"""
|
55
56
|
|
@@ -58,7 +59,7 @@ def tag(
|
|
58
59
|
func.name = alias or func.__name__.replace("_", "-")
|
59
60
|
|
60
61
|
@wraps(func)
|
61
|
-
def wrapped(*args, **kwargs):
|
62
|
+
def wrapped(*args: P.args, **kwargs: P.kwargs) -> TagFunc:
|
62
63
|
# NOTE: Able to do anything before calling hook function.
|
63
64
|
return func(*args, **kwargs)
|
64
65
|
|
@@ -74,10 +75,13 @@ def make_registry(submodule: str) -> dict[str, Registry]:
|
|
74
75
|
"""Return registries of all functions that able to called with task.
|
75
76
|
|
76
77
|
:param submodule: A module prefix that want to import registry.
|
78
|
+
|
77
79
|
:rtype: dict[str, Registry]
|
78
80
|
"""
|
79
81
|
rs: dict[str, Registry] = {}
|
80
|
-
|
82
|
+
regis_hooks: list[str] = config.regis_hook
|
83
|
+
regis_hooks.extend(["ddeutil.vendors"])
|
84
|
+
for module in regis_hooks:
|
81
85
|
# NOTE: try to sequential import task functions
|
82
86
|
try:
|
83
87
|
importer = import_module(f"{module}.{submodule}")
|
@@ -58,7 +58,7 @@ from .utils import (
|
|
58
58
|
batch,
|
59
59
|
delay,
|
60
60
|
)
|
61
|
-
from .workflow import
|
61
|
+
from .workflow import Release, ReleaseQueue, Workflow, WorkflowTask
|
62
62
|
|
63
63
|
P = ParamSpec("P")
|
64
64
|
logger = get_logger("ddeutil.workflow")
|
@@ -170,7 +170,7 @@ class WorkflowSchedule(BaseModel):
|
|
170
170
|
def tasks(
|
171
171
|
self,
|
172
172
|
start_date: datetime,
|
173
|
-
queue: dict[str,
|
173
|
+
queue: dict[str, ReleaseQueue],
|
174
174
|
*,
|
175
175
|
externals: DictData | None = None,
|
176
176
|
) -> list[WorkflowTask]:
|
@@ -193,7 +193,7 @@ class WorkflowSchedule(BaseModel):
|
|
193
193
|
|
194
194
|
# NOTE: Loading workflow model from the name of workflow.
|
195
195
|
wf: Workflow = Workflow.from_loader(self.name, externals=extras)
|
196
|
-
wf_queue:
|
196
|
+
wf_queue: ReleaseQueue = queue[self.alias]
|
197
197
|
|
198
198
|
# IMPORTANT: Create the default 'on' value if it does not pass the `on`
|
199
199
|
# field to the Schedule object.
|
@@ -280,7 +280,7 @@ class Schedule(BaseModel):
|
|
280
280
|
def tasks(
|
281
281
|
self,
|
282
282
|
start_date: datetime,
|
283
|
-
queue: dict[str,
|
283
|
+
queue: dict[str, ReleaseQueue],
|
284
284
|
*,
|
285
285
|
externals: DictData | None = None,
|
286
286
|
) -> list[WorkflowTask]:
|
@@ -289,7 +289,7 @@ class Schedule(BaseModel):
|
|
289
289
|
|
290
290
|
:param start_date: A start date that get from the workflow schedule.
|
291
291
|
:param queue: A mapping of name and list of datetime for queue.
|
292
|
-
:type queue: dict[str,
|
292
|
+
:type queue: dict[str, ReleaseQueue]
|
293
293
|
:param externals: An external parameters that pass to the Loader object.
|
294
294
|
:type externals: DictData | None
|
295
295
|
|
@@ -302,7 +302,7 @@ class Schedule(BaseModel):
|
|
302
302
|
for workflow in self.workflows:
|
303
303
|
|
304
304
|
if workflow.alias not in queue:
|
305
|
-
queue[workflow.alias] =
|
305
|
+
queue[workflow.alias] = ReleaseQueue()
|
306
306
|
|
307
307
|
workflow_tasks.extend(
|
308
308
|
workflow.tasks(start_date, queue=queue, externals=externals)
|
@@ -355,7 +355,7 @@ ReleaseThreads = dict[str, ReleaseThread]
|
|
355
355
|
def schedule_task(
|
356
356
|
tasks: list[WorkflowTask],
|
357
357
|
stop: datetime,
|
358
|
-
queue: dict[str,
|
358
|
+
queue: dict[str, ReleaseQueue],
|
359
359
|
threads: ReleaseThreads,
|
360
360
|
log: type[Log],
|
361
361
|
) -> CancelJob | None:
|
@@ -366,7 +366,7 @@ def schedule_task(
|
|
366
366
|
|
367
367
|
:param tasks: A list of WorkflowTask object.
|
368
368
|
:param stop: A stop datetime object that force stop running scheduler.
|
369
|
-
:param queue: A mapping of alias name and
|
369
|
+
:param queue: A mapping of alias name and ReleaseQueue object.
|
370
370
|
:param threads: A mapping of alias name and Thread object.
|
371
371
|
:param log: A log class that want to make log object.
|
372
372
|
|
@@ -390,7 +390,7 @@ def schedule_task(
|
|
390
390
|
#
|
391
391
|
for task in tasks:
|
392
392
|
|
393
|
-
q:
|
393
|
+
q: ReleaseQueue = queue[task.alias]
|
394
394
|
|
395
395
|
# NOTE: Start adding queue and move the runner date in the WorkflowTask.
|
396
396
|
task.queue(stop, q, log=log)
|
@@ -418,7 +418,7 @@ def schedule_task(
|
|
418
418
|
continue
|
419
419
|
|
420
420
|
# NOTE: Pop the latest release and push it to running.
|
421
|
-
release:
|
421
|
+
release: Release = heappop(q.queue)
|
422
422
|
heappush(q.running, release)
|
423
423
|
|
424
424
|
logger.info(
|
@@ -499,7 +499,7 @@ def schedule_control(
|
|
499
499
|
stop_date: datetime = stop or (start_date + config.stop_boundary_delta)
|
500
500
|
|
501
501
|
# IMPORTANT: Create main mapping of queue and thread object.
|
502
|
-
queue: dict[str,
|
502
|
+
queue: dict[str, ReleaseQueue] = {}
|
503
503
|
threads: ReleaseThreads = {}
|
504
504
|
|
505
505
|
start_date_waiting: datetime = start_date.replace(
|
@@ -295,10 +295,7 @@ def str2template(
|
|
295
295
|
return search_env_replace(value)
|
296
296
|
|
297
297
|
|
298
|
-
def param2template(
|
299
|
-
value: Any,
|
300
|
-
params: DictData,
|
301
|
-
) -> Any:
|
298
|
+
def param2template(value: Any, params: DictData) -> Any:
|
302
299
|
"""Pass param to template string that can search by ``RE_CALLER`` regular
|
303
300
|
expression.
|
304
301
|
|