Skip to content

Commit 26db567

Browse files
committed
🎯 feat: improve logging module for overridable from child package (#49).
* 🎨 format: reformat code on api component. * 🎯 feat: update api application. * 🧪 tests: update testcase for job execute api. * 🧪 tests: add dynamic assert context for foreach stage. * 📌 deps: upgrade typer version to 0.16.0. * 🎨 format: change way to setting log config.
1 parent 923d473 commit 26db567

13 files changed

Lines changed: 339 additions & 137 deletions

File tree

.container/Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ RUN pip install --no-cache-dir -U ddeutil-workflow[all]
1515
USER worker
1616

1717
ENTRYPOINT ["workflow-cli"]
18+
19+
CMD ["api"]

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ dependencies = [
3030
"pydantic==2.11.4",
3131
"pydantic-extra-types==2.10.4",
3232
"python-dotenv==1.1.0",
33-
"typer==0.15.4",
33+
"typer>=0.16.0",
3434
]
3535
dynamic = ["version"]
3636

@@ -78,9 +78,10 @@ omit = [
7878
"src/ddeutil/workflow/__about__.py",
7979
"src/ddeutil/workflow/__cron.py",
8080
"src/ddeutil/workflow/__main__.py",
81+
"src/ddeutil/workflow/__types.py",
8182
"src/ddeutil/workflow/cli.py",
8283
"src/ddeutil/workflow/api/__init__.py",
83-
"src/ddeutil/workflow/api/logs.py",
84+
"src/ddeutil/workflow/api/log_conf.py",
8485
"src/ddeutil/workflow/api/routes/__init__.py",
8586
"src/ddeutil/workflow/api/routes/job.py",
8687
"src/ddeutil/workflow/api/routes/logs.py",

src/ddeutil/workflow/api/__init__.py

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from __future__ import annotations
77

88
import contextlib
9+
import logging
910
from collections.abc import AsyncIterator
1011

1112
from dotenv import load_dotenv
@@ -19,11 +20,10 @@
1920

2021
from ..__about__ import __version__
2122
from ..conf import api_config
22-
from ..logs import get_logger
2323
from .routes import job, log, workflow
2424

2525
load_dotenv()
26-
logger = get_logger("uvicorn.error")
26+
logger = logging.getLogger("uvicorn.error")
2727

2828

2929
@contextlib.asynccontextmanager
@@ -58,34 +58,35 @@ async def lifespan(_: FastAPI) -> AsyncIterator[dict[str, list]]:
5858

5959

6060
@app.get(path="/", response_class=UJSONResponse)
61-
async def health():
61+
async def health() -> UJSONResponse:
6262
"""Index view that not return any template without json status."""
63-
return {"message": "Workflow already start up with healthy status."}
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+
)
6468

6569

66-
# NOTE Add the jobs and logs routes by default.
70+
# NOTE: Add the jobs and logs routes by default.
6771
app.include_router(job, prefix=api_config.prefix_path)
6872
app.include_router(log, prefix=api_config.prefix_path)
6973
app.include_router(workflow, prefix=api_config.prefix_path)
7074

7175

7276
@app.exception_handler(RequestValidationError)
7377
async def validation_exception_handler(
74-
request: Request, exc: RequestValidationError
75-
):
78+
request: Request,
79+
exc: RequestValidationError,
80+
) -> UJSONResponse:
81+
"""Error Handler for model validate does not valid."""
7682
_ = request
7783
return UJSONResponse(
7884
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",
85+
content=jsonable_encoder(
86+
{
87+
"message": "Body does not parsing with model.",
88+
"detail": exc.errors(),
89+
"body": exc.body,
90+
}
91+
),
9192
)
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
from typing import Any
2+
13
from ..conf import config
24

3-
LOGGING_CONFIG = { # pragma: no cov
5+
LOGGING_CONFIG: dict[str, Any] = { # pragma: no cov
46
"version": 1,
57
"disable_existing_loggers": False,
68
"formatters": {
@@ -22,38 +24,49 @@
2224
"stream": "ext://sys.stderr",
2325
},
2426
"stream_handler": {
27+
# "formatter": "standard",
2528
"formatter": "custom_formatter",
2629
"class": "logging.StreamHandler",
2730
"stream": "ext://sys.stdout",
2831
},
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-
},
32+
# "file_handler": {
33+
# "formatter": "custom_formatter",
34+
# "class": "logging.handlers.RotatingFileHandler",
35+
# "filename": "logs/app.log",
36+
# "maxBytes": 1024 * 1024 * 1,
37+
# "backupCount": 3,
38+
# },
3639
},
3740
"loggers": {
3841
"uvicorn": {
39-
"handlers": ["default", "file_handler"],
42+
# "handlers": ["default", "file_handler"],
43+
"handlers": ["default"],
4044
"level": "DEBUG" if config.debug else "INFO",
4145
"propagate": False,
4246
},
4347
"uvicorn.access": {
44-
"handlers": ["stream_handler", "file_handler"],
48+
# "handlers": ["stream_handler", "file_handler"],
49+
"handlers": ["stream_handler"],
4550
"level": "DEBUG" if config.debug else "INFO",
4651
"propagate": False,
4752
},
4853
"uvicorn.error": {
49-
"handlers": ["stream_handler", "file_handler"],
54+
# "handlers": ["stream_handler", "file_handler"],
55+
"handlers": ["stream_handler"],
5056
"level": "DEBUG" if config.debug else "INFO",
5157
"propagate": False,
5258
},
53-
# "uvicorn.asgi": {
54-
# "handlers": ["stream_handler", "file_handler"],
55-
# "level": "TRACE",
56-
# "propagate": False,
59+
"uvicorn.asgi": {
60+
# "handlers": ["stream_handler", "file_handler"],
61+
"handlers": ["stream_handler"],
62+
"level": "TRACE",
63+
"propagate": False,
64+
},
65+
# "ddeutil.workflow": {
66+
# "handlers": ["stream_handler"],
67+
# "level": "INFO",
68+
# # "propagate": False,
69+
# "propagate": True,
5770
# },
5871
},
5972
}

