3737import json
3838import logging
3939from datetime import datetime , timezone
40- from typing import Any
40+ from typing import Any , TypedDict
4141
4242from airflow .providers .common .dataquality .results import DQRun , RuleResult , build_summary
4343from airflow .sdk import ObjectStoragePath
4444
4545log = logging .getLogger (__name__ )
4646
4747
48+ class DQPageResult (TypedDict ):
49+ """Paginated DQ read result."""
50+
51+ items : list [dict [str , Any ]]
52+ next_cursor : str | None
53+
54+
4855class ObjectStorageResultsBackend :
4956 """Persist DQ results as JSON files via ``ObjectStoragePath``."""
5057
51- def __init__ (self , results_path : str , conn_id : str | None = None ) -> None :
58+ def __init__ (self , * , results_path : str , conn_id : str | None = None ) -> None :
5259 self .root = ObjectStoragePath (results_path , conn_id = conn_id )
5360
54- def write_run (self , run : DQRun , results : list [RuleResult ]) -> None :
61+ def write_run (self , * , run : DQRun , results : list [RuleResult ]) -> None :
5562 timestamp = run .started_at or datetime .now (tz = timezone .utc ).isoformat ()
5663 compact_ts = self ._get_safe_key (timestamp )
57- payload = self ._build_run_payload (run , results )
64+ payload = self ._build_run_payload (run = run , results = results )
5865
59- self ._write_run_file (run , timestamp [:10 ], compact_ts , payload )
60- self ._write_task_instance_index (run , payload )
61- self ._write_rule_indexes (run , results , timestamp )
66+ self ._write_run_file (run = run , date_part = timestamp [:10 ], compact_ts = compact_ts , payload = payload )
67+ self ._write_task_instance_index (run = run , payload = payload )
68+ self ._write_rule_indexes (run = run , results = results , timestamp = timestamp )
6269
6370 def read_task_rule_history (
64- self , dag_id : str , task_id : str , rule_uid : str , limit : int = 100 , before : str | None = None
65- ) -> dict [ str , Any ] :
71+ self , * , dag_id : str , task_id : str , rule_uid : str , limit : int = 100 , before : str | None = None
72+ ) -> DQPageResult :
6673 """Return recent results for one rule produced by one task, newest first."""
6774 rule_dir = (
6875 self .root
@@ -72,11 +79,11 @@ def read_task_rule_history(
7279 / f"task_id={ task_id } "
7380 / f"rule_uid={ rule_uid } "
7481 )
75- return self ._read_rule_history_dir (rule_dir , limit , before )
82+ return self ._read_rule_history_dir (rule_dir = rule_dir , limit = limit , before = before )
7683
7784 def read_task_runs (
78- self , dag_id : str , task_id : str , limit : int = 50 , before : str | None = None
79- ) -> dict [ str , Any ] :
85+ self , * , dag_id : str , task_id : str , limit : int = 50 , before : str | None = None
86+ ) -> DQPageResult :
8087 """
8188 Return recent data quality runs for one task, newest first.
8289
@@ -103,11 +110,9 @@ def read_task_runs(
103110 for path in sorted (date_dir .iterdir (), key = lambda p : p .name , reverse = True ):
104111 if not path .name .endswith (".json" ):
105112 continue
106- payload = self ._read_json (path )
107- if payload is None :
113+ if (payload := self ._read_json (path )) is None :
108114 continue
109- cursor = self ._get_run_payload_cursor (payload )
110- if before is not None and cursor >= before :
115+ if before is not None and self ._get_run_payload_cursor (payload ) >= before :
111116 continue
112117 runs .append (payload )
113118 if len (runs ) > limit :
@@ -121,7 +126,7 @@ def read_task_runs(
121126 return {"items" : page , "next_cursor" : next_cursor }
122127
123128 def read_by_task_instance (
124- self , dag_id : str , task_id : str , run_id : str , map_index : int = - 1
129+ self , * , dag_id : str , task_id : str , run_id : str , map_index : int = - 1
125130 ) -> dict [str , Any ]:
126131 """Read the latest run for one task instance as ``{"run": ..., "results": ..., "summary": ...}``."""
127132 path = (
@@ -134,7 +139,9 @@ def read_by_task_instance(
134139 )
135140 return self ._read_json_or_raise (path )
136141
137- def _write_run_file (self , run : DQRun , date_part : str , compact_ts : str , payload : dict [str , Any ]) -> None :
142+ def _write_run_file (
143+ self , * , run : DQRun , date_part : str , compact_ts : str , payload : dict [str , Any ]
144+ ) -> None :
138145 run_dir = (
139146 self .root
140147 / "runs"
@@ -146,39 +153,41 @@ def _write_run_file(self, run: DQRun, date_part: str, compact_ts: str, payload:
146153 run_dir .mkdir (parents = True , exist_ok = True )
147154 (run_dir / f"{ compact_ts } __{ run .run_uid } .json" ).write_text (json .dumps (payload , default = str ))
148155
149- def _write_task_instance_index (self , run : DQRun , payload : dict [str , Any ]) -> None :
156+ def _write_task_instance_index (self , * , run : DQRun , payload : dict [str , Any ]) -> None :
150157 ti_dir = self .root / "runs" / "by_task_instance" / f"dag_id={ run .dag_id } " / f"task_id={ run .task_id } "
151158 ti_dir .mkdir (parents = True , exist_ok = True )
152159 (ti_dir / f"{ self ._get_safe_key (run .run_id )} __{ run .map_index } .json" ).write_text (
153160 json .dumps (payload , default = str )
154161 )
155162
156- def _write_rule_indexes (self , run : DQRun , results : list [RuleResult ], timestamp : str ) -> None :
163+ def _write_rule_indexes (self , * , run : DQRun , results : list [RuleResult ], timestamp : str ) -> None :
157164 run_context = self ._build_run_context (run )
158165 compact_ts = self ._get_safe_key (timestamp )
159166 for result in results :
160167 payload = {"run" : run_context , "result" : result .to_dict ()}
161168 self ._write_rule_index (
162- self .root
163- / "rules"
164- / "by_task_rule"
165- / f"dag_id={ run .dag_id } "
166- / f"task_id={ run .task_id } "
167- / f"rule_uid={ result .rule_uid } " ,
168- compact_ts ,
169- run .run_uid ,
170- payload ,
169+ rule_dir = (
170+ self .root
171+ / "rules"
172+ / "by_task_rule"
173+ / f"dag_id={ run .dag_id } "
174+ / f"task_id={ run .task_id } "
175+ / f"rule_uid={ result .rule_uid } "
176+ ),
177+ compact_ts = compact_ts ,
178+ run_uid = run .run_uid ,
179+ payload = payload ,
171180 )
172181
173182 def _write_rule_index (
174- self , rule_dir : ObjectStoragePath , compact_ts : str , run_uid : str , payload : dict [str , Any ]
183+ self , * , rule_dir : ObjectStoragePath , compact_ts : str , run_uid : str , payload : dict [str , Any ]
175184 ) -> None :
176185 rule_dir .mkdir (parents = True , exist_ok = True )
177186 (rule_dir / f"{ compact_ts } __{ run_uid } .json" ).write_text (json .dumps (payload , default = str ))
178187
179188 def _read_rule_history_dir (
180- self , rule_dir : ObjectStoragePath , limit : int , before : str | None = None
181- ) -> dict [ str , Any ] :
189+ self , * , rule_dir : ObjectStoragePath , limit : int , before : str | None = None
190+ ) -> DQPageResult :
182191 """
183192 Read rule-result records newest-first, as ``{"items": [...], "next_cursor": ...}``.
184193
@@ -211,12 +220,12 @@ def _get_safe_key(value: str) -> str:
211220 return value .replace ("/" , "_" ).replace (":" , "_" ).replace ("+" , "_" )
212221
213222 @staticmethod
214- def _build_run_payload (run : DQRun , results : list [RuleResult ]) -> dict [str , Any ]:
223+ def _build_run_payload (* , run : DQRun , results : list [RuleResult ]) -> dict [str , Any ]:
215224 result_records = [result .to_dict () for result in results ]
216225 return {
217226 "run" : run .to_dict (),
218227 "results" : result_records ,
219- "summary" : build_summary (run , results ),
228+ "summary" : build_summary (run = run , results = results ),
220229 }
221230
222231 @staticmethod
0 commit comments