11from __future__ import annotations
22
3+ import json
34from datetime import datetime
45from typing import Any , ClassVar
56
67import sqlalchemy as sa
78from fastapi import APIRouter , Depends , FastAPI , HTTPException , Query
8- from pydantic import BaseModel , Field
9+ from pydantic import BaseModel , BeforeValidator , Field , model_validator
10+ from sqlalchemy .dialects .postgresql import JSONB
911from sqlalchemy .exc import NoResultFound
1012from typing_extensions import Annotated , Doc
1113
2022from .plugin import TaskManagerPlugin
2123
2224
25+ def _parse_json_str (v : Any ) -> Any :
26+ """Parse a JSON string into a dict for JSONB query parameters."""
27+ if isinstance (v , str ):
28+ return json .loads (v )
29+ return v
30+
31+
32+ JsonDict = Annotated [dict [str , Any ], BeforeValidator (_parse_json_str )]
33+
34+
2335class TaskDbPlugin (TaskManagerPlugin ):
24- """A plugin to store task runs in a database.
36+ """A plugin to store [TaskRun][fluid.scheduler.TaskRun] in a postgresql database.
2537
2638 This plugin listens to task state changes and updates the database accordingly.
2739 It requires a CrudDB instance to perform database operations and allows
@@ -95,7 +107,7 @@ def register(self, task_manager: TaskManager) -> None:
95107 async def get_history (
96108 self ,
97109 q : Annotated [
98- HistoryQuery , Doc ("Query parameters for fetching task run history" )
110+ TaskHistoryQuery , Doc ("Query parameters for fetching task run history" )
99111 ],
100112 ) -> TaskRunHistoryPage :
101113 """Get task run history based on the provided query parameters."""
@@ -169,10 +181,15 @@ def task_meta(meta: sa.MetaData, table_name: str = "tasks") -> None:
169181 nullable = False ,
170182 index = True ,
171183 ),
172- sa .Column ("queued" , sa .DateTime (timezone = True ), nullable = False ),
184+ sa .Column ("queued" , sa .DateTime (timezone = True ), nullable = False , index = True ),
173185 sa .Column ("start" , sa .DateTime (timezone = True )),
174186 sa .Column ("end" , sa .DateTime (timezone = True )),
175- sa .Column ("params" , sa .JSON ),
187+ sa .Column ("params" , JSONB ),
188+ sa .Index (
189+ f"ix_{ table_name } _params" ,
190+ "params" ,
191+ postgresql_using = "gin" ,
192+ ),
176193 )
177194
178195
@@ -228,32 +245,52 @@ class TaskRunHistoryPage(BaseModel):
228245 cursor : str = Field (..., description = "Pagination cursor to fetch the next page" )
229246
230247
231- class HistoryQuery (BaseModel ):
248+ class TaskHistoryQuery (BaseModel ):
232249 """Query parameters for fetching task run history."""
233250
234251 task : Annotated [
235252 str | None ,
236253 Query (description = "Filter by task name" ),
254+ Doc ("Filter by task name when provided" ),
237255 ] = None
238256 start : Annotated [
239257 datetime | None ,
240258 Query (description = "Filter runs queued at or after this time" ),
259+ Doc ("Filter runs queued at or after this time when provided" ),
241260 ] = None
242261 end : Annotated [
243262 datetime | None ,
244263 Query (description = "Filter runs queued at or before this time" ),
264+ Doc ("Filter runs queued at or before this time when provided" ),
245265 ] = None
246266 state : Annotated [
247267 TaskState | None ,
248268 Query (description = "Filter by task state" ),
269+ Doc ("Filter by task state when provided" ),
270+ ] = None
271+ params : Annotated [
272+ dict [str , Any ] | str | None ,
273+ Query (description = "Filter by params using JSON containment" ),
274+ Doc ("Filter by params using JSON containment when provided" ),
249275 ] = None
276+
277+ @model_validator (mode = "before" )
278+ @classmethod
279+ def _parse_params_str (cls , data : Any ) -> Any :
280+ if isinstance (data , dict ) and "params" in data :
281+ data = {** data }
282+ data ["params" ] = _parse_json_str (data ["params" ])
283+ return data
284+
250285 limit : Annotated [
251286 int | None ,
252287 Query (description = "Maximum number of results to return" , ge = 1 ),
288+ Doc ("Maximum number of results to return when provided" ),
253289 ] = None
254290 cursor : Annotated [
255291 str ,
256292 Query (description = "Pagination cursor from a previous response" ),
293+ Doc ("Pagination cursor from a previous response when provided" ),
257294 ] = ""
258295
259296 _filter_map : ClassVar [dict [str , str ]] = {
@@ -278,7 +315,7 @@ def filters(self) -> dict:
278315)
279316async def get_history (
280317 db_plugin : TaskDbPluginDep ,
281- q : Annotated [HistoryQuery , Depends ()],
318+ q : Annotated [TaskHistoryQuery , Query ()],
282319) -> TaskRunHistoryPage :
283320 return await db_plugin .get_history (q )
284321
0 commit comments