2020-04-30 23:47:14 +00:00
|
|
|
"""Helper to help coordinating calls."""
|
2024-03-08 15:36:11 +00:00
|
|
|
|
2021-03-17 17:34:19 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2020-04-30 23:47:14 +00:00
|
|
|
import asyncio
|
2025-01-17 18:22:48 +00:00
|
|
|
from collections.abc import Callable, Coroutine
|
2020-04-30 23:47:14 +00:00
|
|
|
import functools
|
2025-01-17 18:22:48 +00:00
|
|
|
from typing import Any, Literal, assert_type, cast, overload
|
2020-04-30 23:47:14 +00:00
|
|
|
|
|
|
|
from homeassistant.core import HomeAssistant
|
2020-05-04 18:23:12 +00:00
|
|
|
from homeassistant.loader import bind_hass
|
2024-05-07 08:53:13 +00:00
|
|
|
from homeassistant.util.hass_dict import HassKey
|
2020-04-30 23:47:14 +00:00
|
|
|
|
2024-05-18 09:41:46 +00:00
|
|
|
type _FuncType[_T] = Callable[[HomeAssistant], _T]
|
2025-01-17 18:22:48 +00:00
|
|
|
type _Coro[_T] = Coroutine[Any, Any, _T]
|
|
|
|
|
|
|
|
|
|
|
|
@overload
|
|
|
|
def singleton[_T](
|
|
|
|
data_key: HassKey[_T], *, async_: Literal[True]
|
|
|
|
) -> Callable[[_FuncType[_Coro[_T]]], _FuncType[_Coro[_T]]]: ...
|
2020-04-30 23:47:14 +00:00
|
|
|
|
|
|
|
|
2024-05-07 08:53:13 +00:00
|
|
|
@overload
|
2024-05-18 09:41:46 +00:00
|
|
|
def singleton[_T](
|
|
|
|
data_key: HassKey[_T],
|
|
|
|
) -> Callable[[_FuncType[_T]], _FuncType[_T]]: ...
|
2024-05-07 08:53:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
@overload
|
2024-05-18 09:41:46 +00:00
|
|
|
def singleton[_T](data_key: str) -> Callable[[_FuncType[_T]], _FuncType[_T]]: ...
|
2024-05-07 08:53:13 +00:00
|
|
|
|
|
|
|
|
2025-01-17 18:22:48 +00:00
|
|
|
def singleton[_S, _T, _U](
|
|
|
|
data_key: Any, *, async_: bool = False
|
|
|
|
) -> Callable[[_FuncType[_S]], _FuncType[_S]]:
|
2020-04-30 23:47:14 +00:00
|
|
|
"""Decorate a function that should be called once per instance.
|
|
|
|
|
|
|
|
Result will be cached and simultaneous calls will be handled.
|
|
|
|
"""
|
|
|
|
|
2025-01-17 18:22:48 +00:00
|
|
|
@overload
|
|
|
|
def wrapper(func: _FuncType[_Coro[_T]]) -> _FuncType[_Coro[_T]]: ...
|
|
|
|
|
|
|
|
@overload
|
|
|
|
def wrapper(func: _FuncType[_U]) -> _FuncType[_U]: ...
|
|
|
|
|
|
|
|
def wrapper(func: _FuncType[_Coro[_T] | _U]) -> _FuncType[_Coro[_T] | _U]:
|
2020-04-30 23:47:14 +00:00
|
|
|
"""Wrap a function with caching logic."""
|
2020-10-18 20:41:22 +00:00
|
|
|
if not asyncio.iscoroutinefunction(func):
|
|
|
|
|
2024-04-28 17:11:08 +00:00
|
|
|
@functools.lru_cache(maxsize=1)
|
2020-10-18 20:41:22 +00:00
|
|
|
@bind_hass
|
|
|
|
@functools.wraps(func)
|
2025-01-17 18:22:48 +00:00
|
|
|
def wrapped(hass: HomeAssistant) -> _U:
|
2021-09-11 19:02:01 +00:00
|
|
|
if data_key not in hass.data:
|
|
|
|
hass.data[data_key] = func(hass)
|
2025-01-17 18:22:48 +00:00
|
|
|
return cast(_U, hass.data[data_key])
|
2020-10-18 20:41:22 +00:00
|
|
|
|
|
|
|
return wrapped
|
2020-04-30 23:47:14 +00:00
|
|
|
|
2020-05-04 18:23:12 +00:00
|
|
|
@bind_hass
|
2020-04-30 23:47:14 +00:00
|
|
|
@functools.wraps(func)
|
2025-01-17 18:22:48 +00:00
|
|
|
async def async_wrapped(hass: HomeAssistant) -> _T:
|
2021-09-11 19:02:01 +00:00
|
|
|
if data_key not in hass.data:
|
2020-04-30 23:47:14 +00:00
|
|
|
evt = hass.data[data_key] = asyncio.Event()
|
2023-06-21 14:12:51 +00:00
|
|
|
result = await func(hass)
|
2020-04-30 23:47:14 +00:00
|
|
|
hass.data[data_key] = result
|
|
|
|
evt.set()
|
2022-03-17 18:09:55 +00:00
|
|
|
return cast(_T, result)
|
2020-04-30 23:47:14 +00:00
|
|
|
|
2021-09-11 19:02:01 +00:00
|
|
|
obj_or_evt = hass.data[data_key]
|
|
|
|
|
2020-04-30 23:47:14 +00:00
|
|
|
if isinstance(obj_or_evt, asyncio.Event):
|
2021-09-11 19:02:01 +00:00
|
|
|
await obj_or_evt.wait()
|
2022-03-17 18:09:55 +00:00
|
|
|
return cast(_T, hass.data[data_key])
|
2020-04-30 23:47:14 +00:00
|
|
|
|
2022-03-17 18:09:55 +00:00
|
|
|
return cast(_T, obj_or_evt)
|
2020-04-30 23:47:14 +00:00
|
|
|
|
2025-01-17 18:22:48 +00:00
|
|
|
return async_wrapped
|
2020-04-30 23:47:14 +00:00
|
|
|
|
|
|
|
return wrapper
|
2025-01-17 18:22:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def _test_singleton_typing(hass: HomeAssistant) -> None:
|
|
|
|
"""Test singleton overloads work as intended.
|
|
|
|
|
|
|
|
This is tested during the mypy run. Do not move it to 'tests'!
|
|
|
|
"""
|
|
|
|
# Test HassKey
|
|
|
|
key = HassKey[int]("key")
|
|
|
|
|
|
|
|
@singleton(key)
|
|
|
|
def func(hass: HomeAssistant) -> int:
|
|
|
|
return 2
|
|
|
|
|
|
|
|
@singleton(key, async_=True)
|
|
|
|
async def async_func(hass: HomeAssistant) -> int:
|
|
|
|
return 2
|
|
|
|
|
|
|
|
assert_type(func(hass), int)
|
|
|
|
assert_type(await async_func(hass), int)
|
|
|
|
|
|
|
|
# Test invalid use of 'async_' with sync function
|
|
|
|
@singleton(key, async_=True) # type: ignore[arg-type]
|
|
|
|
def func_error(hass: HomeAssistant) -> int:
|
|
|
|
return 2
|
|
|
|
|
|
|
|
# Test string key
|
|
|
|
other_key = "key"
|
|
|
|
|
|
|
|
@singleton(other_key)
|
|
|
|
def func2(hass: HomeAssistant) -> str:
|
|
|
|
return ""
|
|
|
|
|
|
|
|
@singleton(other_key)
|
|
|
|
async def async_func2(hass: HomeAssistant) -> str:
|
|
|
|
return ""
|
|
|
|
|
|
|
|
assert_type(func2(hass), str)
|
|
|
|
assert_type(await async_func2(hass), str)
|