2424from collections import defaultdict , deque
2525from functools import lru_cache
2626from pathlib import Path
27- from typing import Any
27+ from typing import Any , NamedTuple
2828
2929from fastapi import Depends , FastAPI , Header , HTTPException , Request , status
3030from pydantic import BaseModel , Field
@@ -221,8 +221,21 @@ def _build_pipeline_components(
221221 return registry , schema_index , sql_provider , explain_provider
222222
223223
224+ class Singletons (NamedTuple ):
225+ """The four runtime objects the API routes share.
226+
227+ Exposed as a public type so tests can construct mock instances and feed
228+ them through ``app.dependency_overrides[get_singletons]``.
229+ """
230+
231+ pipeline : Any
232+ registry : DatabaseRegistry
233+ schema_index : SchemaIndex
234+ sql_provider : LLMProvider
235+
236+
224237@lru_cache (maxsize = 1 )
225- def _make_singletons () -> tuple [ Any , DatabaseRegistry , SchemaIndex , LLMProvider ] :
238+ def _make_singletons () -> Singletons :
226239 """Lazy: build the pipeline only when the first /ask hits — keeps /healthz
227240 fast and avoids touching Chroma when the API is used for status probes."""
228241 import os
@@ -244,7 +257,12 @@ def _make_singletons() -> tuple[Any, DatabaseRegistry, SchemaIndex, LLMProvider]
244257 use_dac_prompt = os .environ .get ("NLSQL_DAC" ) == "1" ,
245258 )
246259 pipeline = build_pipeline (config )
247- return pipeline , registry , schema_index , sql_provider
260+ return Singletons (pipeline , registry , schema_index , sql_provider )
261+
262+
263+ def get_singletons () -> Singletons :
264+ """FastAPI Depends-able factory; tests override via ``app.dependency_overrides``."""
265+ return _make_singletons ()
248266
249267
250268def create_app () -> FastAPI :
@@ -311,10 +329,11 @@ def readyz() -> ReadyResponse:
311329 registered = 0
312330 schema_chunks = 0
313331 try :
314- _pipeline , registry , schema_index , _sql = _make_singletons ()
315- registered = len (registry .ids ())
332+ factory : Any = app .dependency_overrides .get (get_singletons , get_singletons )
333+ singletons : Singletons = factory ()
334+ registered = len (singletons .registry .ids ())
316335 registry_ok = registered > 0
317- schema_chunks = schema_index .schema_collection .count ()
336+ schema_chunks = singletons . schema_index .schema_collection .count ()
318337 chroma_ok = schema_chunks > 0
319338 except Exception :
320339 pass
@@ -330,13 +349,15 @@ def readyz() -> ReadyResponse:
330349 # --------------------------------------------------------- product API
331350
332351 @app .get ("/databases" , response_model = DatabasesResponse , tags = ["catalog" ])
333- def databases (_auth : str = Depends (require_api_key )) -> DatabasesResponse :
334- _pipeline , registry , schema_index , _sql = _make_singletons ()
352+ def databases (
353+ _auth : str = Depends (require_api_key ),
354+ singletons : Singletons = Depends (get_singletons ), # noqa: B008
355+ ) -> DatabasesResponse :
335356 infos : list [DatabaseInfo ] = []
336- for db_id in registry .ids ():
337- spec = registry .get (db_id )
357+ for db_id in singletons . registry .ids ():
358+ spec = singletons . registry .get (db_id )
338359 try :
339- records = schema_index .schema_collection .get (
360+ records = singletons . schema_index .schema_collection .get (
340361 where = {"db_id" : db_id }, include = ["metadatas" ]
341362 )
342363 table_count = len (records .get ("metadatas" ) or [])
@@ -353,18 +374,21 @@ def databases(_auth: str = Depends(require_api_key)) -> DatabasesResponse:
353374 return DatabasesResponse (databases = infos )
354375
355376 @app .post ("/ask" , response_model = AskResponse , tags = ["nl-sql" ])
356- def ask (req : AskRequest , _auth : str = Depends (require_api_key )) -> AskResponse :
357- pipeline , registry , _schema , _sql = _make_singletons ()
358- if req .db_id not in registry .ids ():
377+ def ask (
378+ req : AskRequest ,
379+ _auth : str = Depends (require_api_key ),
380+ singletons : Singletons = Depends (get_singletons ), # noqa: B008
381+ ) -> AskResponse :
382+ if req .db_id not in singletons .registry .ids ():
359383 raise HTTPException (
360384 status_code = status .HTTP_404_NOT_FOUND ,
361385 detail = f"unknown db_id: { req .db_id !r} ; see /databases for the list" ,
362386 )
363- spec = registry .get (req .db_id )
387+ spec = singletons . registry .get (req .db_id )
364388 t0 = time .perf_counter ()
365389 try :
366390 result = run_pipeline (
367- pipeline ,
391+ singletons . pipeline ,
368392 question = req .question ,
369393 db_id = req .db_id ,
370394 dialect = spec .dialect ,
0 commit comments