Add filter to translation event listeners to avoid creating tasks (#110732)

This commit is contained in:
J. Nick Koston 2024-02-16 12:13:23 -06:00 committed by GitHub
parent 6f74ea9186
commit 2cb0249f0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 57 additions and 14 deletions

View file

@ -5,7 +5,7 @@ import asyncio
from collections.abc import Iterable, Mapping from collections.abc import Iterable, Mapping
import logging import logging
import string import string
from typing import Any from typing import TYPE_CHECKING, Any
from homeassistant.const import ( from homeassistant.const import (
EVENT_COMPONENT_LOADED, EVENT_COMPONENT_LOADED,
@ -205,6 +205,11 @@ class _TranslationCache:
self.cache: dict[str, dict[str, dict[str, dict[str, str]]]] = {} self.cache: dict[str, dict[str, dict[str, dict[str, str]]]] = {}
self.lock = asyncio.Lock() self.lock = asyncio.Lock()
@callback
def async_is_loaded(self, language: str, components: set[str]) -> bool:
"""Return if the given components are loaded for the language."""
return components.issubset(self.loaded.get(language, set()))
async def async_load( async def async_load(
self, self,
language: str, language: str,
@ -465,20 +470,41 @@ def async_setup(hass: HomeAssistant) -> None:
Listeners load translations for every loaded component and after config change. Listeners load translations for every loaded component and after config change.
""" """
cache = _TranslationCache(hass)
current_language = hass.config.language
hass.data[TRANSLATION_FLATTEN_CACHE] = cache
hass.data[TRANSLATION_FLATTEN_CACHE] = _TranslationCache(hass) @callback
def _async_load_translations_filter(event: Event) -> bool:
"""Filter out unwanted events."""
nonlocal current_language
if (
new_language := event.data.get("language")
) and new_language != current_language:
current_language = new_language
return True
return False
async def load_translations(event: Event) -> None: async def _async_load_translations(event: Event) -> None:
if "language" in event.data: new_language = event.data["language"]
language = hass.config.language _LOGGER.debug("Loading translations for language: %s", new_language)
_LOGGER.debug("Loading translations for language: %s", language) await _async_load_state_translations_to_cache(hass, new_language, None)
await _async_load_state_translations_to_cache(hass, language, None)
async def load_translations_for_component(event: Event) -> None: @callback
component = event.data.get("component") def _async_load_translations_for_component_filter(event: Event) -> bool:
"""Filter out unwanted events."""
component: str | None = event.data.get("component")
# Platforms don't have their own translations, skip them # Platforms don't have their own translations, skip them
if component is None or "." in str(component): return bool(
return component
and "." not in component
and not cache.async_is_loaded(hass.config.language, {component})
)
async def _async_load_translations_for_component(event: Event) -> None:
component: str | None = event.data.get("component")
if TYPE_CHECKING:
assert component is not None
language = hass.config.language language = hass.config.language
_LOGGER.debug( _LOGGER.debug(
"Loading translations for language: %s and component: %s", "Loading translations for language: %s and component: %s",
@ -487,8 +513,16 @@ def async_setup(hass: HomeAssistant) -> None:
) )
await _async_load_state_translations_to_cache(hass, language, component) await _async_load_state_translations_to_cache(hass, language, component)
hass.bus.async_listen(EVENT_COMPONENT_LOADED, load_translations_for_component) hass.bus.async_listen(
hass.bus.async_listen(EVENT_CORE_CONFIG_UPDATE, load_translations) EVENT_COMPONENT_LOADED,
_async_load_translations_for_component,
event_filter=_async_load_translations_for_component_filter,
)
hass.bus.async_listen(
EVENT_CORE_CONFIG_UPDATE,
_async_load_translations,
event_filter=_async_load_translations_filter,
)
@callback @callback

View file

@ -602,12 +602,21 @@ async def test_setup(hass: HomeAssistant):
await hass.async_block_till_done() await hass.async_block_till_done()
mock.assert_not_called() mock.assert_not_called()
# Should not be called if the language is the current language
with patch( with patch(
"homeassistant.helpers.translation._async_load_state_translations_to_cache", "homeassistant.helpers.translation._async_load_state_translations_to_cache",
) as mock: ) as mock:
hass.bus.async_fire(EVENT_CORE_CONFIG_UPDATE, {"language": "en"}) hass.bus.async_fire(EVENT_CORE_CONFIG_UPDATE, {"language": "en"})
await hass.async_block_till_done() await hass.async_block_till_done()
mock.assert_called_once_with(hass, hass.config.language, None) mock.assert_not_called()
# Should be called if the language is different
with patch(
"homeassistant.helpers.translation._async_load_state_translations_to_cache",
) as mock:
hass.bus.async_fire(EVENT_CORE_CONFIG_UPDATE, {"language": "es"})
await hass.async_block_till_done()
mock.assert_called_once_with(hass, "es", None)
with patch( with patch(
"homeassistant.helpers.translation._async_load_state_translations_to_cache", "homeassistant.helpers.translation._async_load_state_translations_to_cache",