@@ -139,6 +139,7 @@ def run(self: Dashboard) -> None:
139139 metric , start_t = futs [fut ]
140140 try :
141141 rows , headers = fut .result ()
142+ rows = self ._convert_rows (rows )
142143 except Exception as exc : # pragma: no cover - passthrough
143144 raise RuntimeError (f"error in metric '{ metric .slug } ': { exc } " ) from exc
144145 logging .info (
@@ -195,17 +196,25 @@ def _fetch_rows(self: Dashboard, sql: str) -> tuple[list[tuple], list[str]]:
195196 cur .execute (sql )
196197 rows = cur .fetchall ()
197198 headers = [d [0 ] for d in cur .description ]
198- # Convert Decimal values to plain Python types for JSON serialisation
199- converted : list [tuple ] = []
200- for row in rows :
201- converted .append (
202- tuple (float (val ) if isinstance (val , Decimal ) else val for val in row )
203- )
204- rows = converted
205199 finally :
206200 self .pool .putconn (conn )
207201 return rows , headers
208202
203+ def _convert_rows (self : Dashboard , rows : list [tuple ]) -> list [tuple ]:
204+ """Convert non-JSON serialisable types to basic Python types."""
205+ converted : list [tuple ] = []
206+ for row in rows :
207+ new_row = []
208+ for val in row :
209+ if isinstance (val , Decimal ):
210+ new_row .append (float (val ))
211+ elif isinstance (val , (dt .date , dt .datetime )):
212+ new_row .append (val .isoformat ())
213+ else :
214+ new_row .append (val )
215+ converted .append (tuple (new_row ))
216+ return converted
217+
209218 def _update_history (self : Dashboard , slug : str , value : int ) -> None :
210219 path = self .history_dir / f"{ slug } .csv"
211220 is_new = not path .exists ()
0 commit comments