Add the ability to process integration platforms on demand (#70174)

This commit is contained in:
J. Nick Koston 2022-04-17 00:23:00 -10:00 committed by GitHub
parent 4184c97b65
commit 42c448c422
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 102 additions and 32 deletions

View File

@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio import asyncio
from collections.abc import Awaitable, Callable from collections.abc import Awaitable, Callable
from dataclasses import dataclass
import logging import logging
from typing import Any from typing import Any
@ -12,6 +13,73 @@ from homeassistant.loader import async_get_integration, bind_hass
from homeassistant.setup import ATTR_COMPONENT from homeassistant.setup import ATTR_COMPONENT
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DATA_INTEGRATION_PLATFORMS = "integration_platforms"
@dataclass(frozen=True)
class IntegrationPlatform:
"""An integration platform."""
platform_name: str
process_platform: Callable[[HomeAssistant, str, Any], Awaitable[None]]
seen_components: set[str]
async def _async_process_single_integration_platform(
hass: HomeAssistant, component_name: str, integration_platform: IntegrationPlatform
) -> None:
"""Process a single integration platform."""
if component_name in integration_platform.seen_components:
return
integration_platform.seen_components.add(component_name)
integration = await async_get_integration(hass, component_name)
platform_name = integration_platform.platform_name
try:
platform = integration.get_platform(platform_name)
except ImportError as err:
if f"{component_name}.{platform_name}" not in str(err):
_LOGGER.exception(
"Unexpected error importing %s/%s.py",
component_name,
platform_name,
)
return
try:
await integration_platform.process_platform(hass, component_name, platform) # type: ignore[misc,operator] # https://github.com/python/mypy/issues/5485
except Exception: # pylint: disable=broad-except
_LOGGER.exception(
"Error processing platform %s.%s", component_name, platform_name
)
async def async_process_integration_platform(
hass: HomeAssistant, component_name: str
) -> None:
"""Process integration platforms on demand.
This function will load the integration platforms
for an integration instead of waiting for the EVENT_COMPONENT_LOADED
event to be fired for the integration.
When the integration will create entities before
it has finished setting up; call this function to ensure
that the integration platforms are loaded before the entities
are created.
"""
integration_platforms: list[IntegrationPlatform] = hass.data[
DATA_INTEGRATION_PLATFORMS
]
await asyncio.gather(
*[
_async_process_single_integration_platform(
hass, component_name, integration_platform
)
for integration_platform in integration_platforms
]
)
@bind_hass @bind_hass
@ -22,39 +90,30 @@ async def async_process_integration_platforms(
process_platform: Callable[[HomeAssistant, str, Any], Awaitable[None]], process_platform: Callable[[HomeAssistant, str, Any], Awaitable[None]],
) -> None: ) -> None:
"""Process a specific platform for all current and future loaded integrations.""" """Process a specific platform for all current and future loaded integrations."""
if DATA_INTEGRATION_PLATFORMS not in hass.data:
hass.data[DATA_INTEGRATION_PLATFORMS] = []
async def _process(component_name: str) -> None: async def _async_component_loaded(event: Event) -> None:
"""Process component being loaded.""" """Handle a new component loaded."""
if "." in component_name: comp = event.data[ATTR_COMPONENT]
return if "." not in comp:
await async_process_integration_platform(hass, comp)
integration = await async_get_integration(hass, component_name) hass.bus.async_listen(EVENT_COMPONENT_LOADED, _async_component_loaded)
try: integration_platforms: list[IntegrationPlatform] = hass.data[
platform = integration.get_platform(platform_name) DATA_INTEGRATION_PLATFORMS
except ImportError as err: ]
if f"{component_name}.{platform_name}" not in str(err): integration_platform = IntegrationPlatform(platform_name, process_platform, set())
_LOGGER.exception( integration_platforms.append(integration_platform)
"Unexpected error importing %s/%s.py", if top_level_components := (
component_name, comp for comp in hass.config.components if "." not in comp
platform_name, ):
await asyncio.gather(
*[
_async_process_single_integration_platform(
hass, comp, integration_platform
) )
return for comp in top_level_components
]
try: )
await process_platform(hass, component_name, platform)
except Exception: # pylint: disable=broad-except
_LOGGER.exception(
"Error processing platform %s.%s", component_name, platform_name
)
async def async_component_loaded(event: Event) -> None:
"""Handle a new component loaded."""
await _process(event.data[ATTR_COMPONENT])
hass.bus.async_listen(EVENT_COMPONENT_LOADED, async_component_loaded)
tasks = [_process(comp) for comp in hass.config.components]
if tasks:
await asyncio.gather(*tasks)

