ddeutil-workflow 0.0.9__py3-none-any.whl → 0.0.11__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/route.py CHANGED
@@ -1,71 +1,221 @@
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
+ # ------------------------------------------------------------------------------
1
6
  from __future__ import annotations
2
7
 
3
- from fastapi import APIRouter, Request
8
+ import copy
9
+ import os
10
+ from datetime import datetime, timedelta
11
+ from typing import Any
12
+ from zoneinfo import ZoneInfo
13
+
14
+ from fastapi import APIRouter, HTTPException, Request
4
15
  from fastapi import status as st
16
+ from fastapi.responses import UJSONResponse
17
+ from pydantic import BaseModel
5
18
 
19
+ from . import Workflow
20
+ from .__types import DictData
6
21
  from .log import get_logger
22
+ from .scheduler import Schedule
23
+ from .utils import Loader, Result
24
+
25
+ logger = get_logger("ddeutil.workflow")
26
+ workflow = APIRouter(
27
+ prefix="/api/workflow",
28
+ tags=["workflow"],
29
+ default_response_class=UJSONResponse,
30
+ )
31
+ schedule = APIRouter(
32
+ prefix="/api/schedule",
33
+ tags=["schedule"],
34
+ default_response_class=UJSONResponse,
35
+ )
7
36
 
8
- logger = get_logger(__name__)
9
- workflow = APIRouter(prefix="/wf", tags=["workflow"])
37
+ ListDate = list[datetime]
10
38
 
11
39
 
12
40
  @workflow.get("/")
13
41
  async def get_workflows():
14
- return {"message": "getting all pipelines: []"}
42
+ """Return all workflow workflows that exists in config path."""
43
+ workflows: DictData = Loader.finds(Workflow)
44
+ return {
45
+ "message": f"getting all workflows: {workflows}",
46
+ }
15
47
 
16
48
 
17
49
  @workflow.get("/{name}")
18
- async def get_workflow(name: str):
19
- return {"message": f"getting pipeline {name}"}
50
+ async def get_workflow(name: str) -> DictData:
51
+ """Return model of workflow that passing an input workflow name."""
52
+ try:
53
+ wf: Workflow = Workflow.from_loader(name=name, externals={})
54
+ except ValueError as err:
55
+ logger.exception(err)
56
+ raise HTTPException(
57
+ status_code=st.HTTP_404_NOT_FOUND,
58
+ detail=(
59
+ f"Workflow workflow name: {name!r} does not found in /conf path"
60
+ ),
61
+ ) from None
62
+ return wf.model_dump(
63
+ by_alias=True,
64
+ exclude_none=True,
65
+ exclude_unset=True,
66
+ exclude_defaults=True,
67
+ )
68
+
69
+
70
+ class ExecutePayload(BaseModel):
71
+ params: dict[str, Any]
72
+
73
+
74
+ @workflow.post("/{name}/execute", status_code=st.HTTP_202_ACCEPTED)
75
+ async def execute_workflow(name: str, payload: ExecutePayload) -> DictData:
76
+ """Return model of workflow that passing an input workflow name."""
77
+ try:
78
+ wf: Workflow = Workflow.from_loader(name=name, externals={})
79
+ except ValueError:
80
+ raise HTTPException(
81
+ status_code=st.HTTP_404_NOT_FOUND,
82
+ detail=(
83
+ f"Workflow workflow name: {name!r} does not found in /conf path"
84
+ ),
85
+ ) from None
86
+
87
+ # NOTE: Start execute manually
88
+ rs: Result = wf.execute(params=payload.params)
89
+
90
+ return rs.model_dump(
91
+ by_alias=True,
92
+ exclude_none=True,
93
+ exclude_unset=True,
94
+ exclude_defaults=True,
95
+ )
20
96
 
21
97
 
22
98
  @workflow.get("/{name}/logs")
23
99
  async def get_workflow_logs(name: str):
24
- return {"message": f"getting pipeline {name} logs"}
100
+ return {"message": f"getting workflow {name!r} logs"}
25
101
 
26
102
 
27
103
  @workflow.get("/{name}/logs/{release}")
28
104
  async def get_workflow_release_log(name: str, release: str):
29
- return {"message": f"getting pipeline {name} log in release {release}"}
105
+ return {"message": f"getting workflow {name!r} log in release {release}"}
30
106
 
31
107
 
32
- @workflow.delete(
33
- "/{name}/logs/{release}",
34
- status_code=st.HTTP_204_NO_CONTENT,
35
- )
108
+ @workflow.delete("/{name}/logs/{release}", status_code=st.HTTP_204_NO_CONTENT)
36
109
  async def del_workflow_release_log(name: str, release: str):
37
- return {"message": f"getting pipeline {name} log in release {release}"}
38
-
39
-
40
- class JobNotFoundError(Exception):
41
- pass
110
+ return {"message": f"deleted workflow {name!r} log in release {release}"}
42
111
 
43
112
 
