ddeutil-workflow 0.0.10__py3-none-any.whl → 0.0.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ddeutil/workflow/__about__.py +1 -1
- ddeutil/workflow/__init__.py +3 -2
- ddeutil/workflow/api.py +84 -16
- ddeutil/workflow/cli.py +14 -14
- ddeutil/workflow/exceptions.py +6 -6
- ddeutil/workflow/job.py +572 -0
- ddeutil/workflow/log.py +10 -10
- ddeutil/workflow/repeat.py +4 -2
- ddeutil/workflow/route.py +165 -36
- ddeutil/workflow/scheduler.py +733 -110
- ddeutil/workflow/stage.py +12 -12
- ddeutil/workflow/utils.py +4 -4
- {ddeutil_workflow-0.0.10.dist-info → ddeutil_workflow-0.0.12.dist-info}/METADATA +66 -70
- ddeutil_workflow-0.0.12.dist-info/RECORD +21 -0
- {ddeutil_workflow-0.0.10.dist-info → ddeutil_workflow-0.0.12.dist-info}/WHEEL +1 -1
- ddeutil/workflow/pipeline.py +0 -1186
- ddeutil_workflow-0.0.10.dist-info/RECORD +0 -21
- {ddeutil_workflow-0.0.10.dist-info → ddeutil_workflow-0.0.12.dist-info}/LICENSE +0 -0
- {ddeutil_workflow-0.0.10.dist-info → ddeutil_workflow-0.0.12.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.10.dist-info → ddeutil_workflow-0.0.12.dist-info}/top_level.txt +0 -0
ddeutil/workflow/__about__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__: str = "0.0.
|
1
|
+
__version__: str = "0.0.12"
|
ddeutil/workflow/__init__.py
CHANGED
@@ -6,12 +6,13 @@
|
|
6
6
|
from .exceptions import (
|
7
7
|
JobException,
|
8
8
|
ParamValueException,
|
9
|
-
PipelineException,
|
10
9
|
StageException,
|
11
10
|
UtilException,
|
11
|
+
WorkflowException,
|
12
12
|
)
|
13
|
+
from .job import Job, Strategy
|
13
14
|
from .on import On, interval2crontab
|
14
|
-
from .
|
15
|
+
from .scheduler import Workflow
|
15
16
|
from .stage import Stage, handler_result
|
16
17
|
from .utils import (
|
17
18
|
Param,
|
ddeutil/workflow/api.py
CHANGED
@@ -6,9 +6,14 @@
|
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
8
|
import asyncio
|
9
|
+
import contextlib
|
9
10
|
import os
|
10
11
|
import uuid
|
12
|
+
from collections.abc import AsyncIterator
|
13
|
+
from datetime import datetime, timedelta
|
11
14
|
from queue import Empty, Queue
|
15
|
+
from threading import Thread
|
16
|
+
from typing import TypedDict
|
12
17
|
|
13
18
|
from ddeutil.core import str2bool
|
14
19
|
from dotenv import load_dotenv
|
@@ -19,12 +24,55 @@ from pydantic import BaseModel
|
|
19
24
|
|
20
25
|
from .__about__ import __version__
|
21
26
|
from .log import get_logger
|
22
|
-
from .repeat import repeat_every
|
27
|
+
from .repeat import repeat_at, repeat_every
|
28
|
+
from .scheduler import WorkflowTask
|
23
29
|
|
24
30
|
load_dotenv()
|
25
31
|
logger = get_logger("ddeutil.workflow")
|
26
32
|
|
27
33
|
|
34
|
+
class State(TypedDict):
|
35
|
+
upper_queue: Queue
|
36
|
+
upper_result: dict[str, str]
|
37
|
+
scheduler: list[str]
|
38
|
+
workflow_threads: dict[str, Thread]
|
39
|
+
workflow_tasks: list[WorkflowTask]
|
40
|
+
workflow_queue: dict[str, list[datetime]]
|
41
|
+
workflow_running: dict[str, list[datetime]]
|
42
|
+
|
43
|
+
|
44
|
+
@contextlib.asynccontextmanager
|
45
|
+
async def lifespan(a: FastAPI) -> AsyncIterator[State]:
|
46
|
+
a.state.upper_queue = Queue()
|
47
|
+
a.state.upper_result = {}
|
48
|
+
a.state.scheduler = []
|
49
|
+
a.state.workflow_threads = {}
|
50
|
+
a.state.workflow_tasks = []
|
51
|
+
a.state.workflow_queue = {}
|
52
|
+
a.state.workflow_running = {}
|
53
|
+
|
54
|
+
await asyncio.create_task(broker_upper_messages())
|
55
|
+
|
56
|
+
yield {
|
57
|
+
"upper_queue": a.state.upper_queue,
|
58
|
+
"upper_result": a.state.upper_result,
|
59
|
+
# NOTE: Scheduler value should be contain a key of workflow workflow and
|
60
|
+
# list of datetime of queue and running.
|
61
|
+
#
|
62
|
+
# ... {
|
63
|
+
# ... '<workflow-name>': (
|
64
|
+
# ... [<running-datetime>, ...], [<queue-datetime>, ...]
|
65
|
+
# ... )
|
66
|
+
# ... }
|
67
|
+
#
|
68
|
+
"scheduler": a.state.scheduler,
|
69
|
+
"workflow_queue": a.state.workflow_queue,
|
70
|
+
"workflow_running": a.state.workflow_running,
|
71
|
+
"workflow_threads": a.state.workflow_threads,
|
72
|
+
"workflow_tasks": a.state.workflow_tasks,
|
73
|
+
}
|
74
|
+
|
75
|
+
|
28
76
|
app = FastAPI(
|
29
77
|
titile="Workflow API",
|
30
78
|
description=(
|
@@ -32,47 +80,52 @@ app = FastAPI(
|
|
32
80
|
"execute or schedule workflow via RestAPI."
|
33
81
|
),
|
34
82
|
version=__version__,
|
83
|
+
lifespan=lifespan,
|
84
|
+
default_response_class=UJSONResponse,
|
35
85
|
)
|
36
86
|
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
37
|
-
app.queue = Queue()
|
38
|
-
app.output_dict = {}
|
39
|
-
app.queue_limit = 5
|
40
87
|
|
41
88
|
|
42
|
-
@app.on_event("startup")
|
43
89
|
@repeat_every(seconds=10)
|
44
|
-
def broker_upper_messages():
|
90
|
+
async def broker_upper_messages():
|
45
91
|
"""Broker for receive message from the `/upper` path and change it to upper
|
46
92
|
case. This broker use interval running in background every 10 seconds.
|
47
93
|
"""
|
48
|
-
for _ in range(
|
94
|
+
for _ in range(10):
|
49
95
|
try:
|
50
|
-
obj = app.
|
51
|
-
app.
|
52
|
-
logger.info(f"Upper message: {app.
|
96
|
+
obj = app.state.upper_queue.get_nowait()
|
97
|
+
app.state.upper_result[obj["request_id"]] = obj["text"].upper()
|
98
|
+
logger.info(f"Upper message: {app.state.upper_result}")
|
53
99
|
except Empty:
|
54
100
|
pass
|
101
|
+
await asyncio.sleep(0.0001)
|
55
102
|
|
56
103
|
|
57
104
|
class Payload(BaseModel):
|
58
105
|
text: str
|
59
106
|
|
60
107
|
|
61
|
-
async def get_result(request_id):
|
108
|
+
async def get_result(request_id: str) -> dict[str, str]:
|
62
109
|
"""Get data from output dict that global."""
|
63
110
|
while True:
|
64
|
-
if request_id in app.
|
65
|
-
result = app.
|
66
|
-
del app.
|
111
|
+
if request_id in app.state.upper_result:
|
112
|
+
result: str = app.state.upper_result[request_id]
|
113
|
+
del app.state.upper_result[request_id]
|
67
114
|
return {"message": result}
|
68
115
|
await asyncio.sleep(0.0025)
|
69
116
|
|
70
117
|
|
71
|
-
@app.
|
118
|
+
@app.get("/")
|
119
|
+
@app.get("/api")
|
120
|
+
async def health():
|
121
|
+
return {"message": "Workflow API already start up"}
|
122
|
+
|
123
|
+
|
124
|
+
@app.post("/api")
|
72
125
|
async def message_upper(payload: Payload):
|
73
126
|
"""Convert message from any case to the upper case."""
|
74
127
|
request_id: str = str(uuid.uuid4())
|
75
|
-
app.
|
128
|
+
app.state.upper_queue.put(
|
76
129
|
{"text": payload.text, "request_id": request_id},
|
77
130
|
)
|
78
131
|
return await get_result(request_id)
|
@@ -85,5 +138,20 @@ if str2bool(os.getenv("WORKFLOW_API_ENABLE_ROUTE_WORKFLOW", "true")):
|
|
85
138
|
|
86
139
|
if str2bool(os.getenv("WORKFLOW_API_ENABLE_ROUTE_SCHEDULE", "true")):
|
87
140
|
from .route import schedule
|
141
|
+
from .scheduler import workflow_task
|
88
142
|
|
89
143
|
app.include_router(schedule)
|
144
|
+
|
145
|
+
@schedule.on_event("startup")
|
146
|
+
@repeat_at(cron="* * * * *", delay=2)
|
147
|
+
def schedule_broker_up():
|
148
|
+
logger.debug(
|
149
|
+
f"[SCHEDULER]: Start listening schedule from queue "
|
150
|
+
f"{app.state.scheduler}"
|
151
|
+
)
|
152
|
+
if app.state.workflow_tasks:
|
153
|
+
workflow_task(
|
154
|
+
app.state.workflow_tasks,
|
155
|
+
stop=datetime.now() + timedelta(minutes=1),
|
156
|
+
threads=app.state.workflow_threads,
|
157
|
+
)
|
ddeutil/workflow/cli.py
CHANGED
@@ -29,21 +29,21 @@ cli.add_typer(
|
|
29
29
|
|
30
30
|
@cli.command()
|
31
31
|
def run(
|
32
|
-
|
32
|
+
workflow: Annotated[
|
33
33
|
str,
|
34
|
-
Argument(help="A
|
34
|
+
Argument(help="A workflow name that want to run manually"),
|
35
35
|
],
|
36
36
|
params: Annotated[
|
37
37
|
str,
|
38
38
|
Argument(
|
39
|
-
help="A json string for parameters of this
|
39
|
+
help="A json string for parameters of this workflow execution."
|
40
40
|
),
|
41
41
|
],
|
42
42
|
):
|
43
|
-
"""Run
|
44
|
-
to receive with
|
43
|
+
"""Run workflow workflow manually with an input custom parameters that able
|
44
|
+
to receive with workflow params config.
|
45
45
|
"""
|
46
|
-
logger.info(f"Running
|
46
|
+
logger.info(f"Running workflow name: {workflow}")
|
47
47
|
logger.info(f"... with Parameters: {json.dumps(json.loads(params))}")
|
48
48
|
|
49
49
|
|
@@ -63,7 +63,7 @@ def schedule(
|
|
63
63
|
externals: Annotated[
|
64
64
|
Optional[str],
|
65
65
|
Argument(
|
66
|
-
help="A json string for parameters of this
|
66
|
+
help="A json string for parameters of this workflow execution."
|
67
67
|
),
|
68
68
|
] = None,
|
69
69
|
):
|
@@ -77,20 +77,20 @@ def schedule(
|
|
77
77
|
tz=ZoneInfo(os.getenv("WORKFLOW_CORE_TIMEZONE", "UTC"))
|
78
78
|
)
|
79
79
|
|
80
|
-
from .scheduler import
|
80
|
+
from .scheduler import workflow_runner
|
81
81
|
|
82
82
|
# NOTE: Start running workflow scheduler application.
|
83
|
-
workflow_rs: list[str] =
|
83
|
+
workflow_rs: list[str] = workflow_runner(
|
84
84
|
stop=stop, excluded=excluded, externals=json.loads(externals)
|
85
85
|
)
|
86
86
|
logger.info(f"Application run success: {workflow_rs}")
|
87
87
|
|
88
88
|
|
89
|
-
@cli_log.command("
|
90
|
-
def
|
89
|
+
@cli_log.command("workflow-get")
|
90
|
+
def workflow_log_get(
|
91
91
|
name: Annotated[
|
92
92
|
str,
|
93
|
-
Argument(help="A
|
93
|
+
Argument(help="A workflow name that want to getting log"),
|
94
94
|
],
|
95
95
|
limit: Annotated[
|
96
96
|
int,
|
@@ -113,8 +113,8 @@ class LogMode(str, Enum):
|
|
113
113
|
delete = "delete"
|
114
114
|
|
115
115
|
|
116
|
-
@cli_log.command("
|
117
|
-
def
|
116
|
+
@cli_log.command("workflow-delete")
|
117
|
+
def workflow_log_delete(
|
118
118
|
mode: Annotated[
|
119
119
|
LogMode,
|
120
120
|
Argument(case_sensitive=True),
|
ddeutil/workflow/exceptions.py
CHANGED
@@ -6,22 +6,22 @@
|
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
8
|
|
9
|
-
class
|
9
|
+
class BaseWorkflowException(Exception): ...
|
10
10
|
|
11
11
|
|
12
|
-
class UtilException(
|
12
|
+
class UtilException(BaseWorkflowException): ...
|
13
13
|
|
14
14
|
|
15
|
-
class StageException(
|
15
|
+
class StageException(BaseWorkflowException): ...
|
16
16
|
|
17
17
|
|
18
|
-
class JobException(
|
18
|
+
class JobException(BaseWorkflowException): ...
|
19
19
|
|
20
20
|
|
21
|
-
class
|
21
|
+
class WorkflowException(BaseWorkflowException): ...
|
22
22
|
|
23
23
|
|
24
|
-
class
|
24
|
+
class WorkflowFailException(WorkflowException): ...
|
25
25
|
|
26
26
|
|
27
27
|
class ParamValueException(WorkflowException): ...
|