ddeutil-workflow 0.0.67__py3-none-any.whl → 0.0.69__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 +14 -12
- ddeutil/workflow/api/__init__.py +20 -19
- ddeutil/workflow/api/log_conf.py +59 -0
- ddeutil/workflow/api/routes/__init__.py +3 -3
- ddeutil/workflow/api/routes/job.py +42 -16
- ddeutil/workflow/api/routes/logs.py +8 -8
- ddeutil/workflow/api/routes/workflows.py +12 -11
- ddeutil/workflow/audits.py +374 -0
- ddeutil/workflow/cli.py +80 -15
- ddeutil/workflow/conf.py +9 -52
- ddeutil/workflow/event.py +6 -5
- ddeutil/workflow/result.py +10 -1
- ddeutil/workflow/stages.py +38 -9
- ddeutil/workflow/{logs.py → traces.py} +168 -387
- ddeutil/workflow/utils.py +1 -52
- ddeutil/workflow/workflow.py +8 -16
- {ddeutil_workflow-0.0.67.dist-info → ddeutil_workflow-0.0.69.dist-info}/METADATA +31 -29
- ddeutil_workflow-0.0.69.dist-info/RECORD +30 -0
- {ddeutil_workflow-0.0.67.dist-info → ddeutil_workflow-0.0.69.dist-info}/WHEEL +1 -1
- ddeutil/workflow/api/logs.py +0 -59
- ddeutil_workflow-0.0.67.dist-info/RECORD +0 -29
- {ddeutil_workflow-0.0.67.dist-info → ddeutil_workflow-0.0.69.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.67.dist-info → ddeutil_workflow-0.0.69.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.67.dist-info → ddeutil_workflow-0.0.69.dist-info}/top_level.txt +0 -0
ddeutil/workflow/__about__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__: str = "0.0.
|
1
|
+
__version__: str = "0.0.69"
|
ddeutil/workflow/__init__.py
CHANGED
@@ -5,23 +5,16 @@
|
|
5
5
|
# ------------------------------------------------------------------------------
|
6
6
|
from .__cron import CronJob, CronRunner
|
7
7
|
from .__types import DictData, DictStr, Matrix, Re, TupleStr
|
8
|
-
from .
|
9
|
-
from .errors import *
|
10
|
-
from .event import *
|
11
|
-
from .job import *
|
12
|
-
from .logs import (
|
8
|
+
from .audits import (
|
13
9
|
Audit,
|
14
10
|
AuditModel,
|
15
11
|
FileAudit,
|
16
|
-
FileTrace,
|
17
|
-
Trace,
|
18
|
-
TraceData,
|
19
|
-
TraceMeta,
|
20
|
-
TraceModel,
|
21
12
|
get_audit,
|
22
|
-
get_dt_tznow,
|
23
|
-
get_trace,
|
24
13
|
)
|
14
|
+
from .conf import *
|
15
|
+
from .errors import *
|
16
|
+
from .event import *
|
17
|
+
from .job import *
|
25
18
|
from .params import *
|
26
19
|
from .result import (
|
27
20
|
CANCEL,
|
@@ -34,5 +27,14 @@ from .result import (
|
|
34
27
|
)
|
35
28
|
from .reusables import *
|
36
29
|
from .stages import *
|
30
|
+
from .traces import (
|
31
|
+
ConsoleTrace,
|
32
|
+
FileTrace,
|
33
|
+
Trace,
|
34
|
+
TraceData,
|
35
|
+
TraceMeta,
|
36
|
+
TraceModel,
|
37
|
+
get_trace,
|
38
|
+
)
|
37
39
|
from .utils import *
|
38
40
|
from .workflow import *
|
ddeutil/workflow/api/__init__.py
CHANGED
@@ -6,6 +6,7 @@
|
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
8
|
import contextlib
|
9
|
+
import logging
|
9
10
|
from collections.abc import AsyncIterator
|
10
11
|
|
11
12
|
from dotenv import load_dotenv
|
@@ -19,11 +20,10 @@ from fastapi.responses import UJSONResponse
|
|
19
20
|
|
20
21
|
from ..__about__ import __version__
|
21
22
|
from ..conf import api_config
|
22
|
-
from ..logs import get_logger
|
23
23
|
from .routes import job, log, workflow
|
24
24
|
|
25
25
|
load_dotenv()
|
26
|
-
logger =
|
26
|
+
logger = logging.getLogger("uvicorn.error")
|
27
27
|
|
28
28
|
|
29
29
|
@contextlib.asynccontextmanager
|
@@ -58,12 +58,16 @@ app.add_middleware(
|
|
58
58
|
|
59
59
|
|
60
60
|
@app.get(path="/", response_class=UJSONResponse)
|
61
|
-
async def health():
|
61
|
+
async def health() -> UJSONResponse:
|
62
62
|
"""Index view that not return any template without json status."""
|
63
|
-
|
63
|
+
logger.info("[API]: Workflow API Application already running ...")
|
64
|
+
return UJSONResponse(
|
65
|
+
content={"message": "Workflow already start up with healthy status."},
|
66
|
+
status_code=st.HTTP_200_OK,
|
67
|
+
)
|
64
68
|
|
65
69
|
|
66
|
-
# NOTE Add the jobs and logs routes by default.
|
70
|
+
# NOTE: Add the jobs and logs routes by default.
|
67
71
|
app.include_router(job, prefix=api_config.prefix_path)
|
68
72
|
app.include_router(log, prefix=api_config.prefix_path)
|
69
73
|
app.include_router(workflow, prefix=api_config.prefix_path)
|
@@ -71,21 +75,18 @@ app.include_router(workflow, prefix=api_config.prefix_path)
|
|
71
75
|
|
72
76
|
@app.exception_handler(RequestValidationError)
|
73
77
|
async def validation_exception_handler(
|
74
|
-
request: Request,
|
75
|
-
|
78
|
+
request: Request,
|
79
|
+
exc: RequestValidationError,
|
80
|
+
) -> UJSONResponse:
|
81
|
+
"""Error Handler for model validate does not valid."""
|
76
82
|
_ = request
|
77
83
|
return UJSONResponse(
|
78
84
|
status_code=st.HTTP_422_UNPROCESSABLE_ENTITY,
|
79
|
-
content=jsonable_encoder(
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
uvicorn.run(
|
87
|
-
app,
|
88
|
-
host="0.0.0.0",
|
89
|
-
port=80,
|
90
|
-
log_level="DEBUG",
|
85
|
+
content=jsonable_encoder(
|
86
|
+
{
|
87
|
+
"message": "Body does not parsing with model.",
|
88
|
+
"detail": exc.errors(),
|
89
|
+
"body": exc.body,
|
90
|
+
}
|
91
|
+
),
|
91
92
|
)
|
@@ -0,0 +1,59 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from uvicorn.config import LOGGING_CONFIG as LOGGING_CONFIG_UVICORN
|
4
|
+
|
5
|
+
from ..conf import config
|
6
|
+
|
7
|
+
LOGGING_CONFIG: dict[str, Any] = { # pragma: no cov
|
8
|
+
"version": 1,
|
9
|
+
"disable_existing_loggers": False,
|
10
|
+
"formatters": {
|
11
|
+
"default": LOGGING_CONFIG_UVICORN["formatters"]["default"],
|
12
|
+
"access": LOGGING_CONFIG_UVICORN["formatters"]["access"],
|
13
|
+
"custom": {
|
14
|
+
"format": config.log_format,
|
15
|
+
"datefmt": config.log_datetime_format,
|
16
|
+
},
|
17
|
+
},
|
18
|
+
"root": {
|
19
|
+
"level": "DEBUG" if config.debug else "INFO",
|
20
|
+
},
|
21
|
+
"handlers": {
|
22
|
+
"default": LOGGING_CONFIG_UVICORN["handlers"]["default"],
|
23
|
+
"access": LOGGING_CONFIG_UVICORN["handlers"]["access"],
|
24
|
+
"stream_custom": {
|
25
|
+
"formatter": "custom",
|
26
|
+
"class": "logging.StreamHandler",
|
27
|
+
"stream": "ext://sys.stdout",
|
28
|
+
},
|
29
|
+
# "file_handler": {
|
30
|
+
# "formatter": "custom_formatter",
|
31
|
+
# "class": "logging.handlers.RotatingFileHandler",
|
32
|
+
# "filename": "logs/app.log",
|
33
|
+
# "maxBytes": 1024 * 1024 * 1,
|
34
|
+
# "backupCount": 3,
|
35
|
+
# },
|
36
|
+
},
|
37
|
+
"loggers": {
|
38
|
+
"uvicorn": {
|
39
|
+
"handlers": ["default"],
|
40
|
+
"level": "DEBUG" if config.debug else "INFO",
|
41
|
+
"propagate": False,
|
42
|
+
},
|
43
|
+
"uvicorn.access": {
|
44
|
+
"handlers": ["access"],
|
45
|
+
"level": "DEBUG" if config.debug else "INFO",
|
46
|
+
"propagate": False,
|
47
|
+
},
|
48
|
+
"uvicorn.error": {
|
49
|
+
"handlers": ["default"],
|
50
|
+
"level": "DEBUG" if config.debug else "INFO",
|
51
|
+
},
|
52
|
+
"ddeutil.workflow": {
|
53
|
+
"handlers": ["stream_custom"],
|
54
|
+
"level": "INFO",
|
55
|
+
# "propagate": False,
|
56
|
+
"propagate": True,
|
57
|
+
},
|
58
|
+
},
|
59
|
+
}
|
@@ -3,6 +3,6 @@
|
|
3
3
|
# Licensed under the MIT License. See LICENSE in the project root for
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
|
-
from .job import
|
7
|
-
from .logs import
|
8
|
-
from .workflows import
|
6
|
+
from .job import router as job
|
7
|
+
from .logs import router as log
|
8
|
+
from .workflows import router as workflow
|
@@ -5,20 +5,21 @@
|
|
5
5
|
# ------------------------------------------------------------------------------
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
|
+
import logging
|
8
9
|
from typing import Any, Optional
|
9
10
|
|
10
11
|
from fastapi import APIRouter
|
12
|
+
from fastapi import status as st
|
11
13
|
from fastapi.responses import UJSONResponse
|
12
14
|
from pydantic import BaseModel, Field
|
13
15
|
|
14
16
|
from ...__types import DictData
|
15
17
|
from ...errors import JobError
|
16
18
|
from ...job import Job
|
17
|
-
from ...logs import get_logger
|
18
19
|
from ...result import Result
|
19
20
|
|
20
|
-
logger =
|
21
|
-
|
21
|
+
logger = logging.getLogger("uvicorn.error")
|
22
|
+
router = APIRouter(prefix="/job", tags=["job"])
|
22
23
|
|
23
24
|
|
24
25
|
class ResultCreate(BaseModel):
|
@@ -32,14 +33,19 @@ class ResultCreate(BaseModel):
|
|
32
33
|
)
|
33
34
|
|
34
35
|
|
35
|
-
@
|
36
|
+
@router.post(
|
37
|
+
path="/execute/",
|
38
|
+
response_class=UJSONResponse,
|
39
|
+
status_code=st.HTTP_200_OK,
|
40
|
+
)
|
36
41
|
async def job_execute(
|
37
42
|
result: ResultCreate,
|
38
43
|
job: Job,
|
39
44
|
params: dict[str, Any],
|
40
45
|
extras: Optional[dict[str, Any]] = None,
|
41
|
-
):
|
46
|
+
) -> UJSONResponse:
|
42
47
|
"""Execute job via RestAPI with execute route path."""
|
48
|
+
logger.info("[API]: Start execute job ...")
|
43
49
|
rs: Result = Result(
|
44
50
|
run_id=result.run_id,
|
45
51
|
parent_run_id=result.parent_run_id,
|
@@ -61,15 +67,35 @@ async def job_execute(
|
|
61
67
|
)
|
62
68
|
except JobError as err:
|
63
69
|
rs.trace.error(f"[JOB]: {err.__class__.__name__}: {err}")
|
70
|
+
return UJSONResponse(
|
71
|
+
content={
|
72
|
+
"message": str(err),
|
73
|
+
"result": {
|
74
|
+
"run_id": rs.run_id,
|
75
|
+
"parent_run_id": rs.parent_run_id,
|
76
|
+
},
|
77
|
+
"job": job.model_dump(
|
78
|
+
by_alias=True,
|
79
|
+
exclude_none=False,
|
80
|
+
exclude_unset=True,
|
81
|
+
),
|
82
|
+
"params": params,
|
83
|
+
"context": context,
|
84
|
+
},
|
85
|
+
status_code=st.HTTP_500_INTERNAL_SERVER_ERROR,
|
86
|
+
)
|
64
87
|
|
65
|
-
return
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
88
|
+
return UJSONResponse(
|
89
|
+
content={
|
90
|
+
"message": "Execute job via RestAPI successful.",
|
91
|
+
"result": {"run_id": rs.run_id, "parent_run_id": rs.parent_run_id},
|
92
|
+
"job": job.model_dump(
|
93
|
+
by_alias=True,
|
94
|
+
exclude_none=False,
|
95
|
+
exclude_unset=True,
|
96
|
+
),
|
97
|
+
"params": params,
|
98
|
+
"context": context,
|
99
|
+
},
|
100
|
+
status_code=st.HTTP_200_OK,
|
101
|
+
)
|
@@ -10,17 +10,17 @@ from fastapi import APIRouter, Path, Query
|
|
10
10
|
from fastapi import status as st
|
11
11
|
from fastapi.responses import UJSONResponse
|
12
12
|
|
13
|
-
from ...
|
13
|
+
from ...audits import get_audit
|
14
14
|
from ...result import Result
|
15
15
|
|
16
|
-
|
16
|
+
router = APIRouter(
|
17
17
|
prefix="/logs",
|
18
18
|
tags=["logs"],
|
19
19
|
default_response_class=UJSONResponse,
|
20
20
|
)
|
21
21
|
|
22
22
|
|
23
|
-
@
|
23
|
+
@router.get(
|
24
24
|
path="/traces/",
|
25
25
|
response_class=UJSONResponse,
|
26
26
|
status_code=st.HTTP_200_OK,
|
@@ -50,7 +50,7 @@ async def get_traces(
|
|
50
50
|
}
|
51
51
|
|
52
52
|
|
53
|
-
@
|
53
|
+
@router.get(
|
54
54
|
path="/traces/{run_id}",
|
55
55
|
response_class=UJSONResponse,
|
56
56
|
status_code=st.HTTP_200_OK,
|
@@ -77,7 +77,7 @@ async def get_trace_with_id(run_id: str):
|
|
77
77
|
}
|
78
78
|
|
79
79
|
|
80
|
-
@
|
80
|
+
@router.get(
|
81
81
|
path="/audits/",
|
82
82
|
response_class=UJSONResponse,
|
83
83
|
status_code=st.HTTP_200_OK,
|
@@ -94,7 +94,7 @@ async def get_audits():
|
|
94
94
|
}
|
95
95
|
|
96
96
|
|
97
|
-
@
|
97
|
+
@router.get(
|
98
98
|
path="/audits/{workflow}/",
|
99
99
|
response_class=UJSONResponse,
|
100
100
|
status_code=st.HTTP_200_OK,
|
@@ -113,7 +113,7 @@ async def get_audit_with_workflow(workflow: str):
|
|
113
113
|
}
|
114
114
|
|
115
115
|
|
116
|
-
@
|
116
|
+
@router.get(
|
117
117
|
path="/audits/{workflow}/{release}",
|
118
118
|
response_class=UJSONResponse,
|
119
119
|
status_code=st.HTTP_200_OK,
|
@@ -140,7 +140,7 @@ async def get_audit_with_workflow_release(
|
|
140
140
|
}
|
141
141
|
|
142
142
|
|
143
|
-
@
|
143
|
+
@router.get(
|
144
144
|
path="/audits/{workflow}/{release}/{run_id}",
|
145
145
|
response_class=UJSONResponse,
|
146
146
|
status_code=st.HTTP_200_OK,
|
@@ -5,6 +5,7 @@
|
|
5
5
|
# ------------------------------------------------------------------------------
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
|
+
import logging
|
8
9
|
from dataclasses import asdict
|
9
10
|
from datetime import datetime
|
10
11
|
from typing import Any
|
@@ -15,23 +16,23 @@ from fastapi.responses import UJSONResponse
|
|
15
16
|
from pydantic import BaseModel
|
16
17
|
|
17
18
|
from ...__types import DictData
|
18
|
-
from ...
|
19
|
-
from ...
|
19
|
+
from ...audits import AuditModel, get_audit
|
20
|
+
from ...conf import YamlParser
|
20
21
|
from ...result import Result
|
21
22
|
from ...workflow import Workflow
|
22
23
|
|
23
|
-
logger =
|
24
|
-
|
24
|
+
logger = logging.getLogger("uvicorn.error")
|
25
|
+
router = APIRouter(
|
25
26
|
prefix="/workflows",
|
26
27
|
tags=["workflows"],
|
27
28
|
default_response_class=UJSONResponse,
|
28
29
|
)
|
29
30
|
|
30
31
|
|
31
|
-
@
|
32
|
+
@router.get(path="/", status_code=st.HTTP_200_OK)
|
32
33
|
async def get_workflows() -> DictData:
|
33
34
|
"""Return all workflow workflows that exists in config path."""
|
34
|
-
workflows: DictData = dict(
|
35
|
+
workflows: DictData = dict(YamlParser.finds(Workflow))
|
35
36
|
return {
|
36
37
|
"message": f"Getting all workflows: {len(workflows)}",
|
37
38
|
"count": len(workflows),
|
@@ -39,7 +40,7 @@ async def get_workflows() -> DictData:
|
|
39
40
|
}
|
40
41
|
|
41
42
|
|
42
|
-
@
|
43
|
+
@router.get(path="/{name}", status_code=st.HTTP_200_OK)
|
43
44
|
async def get_workflow_by_name(name: str) -> DictData:
|
44
45
|
"""Return model of workflow that passing an input workflow name."""
|
45
46
|
try:
|
@@ -63,7 +64,7 @@ class ExecutePayload(BaseModel):
|
|
63
64
|
params: dict[str, Any]
|
64
65
|
|
65
66
|
|
66
|
-
@
|
67
|
+
@router.post(path="/{name}/execute", status_code=st.HTTP_202_ACCEPTED)
|
67
68
|
async def workflow_execute(name: str, payload: ExecutePayload) -> DictData:
|
68
69
|
"""Return model of workflow that passing an input workflow name."""
|
69
70
|
try:
|
@@ -88,7 +89,7 @@ async def workflow_execute(name: str, payload: ExecutePayload) -> DictData:
|
|
88
89
|
return asdict(result)
|
89
90
|
|
90
91
|
|
91
|
-
@
|
92
|
+
@router.get(path="/{name}/audits", status_code=st.HTTP_200_OK)
|
92
93
|
async def get_workflow_audits(name: str):
|
93
94
|
try:
|
94
95
|
return {
|
@@ -109,11 +110,11 @@ async def get_workflow_audits(name: str):
|
|
109
110
|
) from None
|
110
111
|
|
111
112
|
|
112
|
-
@
|
113
|
+
@router.get(path="/{name}/audits/{release}", status_code=st.HTTP_200_OK)
|
113
114
|
async def get_workflow_release_audit(name: str, release: str):
|
114
115
|
"""Get Workflow audit log with an input release value."""
|
115
116
|
try:
|
116
|
-
audit:
|
117
|
+
audit: AuditModel = get_audit().find_audit_with_release(
|
117
118
|
name=name,
|
118
119
|
release=datetime.strptime(release, "%Y%m%d%H%M%S"),
|
119
120
|
)
|