Coverage for src/local_deep_research/embeddings/providers/implementations/owned_openai_embeddings.py: 89%
54 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-22 21:20 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-22 21:20 +0000
1"""OpenAI embedding clients with explicit, retryable resource cleanup."""
3import asyncio
4import threading
5from typing import Any
7from langchain_openai import OpenAIEmbeddings
8from pydantic import ConfigDict, PrivateAttr
10from ....security.secure_logging import logger
13async def _aclose_http_clients(
14 http_client: Any, http_async_client: Any
15) -> bool:
16 closed = True
17 try:
18 http_client.close()
19 except Exception:
20 closed = False
21 logger.warning("Failed to close OpenAI embeddings sync httpx client")
22 try:
23 await http_async_client.aclose()
24 except Exception:
25 closed = False
26 logger.warning("Failed to close OpenAI embeddings async httpx client")
27 return closed
30def close_openai_embedding_clients(
31 http_client: Any, http_async_client: Any
32) -> bool:
33 try:
34 asyncio.get_running_loop()
35 except RuntimeError:
36 try:
37 return asyncio.run(
38 _aclose_http_clients(http_client, http_async_client)
39 )
40 except Exception:
41 logger.warning("Failed to close OpenAI embeddings httpx clients")
42 return False
44 closed = False
46 def _close_in_thread() -> None:
47 nonlocal closed
48 try:
49 closed = asyncio.run(
50 _aclose_http_clients(http_client, http_async_client)
51 )
52 except Exception:
53 logger.warning("Failed to close OpenAI embeddings httpx clients")
55 close_thread = threading.Thread(
56 target=_close_in_thread,
57 daemon=True,
58 name="ldr-async-openai-embedding-close",
59 )
60 close_thread.start()
61 close_thread.join(timeout=5)
62 if close_thread.is_alive():
63 logger.warning("OpenAI embeddings async httpx close exceeded 5s")
64 return False
65 return closed
68class OwnedOpenAIEmbeddings(OpenAIEmbeddings):
69 model_config = ConfigDict(arbitrary_types_allowed=True)
70 http_client: Any
71 http_async_client: Any
72 _closed: bool = PrivateAttr(default=False)
74 def close(self) -> None:
75 """Close from synchronous code; use ``aclose`` on the owning loop.
77 The fallback for a running loop waits up to five seconds. A client
78 whose transport cannot close on another loop remains retryable.
79 """
80 if self._closed: 80 ↛ 81line 80 didn't jump to line 81 because the condition on line 80 was never true
81 return
82 if close_openai_embedding_clients(
83 self.http_client, self.http_async_client
84 ):
85 self._closed = True
87 async def aclose(self) -> None:
88 if self._closed:
89 return
90 if await _aclose_http_clients(self.http_client, self.http_async_client):
91 self._closed = True