Load integrations only once (#23132)

This commit is contained in:
Paulus Schoutsen 2019-04-15 20:38:24 -07:00 committed by GitHub
parent f48eb913b3
commit 8b86bf7dd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 30 additions and 12 deletions

View File

@ -4,6 +4,7 @@ The methods for loading Home Assistant integrations.
This module has quite some complex parts. I have tried to add as much This module has quite some complex parts. I have tried to add as much
documentation as possible to keep it understandable. documentation as possible to keep it understandable.
""" """
import asyncio
import functools as ft import functools as ft
import importlib import importlib
import json import json
@ -19,7 +20,7 @@ from typing import (
Any, Any,
TypeVar, TypeVar,
List, List,
Dict Dict,
) )
# Typing imports that create a circular dependency # Typing imports that create a circular dependency
@ -116,6 +117,7 @@ class Integration:
self.domain = manifest['domain'] # type: str self.domain = manifest['domain'] # type: str
self.dependencies = manifest['dependencies'] # type: List[str] self.dependencies = manifest['dependencies'] # type: List[str]
self.requirements = manifest['requirements'] # type: List[str] self.requirements = manifest['requirements'] # type: List[str]
_LOGGER.info("Loaded %s from %s", self.domain, pkg_path)
def get_component(self) -> ModuleType: def get_component(self) -> ModuleType:
"""Return the component.""" """Return the component."""
@ -148,14 +150,20 @@ async def async_get_integration(hass: 'HomeAssistant', domain: str)\
raise IntegrationNotFound(domain) raise IntegrationNotFound(domain)
cache = hass.data[DATA_INTEGRATIONS] = {} cache = hass.data[DATA_INTEGRATIONS] = {}
integration = cache.get(domain, _UNDEF) # type: Optional[Integration] int_or_evt = cache.get(domain, _UNDEF) # type: Optional[Integration]
if integration is _UNDEF: if isinstance(int_or_evt, asyncio.Event):
await int_or_evt.wait()
int_or_evt = cache.get(domain, _UNDEF)
if int_or_evt is _UNDEF:
pass pass
elif integration is None: elif int_or_evt is None:
raise IntegrationNotFound(domain) raise IntegrationNotFound(domain)
else: else:
return integration return int_or_evt
event = cache[domain] = asyncio.Event()
try: try:
import custom_components import custom_components
@ -165,6 +173,7 @@ async def async_get_integration(hass: 'HomeAssistant', domain: str)\
if integration is not None: if integration is not None:
_LOGGER.warning(CUSTOM_WARNING, domain) _LOGGER.warning(CUSTOM_WARNING, domain)
cache[domain] = integration cache[domain] = integration
event.set()
return integration return integration
except ImportError: except ImportError:
@ -179,12 +188,12 @@ async def async_get_integration(hass: 'HomeAssistant', domain: str)\
if integration is not None: if integration is not None:
cache[domain] = integration cache[domain] = integration
event.set()
return integration return integration
integration = await hass.async_add_executor_job( integration = Integration.resolve_legacy(hass, domain)
Integration.resolve_legacy, hass, domain
)
cache[domain] = integration cache[domain] = integration
event.set()
if not integration: if not integration:
raise IntegrationNotFound(domain) raise IntegrationNotFound(domain)
@ -253,8 +262,6 @@ def _load_file(hass, # type: HomeAssistant
if getattr(module, '__file__', None) is None: if getattr(module, '__file__', None) is None:
continue continue
_LOGGER.info("Loaded %s from %s", comp_or_platform, path)
cache[comp_or_platform] = module cache[comp_or_platform] = module
if module.__name__.startswith(PACKAGE_CUSTOM_COMPONENTS): if module.__name__.startswith(PACKAGE_CUSTOM_COMPONENTS):
@ -318,8 +325,9 @@ class Components:
# Test integration cache # Test integration cache
integration = self._hass.data.get(DATA_INTEGRATIONS, {}).get(comp_name) integration = self._hass.data.get(DATA_INTEGRATIONS, {}).get(comp_name)
if integration: if isinstance(integration, Integration):
component = integration.get_component() component = integration.get_component(
) # type: Optional[ModuleType]
else: else:
# Fallback to importing old-school # Fallback to importing old-school
component = _load_file(self._hass, comp_name, LOOKUP_PATHS) component = _load_file(self._hass, comp_name, LOOKUP_PATHS)

View File

@ -152,3 +152,13 @@ def test_integration_properties(hass):
assert integration.domain == 'hue' assert integration.domain == 'hue'
assert integration.dependencies == ['test-dep'] assert integration.dependencies == ['test-dep']
assert integration.requirements == ['test-req==1.0.0'] assert integration.requirements == ['test-req==1.0.0']
async def test_integrations_only_once(hass):
"""Test that we load integrations only once."""
int_1 = hass.async_create_task(
loader.async_get_integration(hass, 'hue'))
int_2 = hass.async_create_task(
loader.async_get_integration(hass, 'hue'))
assert await int_1 is await int_2