View File

@ -60,6 +60,7 @@ async def test_cover_intents_loading(hass):
) )
assert await async_setup_component(hass, "cover", {}) assert await async_setup_component(hass, "cover", {})
await hass.async_block_till_done()
hass.states.async_set("cover.garage_door", "closed") hass.states.async_set("cover.garage_door", "closed")
calls = async_mock_service(hass, "cover", SERVICE_OPEN_COVER) calls = async_mock_service(hass, "cover", SERVICE_OPEN_COVER)
@ -81,6 +82,7 @@ async def test_turn_on_intent(hass):
"""Test HassTurnOn intent.""" """Test HassTurnOn intent."""
result = await async_setup_component(hass, "homeassistant", {}) result = await async_setup_component(hass, "homeassistant", {})
result = await async_setup_component(hass, "intent", {}) result = await async_setup_component(hass, "intent", {})
await hass.async_block_till_done()
assert result assert result
hass.states.async_set("light.test_light", "off") hass.states.async_set("light.test_light", "off")

View File

@ -24,6 +24,7 @@ async def test_system_health_info_storage(hass, hass_storage):
"data": {"config": {"resources": [], "views": []}}, "data": {"config": {"resources": [], "views": []}},
} }
assert await async_setup_component(hass, "lovelace", {}) assert await async_setup_component(hass, "lovelace", {})
await hass.async_block_till_done()
info = await get_system_health_info(hass, "lovelace") info = await get_system_health_info(hass, "lovelace")
assert info == {"dashboards": 1, "mode": "storage", "resources": 0, "views": 0} assert info == {"dashboards": 1, "mode": "storage", "resources": 0, "views": 0}
@ -32,6 +33,7 @@ async def test_system_health_info_yaml(hass):
"""Test system health info endpoint.""" """Test system health info endpoint."""
assert await async_setup_component(hass, "system_health", {}) assert await async_setup_component(hass, "system_health", {})
assert await async_setup_component(hass, "lovelace", {"lovelace": {"mode": "YAML"}}) assert await async_setup_component(hass, "lovelace", {"lovelace": {"mode": "YAML"}})
await hass.async_block_till_done()
with patch( with patch(
"homeassistant.components.lovelace.dashboard.load_yaml", "homeassistant.components.lovelace.dashboard.load_yaml",
return_value={"views": [{"cards": []}]}, return_value={"views": [{"cards": []}]},
@ -44,6 +46,7 @@ async def test_system_health_info_yaml_not_found(hass):
"""Test system health info endpoint.""" """Test system health info endpoint."""
assert await async_setup_component(hass, "system_health", {}) assert await async_setup_component(hass, "system_health", {})
assert await async_setup_component(hass, "lovelace", {"lovelace": {"mode": "YAML"}}) assert await async_setup_component(hass, "lovelace", {"lovelace": {"mode": "YAML"}})
await hass.async_block_till_done()
info = await get_system_health_info(hass, "lovelace") info = await get_system_health_info(hass, "lovelace")
assert info == { assert info == {
"dashboards": 1, "dashboards": 1,

View File

@ -35,3 +35,9 @@ async def test_process_integration_platforms(hass):
assert len(processed) == 2 assert len(processed) == 2
assert processed[1][0] == "event" assert processed[1][0] == "event"
assert processed[1][1] == event_platform assert processed[1][1] == event_platform
# Verify we only process the platform once if we call it manually
await hass.helpers.integration_platform.async_process_integration_platform(
hass, "event"
)
assert len(processed) == 2