You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

http_client_pooling.py 1.6KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. """HTTP client pooling utilities."""
  2. from __future__ import annotations
  3. import atexit
  4. import threading
  5. from collections.abc import Callable
  6. import httpx
  7. ClientBuilder = Callable[[], httpx.Client]
  8. class HttpClientPoolFactory:
  9. """Thread-safe factory that maintains reusable HTTP client instances."""
  10. def __init__(self) -> None:
  11. self._clients: dict[str, httpx.Client] = {}
  12. self._lock = threading.Lock()
  13. def get_or_create(self, key: str, builder: ClientBuilder) -> httpx.Client:
  14. """Return a pooled client associated with ``key`` creating it on demand."""
  15. client = self._clients.get(key)
  16. if client is not None:
  17. return client
  18. with self._lock:
  19. client = self._clients.get(key)
  20. if client is None:
  21. client = builder()
  22. self._clients[key] = client
  23. return client
  24. def close_all(self) -> None:
  25. """Close all pooled clients and clear the pool."""
  26. with self._lock:
  27. for client in self._clients.values():
  28. client.close()
  29. self._clients.clear()
  30. _factory = HttpClientPoolFactory()
  31. def get_pooled_http_client(key: str, builder: ClientBuilder) -> httpx.Client:
  32. """Return a pooled client for the given ``key`` using ``builder`` when missing."""
  33. return _factory.get_or_create(key, builder)
  34. def close_all_pooled_clients() -> None:
  35. """Close every client created through the pooling factory."""
  36. _factory.close_all()
  37. def _register_shutdown_hook() -> None:
  38. atexit.register(close_all_pooled_clients)
  39. _register_shutdown_hook()