Home Assistant Unofficial Reference 2024.12.1
singleton.py
Go to the documentation of this file.
1 """Helper to help coordinating calls."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from collections.abc import Callable
7 import functools
8 from typing import Any, cast, overload
9 
10 from homeassistant.core import HomeAssistant
11 from homeassistant.loader import bind_hass
12 from homeassistant.util.hass_dict import HassKey
13 
14 type _FuncType[_T] = Callable[[HomeAssistant], _T]
15 
16 
17 @overload
18 def singleton[_T](
19  data_key: HassKey[_T],
20 ) -> Callable[[_FuncType[_T]], _FuncType[_T]]: ...
21 
22 
23 @overload
24 def singleton[_T](data_key: str) -> Callable[[_FuncType[_T]], _FuncType[_T]]: ...
25 
26 
27 def singleton[_T](data_key: Any) -> Callable[[_FuncType[_T]], _FuncType[_T]]:
28  """Decorate a function that should be called once per instance.
29 
30  Result will be cached and simultaneous calls will be handled.
31  """
32 
33  def wrapper(func: _FuncType[_T]) -> _FuncType[_T]:
34  """Wrap a function with caching logic."""
35  if not asyncio.iscoroutinefunction(func):
36 
37  @functools.lru_cache(maxsize=1)
38  @bind_hass
39  @functools.wraps(func)
40  def wrapped(hass: HomeAssistant) -> _T:
41  if data_key not in hass.data:
42  hass.data[data_key] = func(hass)
43  return cast(_T, hass.data[data_key])
44 
45  return wrapped
46 
47  @bind_hass
48  @functools.wraps(func)
49  async def async_wrapped(hass: HomeAssistant) -> Any:
50  if data_key not in hass.data:
51  evt = hass.data[data_key] = asyncio.Event()
52  result = await func(hass)
53  hass.data[data_key] = result
54  evt.set()
55  return cast(_T, result)
56 
57  obj_or_evt = hass.data[data_key]
58 
59  if isinstance(obj_or_evt, asyncio.Event):
60  await obj_or_evt.wait()
61  return cast(_T, hass.data[data_key])
62 
63  return cast(_T, obj_or_evt)
64 
65  return async_wrapped # type: ignore[return-value]
66 
67  return wrapper