src/ddeutil/workflow/api/routes/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
# Licensed under the MIT License. See LICENSE in the project root for
44
# license information.
55
# ------------------------------------------------------------------------------
6-
from .job import job_route as job
7-
from .logs import log_route as log
8-
from .workflows import workflow_route as workflow
6+
from .job import router as job
7+
from .logs import router as log
8+
from .workflows import router as workflow

src/ddeutil/workflow/api/routes/job.py

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,21 @@
55
# ------------------------------------------------------------------------------
66
from __future__ import annotations
77

8+
import logging
89
from typing import Any, Optional
910

1011
from fastapi import APIRouter
12+
from fastapi import status as st
1113
from fastapi.responses import UJSONResponse
1214
from pydantic import BaseModel, Field
1315

1416
from ...__types import DictData
1517
from ...errors import JobError
1618
from ...job import Job
17-
from ...logs import get_logger
1819
from ...result import Result
1920

20-
logger = get_logger("uvicorn.error")
21-
job_route = APIRouter(prefix="/job", tags=["job"])
21+
logger = logging.getLogger("uvicorn.error")
22+
router = APIRouter(prefix="/job", tags=["job"])
2223

2324

2425
class ResultCreate(BaseModel):
@@ -32,14 +33,19 @@ class ResultCreate(BaseModel):
3233
)
3334

3435

35-
@job_route.post(path="/execute/", response_class=UJSONResponse)
36+
@router.post(
37+
path="/execute/",
38+
response_class=UJSONResponse,
39+
status_code=st.HTTP_200_OK,
40+
)
3641
async def job_execute(
3742
result: ResultCreate,
3843
job: Job,
3944
params: dict[str, Any],
4045
extras: Optional[dict[str, Any]] = None,
41-
):
46+
) -> UJSONResponse:
4247
"""Execute job via RestAPI with execute route path."""
48+
logger.info("[API]: Start execute job ...")
4349
rs: Result = Result(
4450
run_id=result.run_id,
4551
parent_run_id=result.parent_run_id,
@@ -61,15 +67,35 @@ async def job_execute(
6167
)
6268
except JobError as err:
6369
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+
)
6487

65-
return {
66-
"message": "Execute job via RestAPI.",
67-
"result": {"run_id": rs.run_id, "parent_run_id": rs.parent_run_id},
68-
"job": job.model_dump(
69-
by_alias=True,
70-
exclude_none=False,
71-
exclude_unset=True,
72-
),
73-
"params": params,
74-
"context": context,
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+
)

src/ddeutil/workflow/api/routes/logs.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@
1313
from ...logs import get_audit
1414
from ...result import Result
1515

16-
log_route = APIRouter(
16+
router = APIRouter(
1717
prefix="/logs",
1818
tags=["logs"],
1919
default_response_class=UJSONResponse,
2020
)
2121

2222

23-
@log_route.get(
23+
@router.get(
2424
path="/traces/",
2525
response_class=UJSONResponse,
2626
status_code=st.HTTP_200_OK,
@@ -50,7 +50,7 @@ async def get_traces(
5050
}
5151

5252

53-
@log_route.get(
53+
@router.get(
5454
path="/traces/{run_id}",
5555
response_class=UJSONResponse,
5656
status_code=st.HTTP_200_OK,
@@ -77,7 +77,7 @@ async def get_trace_with_id(run_id: str):
7777
}
7878

7979

80-
@log_route.get(
80+
@router.get(
8181
path="/audits/",
8282
response_class=UJSONResponse,
8383
status_code=st.HTTP_200_OK,
@@ -94,7 +94,7 @@ async def get_audits():
9494
}
9595

9696

97-
@log_route.get(
97+
@router.get(
9898
path="/audits/{workflow}/",
9999
response_class=UJSONResponse,
100100
status_code=st.HTTP_200_OK,
@@ -113,7 +113,7 @@ async def get_audit_with_workflow(workflow: str):
113113
}
114114

115115

116-
@log_route.get(
116+
@router.get(
117117
path="/audits/{workflow}/{release}",
118118
response_class=UJSONResponse,
119119
status_code=st.HTTP_200_OK,
@@ -140,7 +140,7 @@ async def get_audit_with_workflow_release(
140140
}
141141

142142

143-
@log_route.get(
143+
@router.get(
144144
path="/audits/{workflow}/{release}/{run_id}",
145145
response_class=UJSONResponse,
146146
status_code=st.HTTP_200_OK,

0 commit comments

Comments
 (0)