A RAG assistant may need to load an embedding model and vector index before answering queries. Recreating those resources for every test call is expensive. A singleton wrapper keeps one initialized core instance available for reuse. section 12.3
Listing 12.16 Singleton pattern and sync test wrappers
_dakka_bot = None #1
async def get_dakka_bot(): #2
"""Get or create DakkaBot instance"""
global _dakka_bot
if _dakka_bot is None:
_dakka_bot = DakkaBotCore()
await _dakka_bot.initialize()
return _dakka_bot
def dakka_bot_query(query: str) -> str: #3
"""Simple sync wrapper for testing frameworks"""
async def _process():
bot = await get_dakka_bot()
result = await bot.process_query(query)
if result["error"]:
return f"Error: {result['error']}"
return result["response"]
return asyncio.run(_process())
A module-level variable starts as None. The async getter creates and initializes the core only when that variable is empty, then returns the stored instance. A synchronous query wrapper calls the getter and runs the async query through asyncio.run(), so tests can call the same core logic without managing an event loop. section 12.3
Keep caching and the sync/async bridge separate in your design: reuse avoids repeated initialization, while the wrapper makes async behavior accessible to pytest, integration scripts, and command-line tools. section 12.3





