Only load translations for an integration once per test session (#117118)

This commit is contained in:
J. Nick Koston 2024-05-11 12:00:02 +09:00 committed by GitHub
parent 9e107a02db
commit d7aa24fa50
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 55 additions and 9 deletions

View file

@ -5,6 +5,7 @@ from __future__ import annotations
import asyncio
from collections.abc import Iterable, Mapping
from contextlib import suppress
from dataclasses import dataclass
import logging
import pathlib
import string
@ -140,22 +141,34 @@ async def _async_get_component_strings(
return translations_by_language
@dataclass(slots=True)
class _TranslationsCacheData:
"""Data for the translation cache.
This class contains data that is designed to be shared
between multiple instances of the translation cache so
we only have to load the data once.
"""
loaded: dict[str, set[str]]
cache: dict[str, dict[str, dict[str, dict[str, str]]]]
class _TranslationCache:
"""Cache for flattened translations."""
__slots__ = ("hass", "loaded", "cache", "lock")
__slots__ = ("hass", "cache_data", "lock")
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the cache."""
self.hass = hass
self.loaded: dict[str, set[str]] = {}
self.cache: dict[str, dict[str, dict[str, dict[str, str]]]] = {}
self.cache_data = _TranslationsCacheData({}, {})
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()))
return components.issubset(self.cache_data.loaded.get(language, set()))
async def async_load(
self,
@ -163,7 +176,7 @@ class _TranslationCache:
components: set[str],
) -> None:
"""Load resources into the cache."""
loaded = self.loaded.setdefault(language, set())
loaded = self.cache_data.loaded.setdefault(language, set())
if components_to_load := components - loaded:
# Translations are never unloaded so if there are no components to load
# we can skip the lock which reduces contention when multiple different
@ -193,7 +206,7 @@ class _TranslationCache:
components: set[str],
) -> dict[str, str]:
"""Read resources from the cache."""
category_cache = self.cache.get(language, {}).get(category, {})
category_cache = self.cache_data.cache.get(language, {}).get(category, {})
# If only one component was requested, return it directly
# to avoid merging the dictionaries and keeping additional
# copies of the same data in memory.
@ -207,6 +220,7 @@ class _TranslationCache:
async def _async_load(self, language: str, components: set[str]) -> None:
"""Populate the cache for a given set of components."""
loaded = self.cache_data.loaded
_LOGGER.debug(
"Cache miss for %s: %s",
language,
@ -240,7 +254,7 @@ class _TranslationCache:
language, components, translation_by_language_strings[language]
)
loaded_english_components = self.loaded.setdefault(LOCALE_EN, set())
loaded_english_components = loaded.setdefault(LOCALE_EN, set())
# Since we just loaded english anyway we can avoid loading
# again if they switch back to english.
if loaded_english_components.isdisjoint(components):
@ -249,7 +263,7 @@ class _TranslationCache:
)
loaded_english_components.update(components)
self.loaded[language].update(components)
loaded[language].update(components)
def _validate_placeholders(
self,
@ -304,7 +318,7 @@ class _TranslationCache:
) -> None:
"""Extract resources into the cache."""
resource: dict[str, Any] | str
cached = self.cache.setdefault(language, {})
cached = self.cache_data.cache.setdefault(language, {})
categories = {
category
for component in translation_strings.values()