@@ -161,6 +161,45 @@ def initialize(self):
161161 """
162162 pass
163163
164+ def shutdown (self ) -> None :
165+ """Shutdown the client and release all resources.
166+
167+ This method should be called when you're done using the client to properly
168+ close database connections and release other resources. Failure to call this
169+ method may result in the program hanging on exit while waiting for background
170+ threads to complete.
171+
172+ This method is idempotent and can be called multiple times safely.
173+
174+ Example:
175+ client = LlamaStackAsLibraryClient("starter")
176+ # ... use the client ...
177+ client.shutdown()
178+ """
179+ loop = self .loop
180+ asyncio .set_event_loop (loop )
181+ try :
182+ loop .run_until_complete (self .async_client .shutdown ())
183+ finally :
184+ loop .close ()
185+ asyncio .set_event_loop (None )
186+
187+ def __enter__ (self ) -> "LlamaStackAsLibraryClient" :
188+ """Enter the context manager.
189+
190+ The client is already initialized in __init__, so this just returns self.
191+
192+ Example:
193+ with LlamaStackAsLibraryClient("starter") as client:
194+ response = client.models.list()
195+ # Client is automatically shut down here
196+ """
197+ return self
198+
199+ def __exit__ (self , exc_type , exc_val , exc_tb ) -> None :
200+ """Exit the context manager and shut down the client."""
201+ self .shutdown ()
202+
164203 def request (self , * args , ** kwargs ):
165204 loop = self .loop
166205 asyncio .set_event_loop (loop )
@@ -224,6 +263,7 @@ def __init__(
224263 self .custom_provider_registry = custom_provider_registry
225264 self .provider_data = provider_data
226265 self .route_impls : RouteImpls | None = None # Initialize to None to prevent AttributeError
266+ self .stack : Stack | None = None
227267
228268 def _remove_root_logger_handlers (self ):
229269 """
@@ -246,9 +286,9 @@ async def initialize(self) -> bool:
246286 try :
247287 self .route_impls = None
248288
249- stack = Stack (self .config , self .custom_provider_registry )
250- await stack .initialize ()
251- self .impls = stack .impls
289+ self . stack = Stack (self .config , self .custom_provider_registry )
290+ await self . stack .initialize ()
291+ self .impls = self . stack .impls
252292 except ModuleNotFoundError as _e :
253293 cprint (_e .msg , color = "red" , file = sys .stderr )
254294 cprint (
@@ -283,6 +323,43 @@ async def initialize(self) -> bool:
283323 self .route_impls = initialize_route_impls (self .impls )
284324 return True
285325
326+ async def shutdown (self ) -> None :
327+ """Shutdown the client and release all resources.
328+
329+ This method should be called when you're done using the client to properly
330+ close database connections and release other resources. Failure to call this
331+ method may result in the program hanging on exit while waiting for background
332+ threads to complete.
333+
334+ This method is idempotent and can be called multiple times safely.
335+
336+ Example:
337+ client = AsyncLlamaStackAsLibraryClient("starter")
338+ await client.initialize()
339+ # ... use the client ...
340+ await client.shutdown()
341+ """
342+ if self .stack :
343+ await self .stack .shutdown ()
344+ self .stack = None
345+
346+ async def __aenter__ (self ) -> "AsyncLlamaStackAsLibraryClient" :
347+ """Enter the async context manager.
348+
349+ Initializes the client and returns it.
350+
351+ Example:
352+ async with AsyncLlamaStackAsLibraryClient("starter") as client:
353+ response = await client.models.list()
354+ # Client is automatically shut down here
355+ """
356+ await self .initialize ()
357+ return self
358+
359+ async def __aexit__ (self , exc_type , exc_val , exc_tb ) -> None :
360+ """Exit the async context manager and shut down the client."""
361+ await self .shutdown ()
362+
286363 async def request (
287364 self ,
288365 cast_to : Any ,
0 commit comments