diff --git a/homeassistant/core.py b/homeassistant/core.py index 47a8119de71..3648fca99f7 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -32,7 +32,7 @@ from urllib.parse import urlparse import voluptuous as vol import yarl -from . import block_async_io, loader, util +from . import block_async_io, util from .const import ( ATTR_DOMAIN, ATTR_FRIENDLY_NAME, @@ -310,6 +310,9 @@ class HomeAssistant: def __init__(self, config_dir: str) -> None: """Initialize new Home Assistant object.""" + # pylint: disable-next=import-outside-toplevel + from . import loader + self.loop = asyncio.get_running_loop() self._tasks: set[asyncio.Future[Any]] = set() self._background_tasks: set[asyncio.Future[Any]] = set() diff --git a/homeassistant/loader.py b/homeassistant/loader.py index 40161bd3be9..8906cefb241 100644 --- a/homeassistant/loader.py +++ b/homeassistant/loader.py @@ -25,6 +25,7 @@ from awesomeversion import ( import voluptuous as vol from . import generated +from .core import HomeAssistant, callback from .generated.application_credentials import APPLICATION_CREDENTIALS from .generated.bluetooth import BLUETOOTH from .generated.dhcp import DHCP @@ -37,7 +38,6 @@ from .util.json import JSON_DECODE_EXCEPTIONS, json_loads # Typing imports that create a circular dependency if TYPE_CHECKING: from .config_entries import ConfigEntry - from .core import HomeAssistant from .helpers import device_registry as dr from .helpers.typing import ConfigType @@ -875,6 +875,22 @@ def _resolve_integrations_from_root( return integrations +@callback +def async_get_loaded_integration(hass: HomeAssistant, domain: str) -> Integration: + """Get an integration which is already loaded. + + Raises IntegrationNotLoaded if the integration is not loaded. + """ + cache = hass.data[DATA_INTEGRATIONS] + if TYPE_CHECKING: + cache = cast(dict[str, Integration | asyncio.Future[None]], cache) + int_or_fut = cache.get(domain, _UNDEF) + # Integration is never subclassed, so we can check for type + if type(int_or_fut) is Integration: # noqa: E721 + return int_or_fut + raise IntegrationNotLoaded(domain) + + async def async_get_integration(hass: HomeAssistant, domain: str) -> Integration: """Get integration.""" integrations_or_excs = await async_get_integrations(hass, [domain]) @@ -970,6 +986,15 @@ class IntegrationNotFound(LoaderError): self.domain = domain +class IntegrationNotLoaded(LoaderError): + """Raised when a component is not loaded.""" + + def __init__(self, domain: str) -> None: + """Initialize a component not found error.""" + super().__init__(f"Integration '{domain}' not loaded.") + self.domain = domain + + class CircularDependency(LoaderError): """Raised when a circular dependency is found when resolving components.""" diff --git a/tests/test_loader.py b/tests/test_loader.py index 6e62be08f66..b62e25b79e3 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -150,10 +150,17 @@ async def test_custom_integration_version_not_valid( async def test_get_integration(hass: HomeAssistant) -> None: """Test resolving integration.""" + with pytest.raises(loader.IntegrationNotLoaded): + loader.async_get_loaded_integration(hass, "hue") + integration = await loader.async_get_integration(hass, "hue") assert hue == integration.get_component() assert hue_light == integration.get_platform("light") + integration = loader.async_get_loaded_integration(hass, "hue") + assert hue == integration.get_component() + assert hue_light == integration.get_platform("light") + async def test_get_integration_exceptions(hass: HomeAssistant) -> None: """Test resolving integration."""