Home Assistant Unofficial Reference 2024.12.1
httpx_client.py
Go to the documentation of this file.
1 """Helper for httpx."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable, Coroutine
6 import sys
7 from typing import Any, Self
8 
9 import httpx
10 
11 from homeassistant.const import APPLICATION_NAME, EVENT_HOMEASSISTANT_CLOSE, __version__
12 from homeassistant.core import Event, HomeAssistant, callback
13 from homeassistant.loader import bind_hass
14 from homeassistant.util.hass_dict import HassKey
15 from homeassistant.util.ssl import (
16  SSLCipherList,
17  client_context,
18  create_no_verify_ssl_context,
19 )
20 
21 from .frame import warn_use
22 
23 # We have a lot of integrations that poll every 10-30 seconds
24 # and we want to keep the connection open for a while so we
25 # don't have to reconnect every time so we use 15s to match aiohttp.
26 KEEP_ALIVE_TIMEOUT = 15
27 DATA_ASYNC_CLIENT: HassKey[httpx.AsyncClient] = HassKey("httpx_async_client")
28 DATA_ASYNC_CLIENT_NOVERIFY: HassKey[httpx.AsyncClient] = HassKey(
29  "httpx_async_client_noverify"
30 )
31 DEFAULT_LIMITS = limits = httpx.Limits(keepalive_expiry=KEEP_ALIVE_TIMEOUT)
32 SERVER_SOFTWARE = (
33  f"{APPLICATION_NAME}/{__version__} "
34  f"httpx/{httpx.__version__} Python/{sys.version_info[0]}.{sys.version_info[1]}"
35 )
36 USER_AGENT = "User-Agent"
37 
38 
39 @callback
40 @bind_hass
41 def get_async_client(hass: HomeAssistant, verify_ssl: bool = True) -> httpx.AsyncClient:
42  """Return default httpx AsyncClient.
43 
44  This method must be run in the event loop.
45  """
46  key = DATA_ASYNC_CLIENT if verify_ssl else DATA_ASYNC_CLIENT_NOVERIFY
47 
48  if (client := hass.data.get(key)) is None:
49  client = hass.data[key] = create_async_httpx_client(hass, verify_ssl)
50 
51  return client
52 
53 
54 class HassHttpXAsyncClient(httpx.AsyncClient):
55  """httpx AsyncClient that suppresses context management."""
56 
57  async def __aenter__(self) -> Self:
58  """Prevent an integration from reopen of the client via context manager."""
59  return self
60 
61  async def __aexit__(self, *args: object) -> None:
62  """Prevent an integration from close of the client via context manager."""
63 
64 
65 @callback
67  hass: HomeAssistant,
68  verify_ssl: bool = True,
69  auto_cleanup: bool = True,
70  ssl_cipher_list: SSLCipherList = SSLCipherList.PYTHON_DEFAULT,
71  **kwargs: Any,
72 ) -> httpx.AsyncClient:
73  """Create a new httpx.AsyncClient with kwargs, i.e. for cookies.
74 
75  If auto_cleanup is False, the client will be
76  automatically closed on homeassistant_stop.
77 
78  This method must be run in the event loop.
79  """
80  ssl_context = (
81  client_context(ssl_cipher_list)
82  if verify_ssl
83  else create_no_verify_ssl_context(ssl_cipher_list)
84  )
85  client = HassHttpXAsyncClient(
86  verify=ssl_context,
87  headers={USER_AGENT: SERVER_SOFTWARE},
88  limits=DEFAULT_LIMITS,
89  **kwargs,
90  )
91 
92  original_aclose = client.aclose
93 
94  client.aclose = warn_use( # type: ignore[method-assign]
95  client.aclose, "closes the Home Assistant httpx client"
96  )
97 
98  if auto_cleanup:
99  _async_register_async_client_shutdown(hass, client, original_aclose)
100 
101  return client
102 
103 
104 @callback
106  hass: HomeAssistant,
107  client: httpx.AsyncClient,
108  original_aclose: Callable[[], Coroutine[Any, Any, None]],
109 ) -> None:
110  """Register httpx AsyncClient aclose on Home Assistant shutdown.
111 
112  This method must be run in the event loop.
113  """
114 
115  async def _async_close_client(event: Event) -> None:
116  """Close httpx client."""
117  await original_aclose()
118 
119  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, _async_close_client)
httpx.AsyncClient get_async_client(HomeAssistant hass, bool verify_ssl=True)
Definition: httpx_client.py:41
None _async_register_async_client_shutdown(HomeAssistant hass, httpx.AsyncClient client, Callable[[], Coroutine[Any, Any, None]] original_aclose)
httpx.AsyncClient create_async_httpx_client(HomeAssistant hass, bool verify_ssl=True, bool auto_cleanup=True, SSLCipherList ssl_cipher_list=SSLCipherList.PYTHON_DEFAULT, **Any kwargs)
Definition: httpx_client.py:72
ssl.SSLContext client_context(SSLCipherList ssl_cipher_list=SSLCipherList.PYTHON_DEFAULT)
Definition: ssl.py:137
ssl.SSLContext create_no_verify_ssl_context(SSLCipherList ssl_cipher_list=SSLCipherList.PYTHON_DEFAULT)
Definition: ssl.py:144