ddeutil-workflow 0.0.7__py3-none-any.whl → 0.0.9__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 -14
- ddeutil/workflow/api.py +44 -75
- ddeutil/workflow/cli.py +51 -0
- ddeutil/workflow/cron.py +713 -0
- ddeutil/workflow/exceptions.py +1 -4
- ddeutil/workflow/loader.py +65 -13
- ddeutil/workflow/log.py +164 -17
- ddeutil/workflow/on.py +18 -15
- ddeutil/workflow/pipeline.py +644 -235
- ddeutil/workflow/repeat.py +9 -5
- ddeutil/workflow/route.py +30 -37
- ddeutil/workflow/scheduler.py +398 -659
- ddeutil/workflow/stage.py +269 -103
- ddeutil/workflow/utils.py +198 -29
- ddeutil_workflow-0.0.9.dist-info/METADATA +273 -0
- ddeutil_workflow-0.0.9.dist-info/RECORD +22 -0
- {ddeutil_workflow-0.0.7.dist-info → ddeutil_workflow-0.0.9.dist-info}/WHEEL +1 -1
- ddeutil_workflow-0.0.9.dist-info/entry_points.txt +2 -0
- ddeutil/workflow/app.py +0 -41
- ddeutil_workflow-0.0.7.dist-info/METADATA +0 -341
- ddeutil_workflow-0.0.7.dist-info/RECORD +0 -20
- {ddeutil_workflow-0.0.7.dist-info → ddeutil_workflow-0.0.9.dist-info}/LICENSE +0 -0
- {ddeutil_workflow-0.0.7.dist-info → ddeutil_workflow-0.0.9.dist-info}/top_level.txt +0 -0
ddeutil/workflow/__about__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__: str = "0.0.
|
1
|
+
__version__: str = "0.0.9"
|
ddeutil/workflow/__init__.py
CHANGED
@@ -10,22 +10,11 @@ from .exceptions import (
|
|
10
10
|
StageException,
|
11
11
|
UtilException,
|
12
12
|
)
|
13
|
-
from .on import
|
14
|
-
from .pipeline import Job, Pipeline
|
15
|
-
from .stage import
|
16
|
-
BashStage,
|
17
|
-
EmptyStage,
|
18
|
-
HookStage,
|
19
|
-
PyStage,
|
20
|
-
Stage,
|
21
|
-
TriggerStage,
|
22
|
-
)
|
13
|
+
from .on import On, interval2crontab
|
14
|
+
from .pipeline import Job, Pipeline, Strategy
|
15
|
+
from .stage import Stage, handler_result
|
23
16
|
from .utils import (
|
24
|
-
ChoiceParam,
|
25
|
-
DatetimeParam,
|
26
|
-
IntParam,
|
27
17
|
Param,
|
28
|
-
StrParam,
|
29
18
|
dash2underscore,
|
30
19
|
param2template,
|
31
20
|
)
|
ddeutil/workflow/api.py
CHANGED
@@ -6,104 +6,62 @@
|
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
8
|
import asyncio
|
9
|
-
import
|
10
|
-
import
|
9
|
+
import logging
|
10
|
+
import os
|
11
11
|
import uuid
|
12
|
-
from
|
13
|
-
|
14
|
-
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
|
18
|
-
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
19
|
-
from fastapi import BackgroundTasks, FastAPI
|
12
|
+
from queue import Empty, Queue
|
13
|
+
|
14
|
+
from ddeutil.core import str2bool
|
15
|
+
from dotenv import load_dotenv
|
16
|
+
from fastapi import FastAPI
|
20
17
|
from fastapi.middleware.gzip import GZipMiddleware
|
21
18
|
from fastapi.responses import UJSONResponse
|
22
19
|
from pydantic import BaseModel
|
23
20
|
|
24
|
-
from .log import get_logger
|
25
21
|
from .repeat import repeat_every
|
26
|
-
from .route import schedule_route, workflow_route
|
27
22
|
|
28
|
-
|
23
|
+
load_dotenv()
|
24
|
+
logger = logging.getLogger(__name__)
|
25
|
+
logging.basicConfig(
|
26
|
+
level=logging.DEBUG,
|
27
|
+
format=(
|
28
|
+
"%(asctime)s.%(msecs)03d (%(name)-10s, %(process)-5d, %(thread)-5d) "
|
29
|
+
"[%(levelname)-7s] %(message)-120s (%(filename)s:%(lineno)s)"
|
30
|
+
),
|
31
|
+
handlers=[logging.StreamHandler()],
|
32
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
33
|
+
)
|
34
|
+
|
35
|
+
|
36
|
+
app = FastAPI()
|
37
|
+
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
38
|
+
app.queue = Queue()
|
39
|
+
app.output_dict = {}
|
40
|
+
app.queue_limit = 2
|
29
41
|
|
30
42
|
|
43
|
+
@app.on_event("startup")
|
44
|
+
@repeat_every(seconds=10, logger=logger)
|
31
45
|
def broker_upper_messages():
|
46
|
+
"""Broker for receive message from the `/upper` path and change it to upper
|
47
|
+
case.
|
48
|
+
"""
|
32
49
|
for _ in range(app.queue_limit):
|
33
50
|
try:
|
34
51
|
obj = app.queue.get_nowait()
|
35
52
|
app.output_dict[obj["request_id"]] = obj["text"].upper()
|
36
53
|
logger.info(f"Upper message: {app.output_dict}")
|
37
|
-
except
|
54
|
+
except Empty:
|
38
55
|
pass
|
39
56
|
|
40
57
|
|
41
|
-
jobstores = {
|
42
|
-
"default": MemoryJobStore(),
|
43
|
-
"sqlite": SQLAlchemyJobStore(url="sqlite:///jobs-store.sqlite"),
|
44
|
-
}
|
45
|
-
executors = {
|
46
|
-
"default": {"type": "threadpool", "max_workers": 5},
|
47
|
-
"processpool": ProcessPoolExecutor(max_workers=5),
|
48
|
-
}
|
49
|
-
scheduler = AsyncIOScheduler(
|
50
|
-
jobstores=jobstores,
|
51
|
-
executors=executors,
|
52
|
-
timezone="Asia/Bangkok",
|
53
|
-
)
|
54
|
-
|
55
|
-
|
56
|
-
@asynccontextmanager
|
57
|
-
async def lifespan(_: FastAPI):
|
58
|
-
scheduler.start()
|
59
|
-
yield
|
60
|
-
scheduler.shutdown(wait=False)
|
61
|
-
|
62
|
-
|
63
|
-
app = FastAPI(lifespan=lifespan)
|
64
|
-
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
65
|
-
app.include_router(schedule_route)
|
66
|
-
app.include_router(workflow_route)
|
67
|
-
|
68
|
-
app.scheduler = scheduler
|
69
|
-
app.scheduler.add_job(
|
70
|
-
broker_upper_messages,
|
71
|
-
"interval",
|
72
|
-
seconds=10,
|
73
|
-
)
|
74
|
-
app.queue = queue.Queue()
|
75
|
-
app.output_dict = {}
|
76
|
-
app.queue_limit = 2
|
77
|
-
|
78
|
-
|
79
|
-
def write_pipeline(task_id: str, message=""):
|
80
|
-
logger.info(f"{task_id} : {message}")
|
81
|
-
time.sleep(5)
|
82
|
-
logger.info(f"{task_id} : run task successfully!!!")
|
83
|
-
|
84
|
-
|
85
|
-
@app.post("/schedule/{name}", response_class=UJSONResponse)
|
86
|
-
async def send_schedule(name: str, background_tasks: BackgroundTasks):
|
87
|
-
background_tasks.add_task(
|
88
|
-
write_pipeline,
|
89
|
-
name,
|
90
|
-
message=f"some message for {name}",
|
91
|
-
)
|
92
|
-
await fetch_current_time()
|
93
|
-
return {"message": f"Schedule sent {name!r} in the background"}
|
94
|
-
|
95
|
-
|
96
|
-
@repeat_every(seconds=2, max_repetitions=3)
|
97
|
-
async def fetch_current_time():
|
98
|
-
logger.info(f"Fetch: {datetime.now()}")
|
99
|
-
|
100
|
-
|
101
58
|
class Payload(BaseModel):
|
102
59
|
text: str
|
103
60
|
|
104
61
|
|
105
62
|
async def get_result(request_id):
|
106
|
-
|
63
|
+
"""Get data from output dict that global."""
|
64
|
+
while True:
|
107
65
|
if request_id in app.output_dict:
|
108
66
|
result = app.output_dict[request_id]
|
109
67
|
del app.output_dict[request_id]
|
@@ -118,3 +76,14 @@ async def message_upper(payload: Payload):
|
|
118
76
|
{"text": payload.text, "request_id": request_id},
|
119
77
|
)
|
120
78
|
return await get_result(request_id)
|
79
|
+
|
80
|
+
|
81
|
+
if str2bool(os.getenv("WORKFLOW_API_ENABLE_ROUTE_WORKFLOW", "true")):
|
82
|
+
from .route import workflow
|
83
|
+
|
84
|
+
app.include_router(workflow)
|
85
|
+
|
86
|
+
if str2bool(os.getenv("WORKFLOW_API_ENABLE_ROUTE_SCHEDULE", "true")):
|
87
|
+
from .route import schedule
|
88
|
+
|
89
|
+
app.include_router(schedule)
|
ddeutil/workflow/cli.py
ADDED
@@ -0,0 +1,51 @@
|
|
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
|
+
from typing import Optional
|
9
|
+
|
10
|
+
from typer import Typer
|
11
|
+
|
12
|
+
cli: Typer = Typer()
|
13
|
+
state = {"verbose": False}
|
14
|
+
|
15
|
+
|
16
|
+
@cli.command()
|
17
|
+
def run(pipeline: str):
|
18
|
+
"""Run workflow manually"""
|
19
|
+
if state["verbose"]:
|
20
|
+
print("About to create a user")
|
21
|
+
|
22
|
+
print(f"Creating user: {pipeline}")
|
23
|
+
|
24
|
+
if state["verbose"]:
|
25
|
+
print("Just created a user")
|
26
|
+
|
27
|
+
|
28
|
+
@cli.command()
|
29
|
+
def schedule(exclude: Optional[str]):
|
30
|
+
"""Start workflow scheduler"""
|
31
|
+
if state["verbose"]:
|
32
|
+
print("About to delete a user")
|
33
|
+
|
34
|
+
print(f"Deleting user: {exclude}")
|
35
|
+
|
36
|
+
if state["verbose"]:
|
37
|
+
print("Just deleted a user")
|
38
|
+
|
39
|
+
|
40
|
+
@cli.callback()
|
41
|
+
def main(verbose: bool = False):
|
42
|
+
"""
|
43
|
+
Manage workflow with CLI.
|
44
|
+
"""
|
45
|
+
if verbose:
|
46
|
+
print("Will write verbose output")
|
47
|
+
state["verbose"] = True
|
48
|
+
|
49
|
+
|
50
|
+
if __name__ == "__main__":
|
51
|
+
cli()
|