44
- schedule = APIRouter(prefix="/schedule", tags=["schedule"])
45
-
46
-
47
- @schedule.post("/", name="scheduler:add_job", status_code=st.HTTP_201_CREATED)
48
- async def add_job(request: Request):
49
- return {"job": f"{request}"}
50
-
51
-
52
- @schedule.get("/", name="scheduler:get_jobs", response_model=list)
53
- async def get_jobs(request: Request):
54
- jobs = request.app.scheduler.get_jobs()
55
- jobs = [
56
- {k: v for k, v in job.__getstate__().items() if k != "trigger"}
57
- for job in jobs
58
- ]
59
- return jobs
60
-
61
-
62
- @schedule.delete("/{job_id}", name="scheduler:remove_job")
63
- async def remove_job(request: Request, job_id: str):
113
+ @schedule.get("/{name}")
114
+ async def get_schedule(name: str):
115
+ try:
116
+ sch: Schedule = Schedule.from_loader(name=name, externals={})
117
+ except ValueError:
118
+ raise HTTPException(
119
+ status_code=st.HTTP_404_NOT_FOUND,
120
+ detail=f"Schedule name: {name!r} does not found in /conf path",
121
+ ) from None
122
+ return sch.model_dump(
123
+ by_alias=True,
124
+ exclude_none=True,
125
+ exclude_unset=True,
126
+ exclude_defaults=True,
127
+ )
128
+
129
+
130
+ @schedule.get("/deploy")
131
+ async def get_deploy_schedulers(request: Request):
132
+ snapshot = copy.deepcopy(request.state.scheduler)
133
+ return {"schedule": snapshot}
134
+
135
+
136
+ @schedule.get("/deploy/{name}")
137
+ async def get_deploy_scheduler(request: Request, name: str):
138
+ if name in request.state.scheduler:
139
+ sch = Schedule.from_loader(name)
140
+ getter: list[dict[str, dict[str, list[datetime]]]] = []
141
+ for wf in sch.workflows:
142
+ getter.append(
143
+ {
144
+ wf.name: {
145
+ "queue": copy.deepcopy(
146
+ request.state.workflow_queue[wf.name]
147
+ ),
148
+ "running": copy.deepcopy(
149
+ request.state.workflow_running[wf.name]
150
+ ),
151
+ }
152
+ }
153
+ )
154
+ return {
155
+ "message": f"getting {name!r} to schedule listener.",
156
+ "scheduler": getter,
157
+ }
158
+ raise HTTPException(
159
+ status_code=st.HTTP_404_NOT_FOUND,
160
+ detail=f"Does not found {name!r} in schedule listener",
161
+ )
162
+
163
+
164
+ @schedule.post("/deploy/{name}")
165
+ async def add_deploy_scheduler(request: Request, name: str):
166
+ """Adding schedule name to application state store."""
167
+ if name in request.state.scheduler:
168
+ raise HTTPException(
169
+ status_code=st.HTTP_302_FOUND,
170
+ detail="This schedule already exists in scheduler list.",
171
+ )
172
+
173
+ request.state.scheduler.append(name)
174
+
175
+ tz: ZoneInfo = ZoneInfo(os.getenv("WORKFLOW_CORE_TIMEZONE", "UTC"))
176
+ start_date: datetime = datetime.now(tz=tz)
177
+ start_date_waiting: datetime = (start_date + timedelta(minutes=1)).replace(
178
+ second=0, microsecond=0
179
+ )
180
+
181
+ # NOTE: Create pair of workflow and on from schedule model.
64
182
  try:
65
- deleted = request.app.scheduler.remove_job(job_id=job_id)
66
- logger.debug(f"Job {job_id} deleted: {deleted}")
67
- return {"job": f"{job_id}"}
68
- except AttributeError as err:
69
- raise JobNotFoundError(
70
- f"No job by the id of {job_id} was found"
71
- ) from err
183
+ sch = Schedule.from_loader(name)
184
+ except ValueError as e:
185
+ request.state.scheduler.remove(name)
186
+ logger.exception(e)
187
+ raise HTTPException(
188
+ status_code=st.HTTP_404_NOT_FOUND,
189
+ detail=str(e),
190
+ ) from None
191
+ request.state.workflow_tasks.extend(
192
+ sch.tasks(
193
+ start_date_waiting,
194
+ queue=request.state.workflow_queue,
195
+ running=request.state.workflow_running,
196
+ ),
197
+ )
198
+ return {"message": f"adding {name!r} to schedule listener."}
199
+
200
+
201
+ @schedule.delete("/deploy/{name}")
202
+ async def del_deploy_scheduler(request: Request, name: str):
203
+ if name in request.state.scheduler:
204
+ request.state.scheduler.remove(name)
205
+ sche = Schedule.from_loader(name)
206
+ for workflow_task in sche.tasks(datetime.now(), {}, {}):
207
+ request.state.workflow_tasks.remove(workflow_task)
208
+
209
+ for wf in sche.workflows:
210
+ if wf in request.state.workflow_queue:
211
+ request.state.workflow_queue.pop(wf, {})
212
+
213
+ if wf in request.state.workflow_running:
214
+ request.state.workflow_running.pop(wf, {})
215
+
216
+ return {"message": f"deleted {name!r} to schedule listener."}
217
+
218
+ raise HTTPException(
219
+ status_code=st.HTTP_404_NOT_FOUND,
220
+ detail=f"Does not found {name!r} in schedule listener",
221
+ )