Share HTTP client across all API calls for Windows performance
Single shared httpx.AsyncClient for all BooruClient instances (Danbooru, Gelbooru, Moebooru) with connection pooling. E621 gets its own shared client (custom User-Agent required). Site detection also reuses the shared client. Eliminates per-request TLS handshakes on Windows.
This commit is contained in:
parent
4987765520
commit
96c57d16a9
@ -37,6 +37,9 @@ class BooruClient(ABC):
|
||||
|
||||
api_type: str = ""
|
||||
|
||||
# Shared client across all BooruClient instances for connection reuse
|
||||
_shared_client: httpx.AsyncClient | None = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
base_url: str,
|
||||
@ -46,26 +49,25 @@ class BooruClient(ABC):
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.api_key = api_key
|
||||
self.api_user = api_user
|
||||
self._client: httpx.AsyncClient | None = None
|
||||
|
||||
@property
|
||||
def client(self) -> httpx.AsyncClient:
|
||||
if self._client is None or self._client.is_closed:
|
||||
self._client = httpx.AsyncClient(
|
||||
if BooruClient._shared_client is None or BooruClient._shared_client.is_closed:
|
||||
BooruClient._shared_client = httpx.AsyncClient(
|
||||
headers={"User-Agent": USER_AGENT},
|
||||
follow_redirects=True,
|
||||
timeout=20.0,
|
||||
event_hooks={"request": [self._log_request]},
|
||||
limits=httpx.Limits(max_connections=10, max_keepalive_connections=5),
|
||||
)
|
||||
return self._client
|
||||
return BooruClient._shared_client
|
||||
|
||||
@staticmethod
|
||||
async def _log_request(request: httpx.Request) -> None:
|
||||
log_connection(str(request.url))
|
||||
|
||||
async def close(self) -> None:
|
||||
if self._client and not self._client.is_closed:
|
||||
await self._client.aclose()
|
||||
pass # shared client stays open
|
||||
|
||||
@abstractmethod
|
||||
async def search(
|
||||
|
||||
@ -23,11 +23,17 @@ async def detect_site_type(
|
||||
"""
|
||||
url = url.rstrip("/")
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
from .base import BooruClient as _BC
|
||||
# Reuse shared client for site detection
|
||||
if _BC._shared_client is None or _BC._shared_client.is_closed:
|
||||
_BC._shared_client = httpx.AsyncClient(
|
||||
headers={"User-Agent": USER_AGENT},
|
||||
follow_redirects=True,
|
||||
timeout=10.0,
|
||||
) as client:
|
||||
timeout=20.0,
|
||||
limits=httpx.Limits(max_connections=10, max_keepalive_connections=5),
|
||||
)
|
||||
client = _BC._shared_client
|
||||
if True: # keep indent level
|
||||
# Try Danbooru / e621 first — /posts.json is a definitive endpoint
|
||||
try:
|
||||
params: dict = {"limit": 1}
|
||||
|
||||
@ -15,19 +15,23 @@ log = logging.getLogger("booru")
|
||||
class E621Client(BooruClient):
|
||||
api_type = "e621"
|
||||
|
||||
_e621_client: httpx.AsyncClient | None = None
|
||||
_e621_ua: str = ""
|
||||
|
||||
@property
|
||||
def client(self) -> httpx.AsyncClient:
|
||||
if self._client is None or self._client.is_closed:
|
||||
# e621 requires a descriptive User-Agent with username
|
||||
ua = USER_AGENT
|
||||
if self.api_user:
|
||||
ua = f"{USER_AGENT} (by {self.api_user} on e621)"
|
||||
self._client = httpx.AsyncClient(
|
||||
if E621Client._e621_client is None or E621Client._e621_client.is_closed or E621Client._e621_ua != ua:
|
||||
E621Client._e621_ua = ua
|
||||
E621Client._e621_client = httpx.AsyncClient(
|
||||
headers={"User-Agent": ua},
|
||||
follow_redirects=True,
|
||||
timeout=20.0,
|
||||
limits=httpx.Limits(max_connections=10, max_keepalive_connections=5),
|
||||
)
|
||||
return self._client
|
||||
return E621Client._e621_client
|
||||
|
||||
async def search(
|
||||
self, tags: str = "", page: int = 1, limit: int = DEFAULT_PAGE_SIZE
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user