Speed up singleton decorator so it can be used more places (#116292)

This commit is contained in:
J. Nick Koston 2024-04-28 12:11:08 -05:00 committed by GitHub
parent 48b1678075
commit ab2ea6100c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 25 additions and 28 deletions

View file

@ -90,7 +90,11 @@ from .helpers.system_info import async_get_system_info
from .helpers.typing import ConfigType from .helpers.typing import ConfigType
from .setup import ( from .setup import (
BASE_PLATFORMS, BASE_PLATFORMS,
DATA_SETUP_STARTED, # _setup_started is marked as protected to make it clear
# that it is not part of the public API and should not be used
# by integrations. It is only used for internal tracking of
# which integrations are being set up.
_setup_started,
async_get_setup_timings, async_get_setup_timings,
async_notify_setup_error, async_notify_setup_error,
async_set_domains_to_be_loaded, async_set_domains_to_be_loaded,
@ -913,9 +917,7 @@ async def _async_set_up_integrations(
hass: core.HomeAssistant, config: dict[str, Any] hass: core.HomeAssistant, config: dict[str, Any]
) -> None: ) -> None:
"""Set up all the integrations.""" """Set up all the integrations."""
setup_started: dict[tuple[str, str | None], float] = {} watcher = _WatchPendingSetups(hass, _setup_started(hass))
hass.data[DATA_SETUP_STARTED] = setup_started
watcher = _WatchPendingSetups(hass, setup_started)
watcher.async_start() watcher.async_start()
domains_to_setup, integration_cache = await _async_resolve_domains_to_setup( domains_to_setup, integration_cache = await _async_resolve_domains_to_setup(

View file

@ -66,7 +66,7 @@ from homeassistant.loader import async_suggest_report_issue, bind_hass
from homeassistant.util import ensure_unique_string, slugify from homeassistant.util import ensure_unique_string, slugify
from homeassistant.util.frozen_dataclass_compat import FrozenOrThawed from homeassistant.util.frozen_dataclass_compat import FrozenOrThawed
from . import device_registry as dr, entity_registry as er from . import device_registry as dr, entity_registry as er, singleton
from .device_registry import DeviceInfo, EventDeviceRegistryUpdatedData from .device_registry import DeviceInfo, EventDeviceRegistryUpdatedData
from .event import ( from .event import (
async_track_device_registry_updated_event, async_track_device_registry_updated_event,
@ -98,15 +98,15 @@ CONTEXT_RECENT_TIME_SECONDS = 5 # Time that a context is considered recent
@callback @callback
def async_setup(hass: HomeAssistant) -> None: def async_setup(hass: HomeAssistant) -> None:
"""Set up entity sources.""" """Set up entity sources."""
hass.data[DATA_ENTITY_SOURCE] = {} entity_sources(hass)
@callback @callback
@bind_hass @bind_hass
@singleton.singleton(DATA_ENTITY_SOURCE)
def entity_sources(hass: HomeAssistant) -> dict[str, EntityInfo]: def entity_sources(hass: HomeAssistant) -> dict[str, EntityInfo]:
"""Get the entity sources.""" """Get the entity sources."""
_entity_sources: dict[str, EntityInfo] = hass.data[DATA_ENTITY_SOURCE] return {}
return _entity_sources
def generate_entity_id( def generate_entity_id(
@ -1486,7 +1486,7 @@ class Entity(
# The check for self.platform guards against integrations not using an # The check for self.platform guards against integrations not using an
# EntityComponent and can be removed in HA Core 2024.1 # EntityComponent and can be removed in HA Core 2024.1
if self.platform: if self.platform:
self.hass.data[DATA_ENTITY_SOURCE].pop(self.entity_id) entity_sources(self.hass).pop(self.entity_id)
@callback @callback
def _async_registry_updated( def _async_registry_updated(

View file

@ -25,6 +25,7 @@ def singleton(data_key: str) -> Callable[[_FuncType[_T]], _FuncType[_T]]:
"""Wrap a function with caching logic.""" """Wrap a function with caching logic."""
if not asyncio.iscoroutinefunction(func): if not asyncio.iscoroutinefunction(func):
@functools.lru_cache(maxsize=1)
@bind_hass @bind_hass
@functools.wraps(func) @functools.wraps(func)
def wrapped(hass: HomeAssistant) -> _T: def wrapped(hass: HomeAssistant) -> _T:

View file

@ -24,6 +24,8 @@ from homeassistant.loader import (
) )
from homeassistant.util.json import load_json from homeassistant.util.json import load_json
from . import singleton
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
TRANSLATION_FLATTEN_CACHE = "translation_flatten_cache" TRANSLATION_FLATTEN_CACHE = "translation_flatten_cache"
@ -370,11 +372,10 @@ def async_get_cached_translations(
) )
@callback @singleton.singleton(TRANSLATION_FLATTEN_CACHE)
def _async_get_translations_cache(hass: HomeAssistant) -> _TranslationCache: def _async_get_translations_cache(hass: HomeAssistant) -> _TranslationCache:
"""Return the translation cache.""" """Return the translation cache."""
cache: _TranslationCache = hass.data[TRANSLATION_FLATTEN_CACHE] return _TranslationCache(hass)
return cache
@callback @callback
@ -385,7 +386,7 @@ def async_setup(hass: HomeAssistant) -> None:
""" """
cache = _TranslationCache(hass) cache = _TranslationCache(hass)
current_language = hass.config.language current_language = hass.config.language
hass.data[TRANSLATION_FLATTEN_CACHE] = cache _async_get_translations_cache(hass)
@callback @callback
def _async_load_translations_filter(event_data: Mapping[str, Any]) -> bool: def _async_load_translations_filter(event_data: Mapping[str, Any]) -> bool:

View file

@ -12,6 +12,7 @@ from packaging.requirements import Requirement
from .core import HomeAssistant, callback from .core import HomeAssistant, callback
from .exceptions import HomeAssistantError from .exceptions import HomeAssistantError
from .helpers import singleton
from .helpers.typing import UNDEFINED, UndefinedType from .helpers.typing import UNDEFINED, UndefinedType
from .loader import Integration, IntegrationNotFound, async_get_integration from .loader import Integration, IntegrationNotFound, async_get_integration
from .util import package as pkg_util from .util import package as pkg_util
@ -72,14 +73,10 @@ async def async_load_installed_versions(
@callback @callback
@singleton.singleton(DATA_REQUIREMENTS_MANAGER)
def _async_get_manager(hass: HomeAssistant) -> RequirementsManager: def _async_get_manager(hass: HomeAssistant) -> RequirementsManager:
"""Get the requirements manager.""" """Get the requirements manager."""
if DATA_REQUIREMENTS_MANAGER in hass.data: return RequirementsManager(hass)
manager: RequirementsManager = hass.data[DATA_REQUIREMENTS_MANAGER]
return manager
manager = hass.data[DATA_REQUIREMENTS_MANAGER] = RequirementsManager(hass)
return manager
@callback @callback

View file

@ -29,7 +29,7 @@ from .core import (
callback, callback,
) )
from .exceptions import DependencyError, HomeAssistantError from .exceptions import DependencyError, HomeAssistantError
from .helpers import translation from .helpers import singleton, translation
from .helpers.issue_registry import IssueSeverity, async_create_issue from .helpers.issue_registry import IssueSeverity, async_create_issue
from .helpers.typing import ConfigType from .helpers.typing import ConfigType
from .util.async_ import create_eager_task from .util.async_ import create_eager_task
@ -671,13 +671,12 @@ class SetupPhases(StrEnum):
"""Wait time for the packages to import.""" """Wait time for the packages to import."""
@singleton.singleton(DATA_SETUP_STARTED)
def _setup_started( def _setup_started(
hass: core.HomeAssistant, hass: core.HomeAssistant,
) -> dict[tuple[str, str | None], float]: ) -> dict[tuple[str, str | None], float]:
"""Return the setup started dict.""" """Return the setup started dict."""
if DATA_SETUP_STARTED not in hass.data: return {}
hass.data[DATA_SETUP_STARTED] = {}
return hass.data[DATA_SETUP_STARTED] # type: ignore[no-any-return]
@contextlib.contextmanager @contextlib.contextmanager
@ -717,15 +716,12 @@ def async_pause_setup(
) )
@singleton.singleton(DATA_SETUP_TIME)
def _setup_times( def _setup_times(
hass: core.HomeAssistant, hass: core.HomeAssistant,
) -> defaultdict[str, defaultdict[str | None, defaultdict[SetupPhases, float]]]: ) -> defaultdict[str, defaultdict[str | None, defaultdict[SetupPhases, float]]]:
"""Return the setup timings default dict.""" """Return the setup timings default dict."""
if DATA_SETUP_TIME not in hass.data: return defaultdict(lambda: defaultdict(lambda: defaultdict(float)))
hass.data[DATA_SETUP_TIME] = defaultdict(
lambda: defaultdict(lambda: defaultdict(float))
)
return hass.data[DATA_SETUP_TIME] # type: ignore[no-any-return]
@contextlib.contextmanager @contextlib.contextmanager