ddeutil-workflow 0.0.62__tar.gz → 0.0.64__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.62 → ddeutil_workflow-0.0.64}/PKG-INFO +11 -57
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/README.md +9 -56
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/pyproject.toml +1 -0
- ddeutil_workflow-0.0.64/src/ddeutil/workflow/__about__.py +1 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/__init__.py +2 -31
- ddeutil_workflow-0.0.64/src/ddeutil/workflow/api/__init__.py +91 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/api/routes/__init__.py +0 -1
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/api/routes/job.py +0 -1
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/api/routes/logs.py +0 -2
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/api/routes/workflows.py +0 -3
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/conf.py +40 -36
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/event.py +2 -1
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/exceptions.py +0 -3
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/reusables.py +104 -14
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/stages.py +60 -52
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/utils.py +4 -2
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil_workflow.egg-info/PKG-INFO +11 -57
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil_workflow.egg-info/SOURCES.txt +2 -12
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil_workflow.egg-info/requires.txt +1 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_conf.py +28 -68
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_reusables_call_tag.py +5 -1
- ddeutil_workflow-0.0.64/tests/test_reusables_func_model.py +158 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_stage_handler_exec.py +20 -3
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_workflow_exec.py +1 -1
- ddeutil_workflow-0.0.62/src/ddeutil/workflow/__about__.py +0 -1
- ddeutil_workflow-0.0.62/src/ddeutil/workflow/api/__init__.py +0 -170
- ddeutil_workflow-0.0.62/src/ddeutil/workflow/api/routes/schedules.py +0 -141
- ddeutil_workflow-0.0.62/src/ddeutil/workflow/api/utils.py +0 -174
- ddeutil_workflow-0.0.62/src/ddeutil/workflow/scheduler.py +0 -813
- ddeutil_workflow-0.0.62/tests/test_schedule.py +0 -173
- ddeutil_workflow-0.0.62/tests/test_schedule_pending.py +0 -13
- ddeutil_workflow-0.0.62/tests/test_schedule_tasks.py +0 -82
- ddeutil_workflow-0.0.62/tests/test_schedule_workflow.py +0 -124
- ddeutil_workflow-0.0.62/tests/test_scheduler_control.py +0 -49
- ddeutil_workflow-0.0.62/tests/test_workflow_poke.py +0 -168
- ddeutil_workflow-0.0.62/tests/test_workflow_release.py +0 -153
- ddeutil_workflow-0.0.62/tests/test_workflow_task.py +0 -223
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/LICENSE +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/setup.cfg +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/__cron.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/__main__.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/__types.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/api/logs.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/job.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/logs.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/params.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/result.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/workflow.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil_workflow.egg-info/dependency_links.txt +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil_workflow.egg-info/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil_workflow.egg-info/top_level.txt +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test__cron.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test__regex.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_event.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_job.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_job_exec.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_job_exec_strategy.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_logs_audit.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_logs_trace.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_params.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_release.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_release_queue.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_result.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_reusables_template.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_reusables_template_filter.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_stage.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_strategy.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_utils.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_workflow.py +0 -0
- {ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/tests/test_workflow_exec_job.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.64
|
4
4
|
Summary: Lightweight workflow orchestration
|
5
5
|
Author-email: ddeutils <korawich.anu@gmail.com>
|
6
6
|
License: MIT
|
@@ -25,6 +25,7 @@ License-File: LICENSE
|
|
25
25
|
Requires-Dist: ddeutil[checksum]>=0.4.8
|
26
26
|
Requires-Dist: ddeutil-io[toml,yaml]>=0.2.13
|
27
27
|
Requires-Dist: pydantic==2.11.4
|
28
|
+
Requires-Dist: pydantic-extra-types==2.10.4
|
28
29
|
Requires-Dist: python-dotenv==1.1.0
|
29
30
|
Requires-Dist: schedule<2.0.0,==1.2.2
|
30
31
|
Provides-Extra: all
|
@@ -215,19 +216,23 @@ registry-caller/
|
|
215
216
|
This function will store as module that will import from `WORKFLOW_CORE_REGISTRY_CALLER`
|
216
217
|
value (This config can override by extra parameters with `registry_caller` key).
|
217
218
|
|
219
|
+
> [!NOTE]
|
220
|
+
> You can use Pydantic Model as argument of your caller function. The core workflow
|
221
|
+
> engine will auto use the `model_validate` method before run your caller function.
|
222
|
+
|
218
223
|
```python
|
219
|
-
from ddeutil.workflow import Result, tag
|
224
|
+
from ddeutil.workflow import Result, CallerSecret, tag
|
220
225
|
from ddeutil.workflow.exceptions import StageException
|
221
|
-
from pydantic import BaseModel
|
226
|
+
from pydantic import BaseModel
|
222
227
|
|
223
228
|
class AwsCredential(BaseModel):
|
224
229
|
path: str
|
225
230
|
access_client_id: str
|
226
|
-
access_client_secret:
|
231
|
+
access_client_secret: CallerSecret
|
227
232
|
|
228
233
|
class RestAuth(BaseModel):
|
229
234
|
type: str
|
230
|
-
keys:
|
235
|
+
keys: CallerSecret
|
231
236
|
|
232
237
|
@tag("requests", alias="get-api-with-oauth-to-s3")
|
233
238
|
def get_api_with_oauth_to_s3(
|
@@ -243,6 +248,7 @@ def get_api_with_oauth_to_s3(
|
|
243
248
|
result.trace.info(f"... {method}: {url}")
|
244
249
|
if method != "post":
|
245
250
|
raise StageException(f"RestAPI does not support for {method} action.")
|
251
|
+
# NOTE: If you want to use secret, you can use `auth.keys.get_secret_value()`.
|
246
252
|
return {"records": 1000}
|
247
253
|
```
|
248
254
|
|
@@ -259,45 +265,6 @@ result: Result = workflow.execute(
|
|
259
265
|
)
|
260
266
|
```
|
261
267
|
|
262
|
-
> [!NOTE]
|
263
|
-
> So, this package provide the `Schedule` template for this action, and you can
|
264
|
-
> pass the parameters dynamically for changing align with that running time by
|
265
|
-
> the `release` prefix.
|
266
|
-
>
|
267
|
-
> ```yaml
|
268
|
-
> schedule-run-local-wf:
|
269
|
-
>
|
270
|
-
> # Validate model that use to parsing exists for template file
|
271
|
-
> type: Schedule
|
272
|
-
> workflows:
|
273
|
-
>
|
274
|
-
> # Map existing workflow that want to deploy with scheduler application.
|
275
|
-
> # It allows you to pass release parameter that dynamic change depend on the
|
276
|
-
> # current context of this scheduler application releasing that time.
|
277
|
-
> - name: run-py-local
|
278
|
-
> params:
|
279
|
-
> source-extract: "USD-THB"
|
280
|
-
> run-date: "${{ release.logical_date }}"
|
281
|
-
> ```
|
282
|
-
>
|
283
|
-
> The main method of the `Schedule` model that use to running is `pending`. If you
|
284
|
-
> do not pass the `stop` date on this method, it will use config with
|
285
|
-
> `WORKFLOW_APP_STOP_BOUNDARY_DELTA` key for generate this stop date.
|
286
|
-
>
|
287
|
-
> ```python
|
288
|
-
> from ddeutil.workflow import Schedule
|
289
|
-
>
|
290
|
-
> (
|
291
|
-
> Schedule
|
292
|
-
> .from_conf("schedule-run-local-wf")
|
293
|
-
> .pending(stop=None)
|
294
|
-
> )
|
295
|
-
> ```
|
296
|
-
|
297
|
-
> [!WARNING]
|
298
|
-
> The scheduler feature is the expensive feature of this project. You should
|
299
|
-
> avoid to use it and find a scheduler tool instead.
|
300
|
-
|
301
268
|
## :cookie: Configuration
|
302
269
|
|
303
270
|
The main configuration that use to dynamic changing this workflow engine for your
|
@@ -327,19 +294,6 @@ it will use default value and do not raise any error to you.
|
|
327
294
|
| **TRACE_ENABLE_WRITE** | Log | `false` | |
|
328
295
|
| **AUDIT_PATH** | Log | `./audits` | |
|
329
296
|
| **AUDIT_ENABLE_WRITE** | Log | `true` | A flag that enable logging object saving log to its destination. |
|
330
|
-
| **MAX_PROCESS** | App | `2` | The maximum process worker number that run in scheduler app module. |
|
331
|
-
| **MAX_SCHEDULE_PER_PROCESS** | App | `100` | A schedule per process that run parallel. |
|
332
|
-
| **STOP_BOUNDARY_DELTA** | App | `'{"minutes": 5, "seconds": 20}'` | A time delta value that use to stop scheduler app in json string format. |
|
333
|
-
|
334
|
-
**API Application**:
|
335
|
-
|
336
|
-
This config part use for the workflow application that build from the FastAPI
|
337
|
-
only.
|
338
|
-
|
339
|
-
| Environment | Component | Default | Description |
|
340
|
-
|:---------------------------|:-----------:|---------|------------------------------------------------------------------------------------|
|
341
|
-
| **ENABLE_ROUTE_WORKFLOW** | API | `true` | A flag that enable workflow route to manage execute manually and workflow logging. |
|
342
|
-
| **ENABLE_ROUTE_SCHEDULE** | API | `true` | A flag that enable run scheduler. |
|
343
297
|
|
344
298
|
## :rocket: Deployment
|
345
299
|
|
@@ -165,19 +165,23 @@ registry-caller/
|
|
165
165
|
This function will store as module that will import from `WORKFLOW_CORE_REGISTRY_CALLER`
|
166
166
|
value (This config can override by extra parameters with `registry_caller` key).
|
167
167
|
|
168
|
+
> [!NOTE]
|
169
|
+
> You can use Pydantic Model as argument of your caller function. The core workflow
|
170
|
+
> engine will auto use the `model_validate` method before run your caller function.
|
171
|
+
|
168
172
|
```python
|
169
|
-
from ddeutil.workflow import Result, tag
|
173
|
+
from ddeutil.workflow import Result, CallerSecret, tag
|
170
174
|
from ddeutil.workflow.exceptions import StageException
|
171
|
-
from pydantic import BaseModel
|
175
|
+
from pydantic import BaseModel
|
172
176
|
|
173
177
|
class AwsCredential(BaseModel):
|
174
178
|
path: str
|
175
179
|
access_client_id: str
|
176
|
-
access_client_secret:
|
180
|
+
access_client_secret: CallerSecret
|
177
181
|
|
178
182
|
class RestAuth(BaseModel):
|
179
183
|
type: str
|
180
|
-
keys:
|
184
|
+
keys: CallerSecret
|
181
185
|
|
182
186
|
@tag("requests", alias="get-api-with-oauth-to-s3")
|
183
187
|
def get_api_with_oauth_to_s3(
|
@@ -193,6 +197,7 @@ def get_api_with_oauth_to_s3(
|
|
193
197
|
result.trace.info(f"... {method}: {url}")
|
194
198
|
if method != "post":
|
195
199
|
raise StageException(f"RestAPI does not support for {method} action.")
|
200
|
+
# NOTE: If you want to use secret, you can use `auth.keys.get_secret_value()`.
|
196
201
|
return {"records": 1000}
|
197
202
|
```
|
198
203
|
|
@@ -209,45 +214,6 @@ result: Result = workflow.execute(
|
|
209
214
|
)
|
210
215
|
```
|
211
216
|
|
212
|
-
> [!NOTE]
|
213
|
-
> So, this package provide the `Schedule` template for this action, and you can
|
214
|
-
> pass the parameters dynamically for changing align with that running time by
|
215
|
-
> the `release` prefix.
|
216
|
-
>
|
217
|
-
> ```yaml
|
218
|
-
> schedule-run-local-wf:
|
219
|
-
>
|
220
|
-
> # Validate model that use to parsing exists for template file
|
221
|
-
> type: Schedule
|
222
|
-
> workflows:
|
223
|
-
>
|
224
|
-
> # Map existing workflow that want to deploy with scheduler application.
|
225
|
-
> # It allows you to pass release parameter that dynamic change depend on the
|
226
|
-
> # current context of this scheduler application releasing that time.
|
227
|
-
> - name: run-py-local
|
228
|
-
> params:
|
229
|
-
> source-extract: "USD-THB"
|
230
|
-
> run-date: "${{ release.logical_date }}"
|
231
|
-
> ```
|
232
|
-
>
|
233
|
-
> The main method of the `Schedule` model that use to running is `pending`. If you
|
234
|
-
> do not pass the `stop` date on this method, it will use config with
|
235
|
-
> `WORKFLOW_APP_STOP_BOUNDARY_DELTA` key for generate this stop date.
|
236
|
-
>
|
237
|
-
> ```python
|
238
|
-
> from ddeutil.workflow import Schedule
|
239
|
-
>
|
240
|
-
> (
|
241
|
-
> Schedule
|
242
|
-
> .from_conf("schedule-run-local-wf")
|
243
|
-
> .pending(stop=None)
|
244
|
-
> )
|
245
|
-
> ```
|
246
|
-
|
247
|
-
> [!WARNING]
|
248
|
-
> The scheduler feature is the expensive feature of this project. You should
|
249
|
-
> avoid to use it and find a scheduler tool instead.
|
250
|
-
|
251
217
|
## :cookie: Configuration
|
252
218
|
|
253
219
|
The main configuration that use to dynamic changing this workflow engine for your
|
@@ -277,19 +243,6 @@ it will use default value and do not raise any error to you.
|
|
277
243
|
| **TRACE_ENABLE_WRITE** | Log | `false` | |
|
278
244
|
| **AUDIT_PATH** | Log | `./audits` | |
|
279
245
|
| **AUDIT_ENABLE_WRITE** | Log | `true` | A flag that enable logging object saving log to its destination. |
|
280
|
-
| **MAX_PROCESS** | App | `2` | The maximum process worker number that run in scheduler app module. |
|
281
|
-
| **MAX_SCHEDULE_PER_PROCESS** | App | `100` | A schedule per process that run parallel. |
|
282
|
-
| **STOP_BOUNDARY_DELTA** | App | `'{"minutes": 5, "seconds": 20}'` | A time delta value that use to stop scheduler app in json string format. |
|
283
|
-
|
284
|
-
**API Application**:
|
285
|
-
|
286
|
-
This config part use for the workflow application that build from the FastAPI
|
287
|
-
only.
|
288
|
-
|
289
|
-
| Environment | Component | Default | Description |
|
290
|
-
|:---------------------------|:-----------:|---------|------------------------------------------------------------------------------------|
|
291
|
-
| **ENABLE_ROUTE_WORKFLOW** | API | `true` | A flag that enable workflow route to manage execute manually and workflow logging. |
|
292
|
-
| **ENABLE_ROUTE_SCHEDULE** | API | `true` | A flag that enable run scheduler. |
|
293
246
|
|
294
247
|
## :rocket: Deployment
|
295
248
|
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__: str = "0.0.64"
|
@@ -5,12 +5,7 @@
|
|
5
5
|
# ------------------------------------------------------------------------------
|
6
6
|
from .__cron import CronJob, CronRunner
|
7
7
|
from .__types import DictData, DictStr, Matrix, Re, TupleStr
|
8
|
-
from .conf import
|
9
|
-
Config,
|
10
|
-
FileLoad,
|
11
|
-
config,
|
12
|
-
env,
|
13
|
-
)
|
8
|
+
from .conf import *
|
14
9
|
from .event import *
|
15
10
|
from .exceptions import *
|
16
11
|
from .job import *
|
@@ -37,31 +32,7 @@ from .result import (
|
|
37
32
|
Result,
|
38
33
|
Status,
|
39
34
|
)
|
40
|
-
from .reusables import
|
41
|
-
FILTERS,
|
42
|
-
FilterFunc,
|
43
|
-
FilterRegistry,
|
44
|
-
ReturnTagFunc,
|
45
|
-
TagFunc,
|
46
|
-
custom_filter,
|
47
|
-
extract_call,
|
48
|
-
get_args_const,
|
49
|
-
has_template,
|
50
|
-
make_filter_registry,
|
51
|
-
make_registry,
|
52
|
-
map_post_filter,
|
53
|
-
not_in_template,
|
54
|
-
param2template,
|
55
|
-
str2template,
|
56
|
-
tag,
|
57
|
-
)
|
58
|
-
from .scheduler import (
|
59
|
-
Schedule,
|
60
|
-
ScheduleWorkflow,
|
61
|
-
schedule_control,
|
62
|
-
schedule_runner,
|
63
|
-
schedule_task,
|
64
|
-
)
|
35
|
+
from .reusables import *
|
65
36
|
from .stages import *
|
66
37
|
from .utils import *
|
67
38
|
from .workflow import *
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# ------------------------------------------------------------------------------
|
2
|
+
# Copyright (c) 2022 Korawich Anuttra. All rights reserved.
|
3
|
+
# Licensed under the MIT License. See LICENSE in the project root for
|
4
|
+
# license information.
|
5
|
+
# ------------------------------------------------------------------------------
|
6
|
+
from __future__ import annotations
|
7
|
+
|
8
|
+
import contextlib
|
9
|
+
from collections.abc import AsyncIterator
|
10
|
+
|
11
|
+
from dotenv import load_dotenv
|
12
|
+
from fastapi import FastAPI, Request
|
13
|
+
from fastapi import status as st
|
14
|
+
from fastapi.encoders import jsonable_encoder
|
15
|
+
from fastapi.exceptions import RequestValidationError
|
16
|
+
from fastapi.middleware.cors import CORSMiddleware
|
17
|
+
from fastapi.middleware.gzip import GZipMiddleware
|
18
|
+
from fastapi.responses import UJSONResponse
|
19
|
+
|
20
|
+
from ..__about__ import __version__
|
21
|
+
from ..conf import api_config
|
22
|
+
from ..logs import get_logger
|
23
|
+
from .routes import job, log, workflow
|
24
|
+
|
25
|
+
load_dotenv()
|
26
|
+
logger = get_logger("uvicorn.error")
|
27
|
+
|
28
|
+
|
29
|
+
@contextlib.asynccontextmanager
|
30
|
+
async def lifespan(_: FastAPI) -> AsyncIterator[dict[str, list]]:
|
31
|
+
"""Lifespan function for the FastAPI application."""
|
32
|
+
yield {}
|
33
|
+
|
34
|
+
|
35
|
+
app = FastAPI(
|
36
|
+
titile="Workflow",
|
37
|
+
description=(
|
38
|
+
"This is a workflow FastAPI application that use to manage manual "
|
39
|
+
"execute, logging, and schedule workflow via RestAPI."
|
40
|
+
),
|
41
|
+
version=__version__,
|
42
|
+
lifespan=lifespan,
|
43
|
+
default_response_class=UJSONResponse,
|
44
|
+
)
|
45
|
+
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
46
|
+
origins: list[str] = [
|
47
|
+
"http://localhost",
|
48
|
+
"http://localhost:88",
|
49
|
+
"http://localhost:80",
|
50
|
+
]
|
51
|
+
app.add_middleware(
|
52
|
+
CORSMiddleware,
|
53
|
+
allow_origins=origins,
|
54
|
+
allow_credentials=True,
|
55
|
+
allow_methods=["*"],
|
56
|
+
allow_headers=["*"],
|
57
|
+
)
|
58
|
+
|
59
|
+
|
60
|
+
@app.get(path="/", response_class=UJSONResponse)
|
61
|
+
async def health():
|
62
|
+
"""Index view that not return any template without json status."""
|
63
|
+
return {"message": "Workflow already start up with healthy status."}
|
64
|
+
|
65
|
+
|
66
|
+
# NOTE Add the jobs and logs routes by default.
|
67
|
+
app.include_router(job, prefix=api_config.prefix_path)
|
68
|
+
app.include_router(log, prefix=api_config.prefix_path)
|
69
|
+
app.include_router(workflow, prefix=api_config.prefix_path)
|
70
|
+
|
71
|
+
|
72
|
+
@app.exception_handler(RequestValidationError)
|
73
|
+
async def validation_exception_handler(
|
74
|
+
request: Request, exc: RequestValidationError
|
75
|
+
):
|
76
|
+
_ = request
|
77
|
+
return UJSONResponse(
|
78
|
+
status_code=st.HTTP_422_UNPROCESSABLE_ENTITY,
|
79
|
+
content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
|
80
|
+
)
|
81
|
+
|
82
|
+
|
83
|
+
if __name__ == "__main__":
|
84
|
+
import uvicorn
|
85
|
+
|
86
|
+
uvicorn.run(
|
87
|
+
app,
|
88
|
+
host="0.0.0.0",
|
89
|
+
port=80,
|
90
|
+
log_level="DEBUG",
|
91
|
+
)
|
@@ -44,7 +44,6 @@ async def get_traces(
|
|
44
44
|
by_alias=True,
|
45
45
|
exclude_none=True,
|
46
46
|
exclude_unset=True,
|
47
|
-
exclude_defaults=True,
|
48
47
|
)
|
49
48
|
for trace in result.trace.find_traces()
|
50
49
|
],
|
@@ -73,7 +72,6 @@ async def get_trace_with_id(run_id: str):
|
|
73
72
|
by_alias=True,
|
74
73
|
exclude_none=True,
|
75
74
|
exclude_unset=True,
|
76
|
-
exclude_defaults=True,
|
77
75
|
)
|
78
76
|
),
|
79
77
|
}
|
{ddeutil_workflow-0.0.62 → ddeutil_workflow-0.0.64}/src/ddeutil/workflow/api/routes/workflows.py
RENAMED
@@ -56,7 +56,6 @@ async def get_workflow_by_name(name: str) -> DictData:
|
|
56
56
|
by_alias=True,
|
57
57
|
exclude_none=False,
|
58
58
|
exclude_unset=True,
|
59
|
-
exclude_defaults=True,
|
60
59
|
)
|
61
60
|
|
62
61
|
|
@@ -99,7 +98,6 @@ async def get_workflow_audits(name: str):
|
|
99
98
|
by_alias=True,
|
100
99
|
exclude_none=False,
|
101
100
|
exclude_unset=True,
|
102
|
-
exclude_defaults=True,
|
103
101
|
)
|
104
102
|
for audit in get_audit().find_audits(name=name)
|
105
103
|
],
|
@@ -133,6 +131,5 @@ async def get_workflow_release_audit(name: str, release: str):
|
|
133
131
|
by_alias=True,
|
134
132
|
exclude_none=False,
|
135
133
|
exclude_unset=True,
|
136
|
-
exclude_defaults=True,
|
137
134
|
),
|
138
135
|
}
|
@@ -6,11 +6,9 @@
|
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
8
|
import copy
|
9
|
-
import json
|
10
9
|
import os
|
11
10
|
from abc import ABC, abstractmethod
|
12
11
|
from collections.abc import Iterator
|
13
|
-
from datetime import timedelta
|
14
12
|
from functools import cached_property
|
15
13
|
from inspect import isclass
|
16
14
|
from pathlib import Path
|
@@ -18,8 +16,9 @@ from typing import Final, Optional, Protocol, TypeVar, Union
|
|
18
16
|
from zoneinfo import ZoneInfo
|
19
17
|
|
20
18
|
from ddeutil.core import str2bool
|
21
|
-
from ddeutil.io import YamlFlResolve
|
19
|
+
from ddeutil.io import YamlFlResolve, search_env_replace
|
22
20
|
from ddeutil.io.paths import glob_files, is_ignored, read_ignore
|
21
|
+
from pydantic import SecretStr, TypeAdapter
|
23
22
|
|
24
23
|
from .__types import DictData
|
25
24
|
|
@@ -162,28 +161,6 @@ class Config: # pragma: no cov
|
|
162
161
|
def max_queue_complete_hist(self) -> int:
|
163
162
|
return int(env("CORE_MAX_QUEUE_COMPLETE_HIST", "16"))
|
164
163
|
|
165
|
-
# NOTE: App
|
166
|
-
@property
|
167
|
-
def max_schedule_process(self) -> int:
|
168
|
-
return int(env("APP_MAX_PROCESS", "2"))
|
169
|
-
|
170
|
-
@property
|
171
|
-
def max_schedule_per_process(self) -> int:
|
172
|
-
return int(env("APP_MAX_SCHEDULE_PER_PROCESS", "100"))
|
173
|
-
|
174
|
-
@property
|
175
|
-
def stop_boundary_delta(self) -> timedelta:
|
176
|
-
stop_boundary_delta_str: str = env(
|
177
|
-
"APP_STOP_BOUNDARY_DELTA", '{"minutes": 5, "seconds": 20}'
|
178
|
-
)
|
179
|
-
try:
|
180
|
-
return timedelta(**json.loads(stop_boundary_delta_str))
|
181
|
-
except Exception as err:
|
182
|
-
raise ValueError(
|
183
|
-
"Config `WORKFLOW_APP_STOP_BOUNDARY_DELTA` can not parsing to"
|
184
|
-
f"timedelta with {stop_boundary_delta_str}."
|
185
|
-
) from err
|
186
|
-
|
187
164
|
|
188
165
|
class APIConfig:
|
189
166
|
"""API Config object."""
|
@@ -192,14 +169,6 @@ class APIConfig:
|
|
192
169
|
def prefix_path(self) -> str:
|
193
170
|
return env("API_PREFIX_PATH", "/api/v1")
|
194
171
|
|
195
|
-
@property
|
196
|
-
def enable_route_workflow(self) -> bool:
|
197
|
-
return str2bool(env("API_ENABLE_ROUTE_WORKFLOW", "true"))
|
198
|
-
|
199
|
-
@property
|
200
|
-
def enable_route_schedule(self) -> bool:
|
201
|
-
return str2bool(env("API_ENABLE_ROUTE_SCHEDULE", "true"))
|
202
|
-
|
203
172
|
|
204
173
|
class BaseLoad(ABC): # pragma: no cov
|
205
174
|
"""Base Load object is the abstraction object for any Load object that
|
@@ -321,15 +290,16 @@ class FileLoad(BaseLoad):
|
|
321
290
|
*,
|
322
291
|
path: Optional[Path] = None,
|
323
292
|
paths: Optional[list[Path]] = None,
|
324
|
-
excluded: list[str]
|
293
|
+
excluded: Optional[list[str]] = None,
|
325
294
|
extras: Optional[DictData] = None,
|
326
295
|
) -> Iterator[tuple[str, DictData]]:
|
327
296
|
"""Find all data that match with object type in config path. This class
|
328
297
|
method can use include and exclude list of identity name for filter and
|
329
298
|
adds-on.
|
330
299
|
|
331
|
-
:param obj: An object that want to validate matching before
|
332
|
-
|
300
|
+
:param obj: (object) An object that want to validate matching before
|
301
|
+
return.
|
302
|
+
:param path: (Path) A config path object.
|
333
303
|
:param paths: (list[Path]) A list of config path object.
|
334
304
|
:param excluded: An included list of data key that want to filter from
|
335
305
|
data.
|
@@ -474,3 +444,37 @@ class Loader(Protocol): # pragma: no cov
|
|
474
444
|
def finds(
|
475
445
|
cls, obj: object, *args, **kwargs
|
476
446
|
) -> Iterator[tuple[str, DictData]]: ...
|
447
|
+
|
448
|
+
|
449
|
+
def pass_env(value: T) -> T: # pragma: no cov
|
450
|
+
"""Passing environment variable to an input value.
|
451
|
+
|
452
|
+
:param value: (Any) A value that want to pass env var searching.
|
453
|
+
|
454
|
+
:rtype: Any
|
455
|
+
"""
|
456
|
+
if isinstance(value, dict):
|
457
|
+
return {k: pass_env(value[k]) for k in value}
|
458
|
+
elif isinstance(value, (list, tuple, set)):
|
459
|
+
return type(value)([pass_env(i) for i in value])
|
460
|
+
if not isinstance(value, str):
|
461
|
+
return value
|
462
|
+
|
463
|
+
rs: str = search_env_replace(value)
|
464
|
+
return None if rs == "null" else rs
|
465
|
+
|
466
|
+
|
467
|
+
class CallerSecret(SecretStr): # pragma: no cov
|
468
|
+
"""Workflow Secret String model."""
|
469
|
+
|
470
|
+
def get_secret_value(self) -> str:
|
471
|
+
"""Override get_secret_value by adding pass_env before return the
|
472
|
+
real-value.
|
473
|
+
|
474
|
+
:rtype: str
|
475
|
+
"""
|
476
|
+
return pass_env(super().get_secret_value())
|
477
|
+
|
478
|
+
|
479
|
+
# NOTE: Define the caller secret type for use it directly in the caller func.
|
480
|
+
CallerSecretType = TypeAdapter(CallerSecret)
|
@@ -17,6 +17,7 @@ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
|
17
17
|
from pydantic import BaseModel, ConfigDict, Field, ValidationInfo
|
18
18
|
from pydantic.functional_serializers import field_serializer
|
19
19
|
from pydantic.functional_validators import field_validator, model_validator
|
20
|
+
from pydantic_extra_types.timezone_name import TimeZoneName
|
20
21
|
from typing_extensions import Self
|
21
22
|
|
22
23
|
from .__cron import WEEKDAYS, CronJob, CronJobYear, CronRunner, Options
|
@@ -92,7 +93,7 @@ class Crontab(BaseModel):
|
|
92
93
|
),
|
93
94
|
]
|
94
95
|
tz: Annotated[
|
95
|
-
|
96
|
+
TimeZoneName,
|
96
97
|
Field(
|
97
98
|
description="A timezone string value",
|
98
99
|
alias="timezone",
|