Optimize loading of translations (#114089)

This commit is contained in:
J. Nick Koston 2024-03-23 22:22:09 -10:00 committed by GitHub
parent de831b6e87
commit e8cb6a8e29
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 88 additions and 47 deletions

View File

@ -734,7 +734,7 @@ async def _async_resolve_domains_to_setup(
*chain.from_iterable(platform_integrations.values()),
}
translations_to_load = {*domains_to_setup, *additional_manifests_to_load}
translations_to_load = additional_manifests_to_load.copy()
# Resolve all dependencies so we know all integrations
# that will have to be loaded and start right-away
@ -818,6 +818,12 @@ async def _async_resolve_domains_to_setup(
"check installed requirements",
eager_start=True,
)
#
# Only add the domains_to_setup after we finish resolving
# as new domains are likely to added in the process
#
translations_to_load.update(domains_to_setup)
# Start loading translations for all integrations we are going to set up
# in the background so they are ready when we need them. This avoids a
# lot of waiting for the translation load lock and a thundering herd of

View File

@ -29,6 +29,7 @@ from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import CONF_HOST, CONF_MODEL, CONF_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import device_registry as dr, discovery_flow
from homeassistant.util.async_ import create_eager_task
from homeassistant.util.network import is_ip_address
from .const import (
@ -185,7 +186,7 @@ async def async_discover_devices(
for idx, discovered in enumerate(
await asyncio.gather(
*[
scanner.async_scan(timeout=timeout, address=address)
create_eager_task(scanner.async_scan(timeout=timeout, address=address))
for address in targets
],
return_exceptions=True,

View File

@ -10,7 +10,7 @@ from discovery30303 import Device30303, normalize_mac
import voluptuous as vol
from homeassistant.components import dhcp
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.config_entries import ConfigEntryState, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_MODEL, CONF_NAME
from homeassistant.core import callback
from homeassistant.helpers import device_registry as dr
@ -72,10 +72,11 @@ class SteamistConfigFlow(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(mac)
for entry in self._async_current_entries(include_ignore=False):
if entry.unique_id == mac or entry.data[CONF_HOST] == host:
if async_update_entry_from_discovery(self.hass, entry, device):
self.hass.async_create_task(
self.hass.config_entries.async_reload(entry.entry_id)
)
if (
async_update_entry_from_discovery(self.hass, entry, device)
and entry.state is not ConfigEntryState.SETUP_IN_PROGRESS
):
self.hass.config_entries.async_schedule_reload(entry.entry_id)
return self.async_abort(reason="already_configured")
self.context[CONF_HOST] = host
for progress in self._async_in_progress():

View File

@ -285,6 +285,19 @@ async def _async_setup_component( # noqa: C901
log_error(f"Dependency is disabled - {integration.disabled}")
return False
integration_set = {domain}
load_translations_task: asyncio.Task[None] | None = None
if integration.has_translations and not translation.async_translations_loaded(
hass, integration_set
):
# For most cases we expect the translations are already
# loaded since we try to load them in bootstrap ahead of time.
# If for some reason the background task in bootstrap was too slow
# or the integration was added after bootstrap, we will load them here.
load_translations_task = create_eager_task(
translation.async_load_integrations(hass, integration_set)
)
# Validate all dependencies exist and there are no circular dependencies
if not await integration.resolve_dependencies():
return False
@ -351,17 +364,6 @@ async def _async_setup_component( # noqa: C901
)
_LOGGER.info("Setting up %s", domain)
integration_set = {domain}
load_translations_task: asyncio.Task[None] | None = None
if not translation.async_translations_loaded(hass, integration_set):
# For most cases we expect the translations are already
# loaded since we try to load them in bootstrap ahead of time.
# If for some reason the background task in bootstrap was too slow
# or the integration was added after bootstrap, we will load them here.
load_translations_task = create_eager_task(
translation.async_load_integrations(hass, integration_set)
)
with async_start_setup(hass, integration=domain, phase=SetupPhases.SETUP):
if hasattr(component, "PLATFORM_SCHEMA"):

View File

@ -234,6 +234,8 @@ async def test_discover_platform(
) -> None:
"""Test discovery of device_tracker demo platform."""
await async_setup_component(hass, "homeassistant", {})
await async_setup_component(hass, device_tracker.DOMAIN, {})
await hass.async_block_till_done()
with patch("homeassistant.components.device_tracker.legacy.update_config"):
await discovery.async_load_platform(
hass, device_tracker.DOMAIN, "demo", {"test_key": "test_val"}, {"bla": {}}
@ -337,6 +339,7 @@ async def test_see_service(
"""Test the see service with a unicode dev_id and NO MAC."""
with assert_setup_component(1, device_tracker.DOMAIN):
assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM)
await hass.async_block_till_done()
params = {
"dev_id": "some_device",
"host_name": "example.com",
@ -372,6 +375,7 @@ async def test_see_service_guard_config_entry(
mock_registry(hass, {entity_id: mock_entry})
devices = mock_device_tracker_conf
assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM)
await hass.async_block_till_done()
params = {"dev_id": dev_id, "gps": [0.3, 0.8]}
common.async_see(hass, **params)
@ -388,6 +392,7 @@ async def test_new_device_event_fired(
"""Test that the device tracker will fire an event."""
with assert_setup_component(1, device_tracker.DOMAIN):
assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM)
await hass.async_block_till_done()
test_events = []
@callback
@ -423,6 +428,7 @@ async def test_duplicate_yaml_keys(
devices = mock_device_tracker_conf
with assert_setup_component(1, device_tracker.DOMAIN):
assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM)
await hass.async_block_till_done()
common.async_see(hass, "mac_1", host_name="hello")
common.async_see(hass, "mac_2", host_name="hello")
@ -442,6 +448,7 @@ async def test_invalid_dev_id(
devices = mock_device_tracker_conf
with assert_setup_component(1, device_tracker.DOMAIN):
assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM)
await hass.async_block_till_done()
common.async_see(hass, dev_id="hello-world")
await hass.async_block_till_done()
@ -454,6 +461,7 @@ async def test_see_state(
) -> None:
"""Test device tracker see records state correctly."""
assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM)
await hass.async_block_till_done()
params = {
"mac": "AA:BB:CC:DD:EE:FF",
@ -508,6 +516,7 @@ async def test_see_passive_zone_state(
}
await async_setup_component(hass, zone.DOMAIN, {"zone": zone_info})
await hass.async_block_till_done()
scanner = getattr(hass.components, "test.device_tracker").SCANNER
scanner.reset()
@ -603,6 +612,7 @@ async def test_async_added_to_hass(hass: HomeAssistant) -> None:
files = {path: "jk:\n name: JK Phone\n track: True"}
with patch_yaml_files(files):
assert await async_setup_component(hass, device_tracker.DOMAIN, {})
await hass.async_block_till_done()
state = hass.states.get("device_tracker.jk")
assert state

View File

@ -70,7 +70,7 @@ async def test_configuring_tplink_causes_discovery(
async def test_config_entry_reload(hass: HomeAssistant) -> None:
"""Test that a config entry can be reloaded."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
with _patch_discovery(), _patch_single_discovery(), _patch_connect():
@ -266,7 +266,9 @@ async def test_config_entry_errors(
async def test_plug_auth_fails(hass: HomeAssistant) -> None:
"""Test a smart plug auth failure."""
config_entry = MockConfigEntry(domain=DOMAIN, data={}, unique_id=MAC_ADDRESS)
config_entry = MockConfigEntry(
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
config_entry.add_to_hass(hass)
plug = _mocked_plug()
with _patch_discovery(device=plug), _patch_connect(device=plug):

View File

@ -24,7 +24,7 @@ from homeassistant.components.light import (
DOMAIN as LIGHT_DOMAIN,
)
from homeassistant.components.tplink.const import DOMAIN
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
@ -45,7 +45,7 @@ from tests.common import MockConfigEntry, async_fire_time_changed
async def test_light_unique_id(hass: HomeAssistant) -> None:
"""Test a light unique id."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
bulb = _mocked_bulb()
@ -67,7 +67,7 @@ async def test_color_light(
) -> None:
"""Test a color light and that all transitions are correctly passed."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
bulb.color_temp = None
@ -147,7 +147,7 @@ async def test_color_light(
async def test_color_light_no_temp(hass: HomeAssistant) -> None:
"""Test a light."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
bulb = _mocked_bulb()
@ -207,7 +207,7 @@ async def test_color_temp_light(
) -> None:
"""Test a light."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
bulb.is_color = is_color
@ -286,7 +286,7 @@ async def test_color_temp_light(
async def test_brightness_only_light(hass: HomeAssistant) -> None:
"""Test a light."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
bulb = _mocked_bulb()
@ -330,7 +330,7 @@ async def test_brightness_only_light(hass: HomeAssistant) -> None:
async def test_on_off_light(hass: HomeAssistant) -> None:
"""Test a light."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
bulb = _mocked_bulb()
@ -364,7 +364,7 @@ async def test_on_off_light(hass: HomeAssistant) -> None:
async def test_off_at_start_light(hass: HomeAssistant) -> None:
"""Test a light."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
bulb = _mocked_bulb()
@ -388,7 +388,7 @@ async def test_off_at_start_light(hass: HomeAssistant) -> None:
async def test_dimmer_turn_on_fix(hass: HomeAssistant) -> None:
"""Test a light."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
bulb = _mocked_bulb()
@ -414,7 +414,7 @@ async def test_dimmer_turn_on_fix(hass: HomeAssistant) -> None:
async def test_smart_strip_effects(hass: HomeAssistant) -> None:
"""Test smart strip effects."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
strip = _mocked_smart_light_strip()
@ -496,7 +496,7 @@ async def test_smart_strip_effects(hass: HomeAssistant) -> None:
async def test_smart_strip_custom_random_effect(hass: HomeAssistant) -> None:
"""Test smart strip custom random effects."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
strip = _mocked_smart_light_strip()
@ -653,7 +653,7 @@ async def test_smart_strip_custom_random_effect(hass: HomeAssistant) -> None:
async def test_smart_strip_custom_random_effect_at_start(hass: HomeAssistant) -> None:
"""Test smart strip custom random effects at startup."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
strip = _mocked_smart_light_strip()
@ -686,7 +686,7 @@ async def test_smart_strip_custom_random_effect_at_start(hass: HomeAssistant) ->
async def test_smart_strip_custom_sequence_effect(hass: HomeAssistant) -> None:
"""Test smart strip custom sequence effects."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
strip = _mocked_smart_light_strip()

View File

@ -4,6 +4,7 @@ from unittest.mock import Mock
from homeassistant.components import tplink
from homeassistant.components.tplink.const import DOMAIN
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
@ -16,7 +17,7 @@ from tests.common import MockConfigEntry
async def test_color_light_with_an_emeter(hass: HomeAssistant) -> None:
"""Test a light with an emeter."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
bulb = _mocked_bulb()
@ -56,7 +57,7 @@ async def test_color_light_with_an_emeter(hass: HomeAssistant) -> None:
async def test_plug_with_an_emeter(hass: HomeAssistant) -> None:
"""Test a plug with an emeter."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
plug = _mocked_plug()
@ -91,7 +92,7 @@ async def test_plug_with_an_emeter(hass: HomeAssistant) -> None:
async def test_color_light_no_emeter(hass: HomeAssistant) -> None:
"""Test a light without an emeter."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
bulb = _mocked_bulb()
@ -120,7 +121,7 @@ async def test_color_light_no_emeter(hass: HomeAssistant) -> None:
async def test_sensor_unique_id(hass: HomeAssistant) -> None:
"""Test a sensor unique ids."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
plug = _mocked_plug()

View File

@ -9,7 +9,13 @@ import pytest
from homeassistant.components import tplink
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.tplink.const import DOMAIN
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_HOST,
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
@ -30,7 +36,7 @@ from tests.common import MockConfigEntry, async_fire_time_changed
async def test_plug(hass: HomeAssistant) -> None:
"""Test a smart plug."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
plug = _mocked_plug()
@ -66,7 +72,7 @@ async def test_plug(hass: HomeAssistant) -> None:
async def test_led_switch(hass: HomeAssistant, dev, domain: str) -> None:
"""Test LED setting for plugs, strips and dimmers."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
with _patch_discovery(device=dev), _patch_connect(device=dev):
@ -96,7 +102,7 @@ async def test_led_switch(hass: HomeAssistant, dev, domain: str) -> None:
async def test_plug_unique_id(hass: HomeAssistant) -> None:
"""Test a plug unique id."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
plug = _mocked_plug()
@ -112,7 +118,7 @@ async def test_plug_unique_id(hass: HomeAssistant) -> None:
async def test_plug_update_fails(hass: HomeAssistant) -> None:
"""Test a smart plug update failure."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
plug = _mocked_plug()
@ -134,7 +140,7 @@ async def test_plug_update_fails(hass: HomeAssistant) -> None:
async def test_strip(hass: HomeAssistant) -> None:
"""Test a smart strip."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
strip = _mocked_strip()
@ -182,7 +188,7 @@ async def test_strip(hass: HomeAssistant) -> None:
async def test_strip_unique_ids(hass: HomeAssistant) -> None:
"""Test a strip unique id."""
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={}, unique_id=MAC_ADDRESS
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=MAC_ADDRESS
)
already_migrated_config_entry.add_to_hass(hass)
strip = _mocked_strip()

View File

@ -1,5 +1,4 @@
"""Test the bootstrapping."""
import asyncio
from collections.abc import Generator, Iterable
import glob
@ -17,8 +16,10 @@ from homeassistant.const import SIGNAL_BOOTSTRAP_INTEGRATIONS
from homeassistant.core import CoreState, HomeAssistant, async_get_hass, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.translation import async_translations_loaded
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import Integration
from homeassistant.setup import BASE_PLATFORMS
from .common import (
MockConfigEntry,
@ -105,6 +106,16 @@ async def test_empty_setup(hass: HomeAssistant) -> None:
assert domain in hass.config.components, domain
@pytest.mark.parametrize("load_registries", [False])
async def test_preload_translations(hass: HomeAssistant) -> None:
"""Test translations are preloaded for all frontend deps and base platforms."""
await bootstrap.async_from_config_dict({}, hass)
await hass.async_block_till_done(wait_background_tasks=True)
frontend = await loader.async_get_integration(hass, "frontend")
assert async_translations_loaded(hass, set(frontend.all_dependencies))
assert async_translations_loaded(hass, BASE_PLATFORMS)
async def test_core_failure_loads_recovery_mode(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:

View File

@ -1167,8 +1167,9 @@ async def test_loading_component_loads_translations(hass: HomeAssistant) -> None
mock_setup = Mock(return_value=True)
mock_integration(hass, MockModule("comp", setup=mock_setup))
assert await setup.async_setup_component(hass, "comp", {})
integration = await loader.async_get_integration(hass, "comp")
with patch.object(integration, "has_translations", True):
assert await setup.async_setup_component(hass, "comp", {})
assert mock_setup.called
assert translation.async_translations_loaded(hass, {"comp"}) is True