From 3b246fb40a9f33212e4a9dc987ecba8f294bfad4 Mon Sep 17 00:00:00 2001 From: Jason Cheatham Date: Wed, 8 Apr 2020 14:42:15 -0400 Subject: [PATCH] Load integrations with requirements in device_automation (#33714) * Load integrations with requirements in device_automation - Split cached loader behavior out of async_get_integration - Use cached loader for both async_get_integration and async_get_integration_with_requirements - Use async_get_integration_with_requirements for device_automation resolves #33104 * Duplicate caching logic in requirements, remove loader mods * Update homeassistant/requirements.py Co-authored-by: Paulus Schoutsen --- .../components/device_automation/__init__.py | 5 +-- homeassistant/requirements.py | 34 +++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/device_automation/__init__.py b/homeassistant/components/device_automation/__init__.py index 95b3fc9fdb3..33685b2bc1c 100644 --- a/homeassistant/components/device_automation/__init__.py +++ b/homeassistant/components/device_automation/__init__.py @@ -13,7 +13,8 @@ from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity_registry import async_entries_for_device -from homeassistant.loader import IntegrationNotFound, async_get_integration +from homeassistant.loader import IntegrationNotFound +from homeassistant.requirements import async_get_integration_with_requirements from .exceptions import DeviceNotFound, InvalidDeviceAutomationConfig @@ -80,7 +81,7 @@ async def async_get_device_automation_platform( """ platform_name = TYPES[automation_type][0] try: - integration = await async_get_integration(hass, domain) + integration = await async_get_integration_with_requirements(hass, domain) platform = integration.get_platform(platform_name) except IntegrationNotFound: raise InvalidDeviceAutomationConfig(f"Integration '{domain}' not found") diff --git a/homeassistant/requirements.py b/homeassistant/requirements.py index 317fffe84bf..505704f63c2 100644 --- a/homeassistant/requirements.py +++ b/homeassistant/requirements.py @@ -3,15 +3,21 @@ import asyncio import logging import os from pathlib import Path -from typing import Any, Dict, Iterable, List, Optional, Set +from typing import Any, Dict, Iterable, List, Optional, Set, Union, cast from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError -from homeassistant.loader import Integration, async_get_integration +from homeassistant.loader import ( + Integration, + IntegrationNotFound, + _async_mount_config_dir, + async_get_integration, +) import homeassistant.util.package as pkg_util DATA_PIP_LOCK = "pip_lock" DATA_PKG_CACHE = "pkg_cache" +DATA_INTEGRATIONS_WITH_REQS = "integrations_with_reqs" CONSTRAINT_FILE = "package_constraints.txt" PROGRESS_FILE = ".pip_progress" _LOGGER = logging.getLogger(__name__) @@ -19,6 +25,7 @@ DISCOVERY_INTEGRATIONS: Dict[str, Iterable[str]] = { "ssdp": ("ssdp",), "zeroconf": ("zeroconf", "homekit"), } +_UNDEF = object() class RequirementsNotFound(HomeAssistantError): @@ -50,6 +57,27 @@ async def async_get_integration_with_requirements( if hass.config.skip_pip: return integration + cache = hass.data.get(DATA_INTEGRATIONS_WITH_REQS) + if cache is None: + cache = hass.data[DATA_INTEGRATIONS_WITH_REQS] = {} + + int_or_evt: Union[Integration, asyncio.Event, None] = cache.get(domain, _UNDEF) + + if isinstance(int_or_evt, asyncio.Event): + await int_or_evt.wait() + int_or_evt = cache.get(domain, _UNDEF) + + # When we have waited and it's _UNDEF, it doesn't exist + # We don't cache that it doesn't exist, or else people can't fix it + # and then restart, because their config will never be valid. + if int_or_evt is _UNDEF: + raise IntegrationNotFound(domain) + + if int_or_evt is not _UNDEF: + return cast(Integration, int_or_evt) + + event = cache[domain] = asyncio.Event() + if integration.requirements: await async_process_requirements( hass, integration.domain, integration.requirements @@ -77,6 +105,8 @@ async def async_get_integration_with_requirements( ] ) + cache[domain] = integration + event.set() return integration