Speed up async_get_loaded_integrations (#117851)

* Speed up async_get_loaded_integrations

Use a setcomp and difference to find the components to split
to avoid the loop. A setcomp is inlined in python3.12 so its
much faster

* Speed up async_get_loaded_integrations

Use a setcomp and difference to find the components to split
to avoid the loop. A setcomp is inlined in python3.12 so its
much faster

* simplify

* fix compat

* bootstrap

* fix tests
This commit is contained in:
J. Nick Koston 2024-05-21 03:08:49 -10:00 committed by GitHub
parent 266ce9e268
commit e12d23bd48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 29 additions and 17 deletions

View File

@ -63,6 +63,7 @@ from .components import (
) )
from .components.sensor import recorder as sensor_recorder # noqa: F401 from .components.sensor import recorder as sensor_recorder # noqa: F401
from .const import ( from .const import (
BASE_PLATFORMS,
FORMAT_DATETIME, FORMAT_DATETIME,
KEY_DATA_LOGGING as DATA_LOGGING, KEY_DATA_LOGGING as DATA_LOGGING,
REQUIRED_NEXT_PYTHON_HA_RELEASE, REQUIRED_NEXT_PYTHON_HA_RELEASE,
@ -90,7 +91,6 @@ from .helpers.storage import get_internal_store_manager
from .helpers.system_info import async_get_system_info from .helpers.system_info import async_get_system_info
from .helpers.typing import ConfigType from .helpers.typing import ConfigType
from .setup import ( from .setup import (
BASE_PLATFORMS,
# _setup_started is marked as protected to make it clear # _setup_started is marked as protected to make it clear
# that it is not part of the public API and should not be used # that it is not part of the public API and should not be used
# by integrations. It is only used for internal tracking of # by integrations. It is only used for internal tracking of

View File

@ -83,6 +83,9 @@ class Platform(StrEnum):
WEATHER = "weather" WEATHER = "weather"
BASE_PLATFORMS: Final = {platform.value for platform in Platform}
# Can be used to specify a catch all when registering state or event listeners. # Can be used to specify a catch all when registering state or event listeners.
MATCH_ALL: Final = "*" MATCH_ALL: Final = "*"

View File

@ -55,6 +55,7 @@ from .const import (
ATTR_FRIENDLY_NAME, ATTR_FRIENDLY_NAME,
ATTR_SERVICE, ATTR_SERVICE,
ATTR_SERVICE_DATA, ATTR_SERVICE_DATA,
BASE_PLATFORMS,
COMPRESSED_STATE_ATTRIBUTES, COMPRESSED_STATE_ATTRIBUTES,
COMPRESSED_STATE_CONTEXT, COMPRESSED_STATE_CONTEXT,
COMPRESSED_STATE_LAST_CHANGED, COMPRESSED_STATE_LAST_CHANGED,
@ -2769,16 +2770,27 @@ class _ComponentSet(set[str]):
The top level components set only contains the top level components. The top level components set only contains the top level components.
The all components set contains all components, including platform
based components.
""" """
def __init__(self, top_level_components: set[str]) -> None: def __init__(
self, top_level_components: set[str], all_components: set[str]
) -> None:
"""Initialize the component set.""" """Initialize the component set."""
self._top_level_components = top_level_components self._top_level_components = top_level_components
self._all_components = all_components
def add(self, component: str) -> None: def add(self, component: str) -> None:
"""Add a component to the store.""" """Add a component to the store."""
if "." not in component: if "." not in component:
self._top_level_components.add(component) self._top_level_components.add(component)
self._all_components.add(component)
else:
platform, _, domain = component.partition(".")
if domain in BASE_PLATFORMS:
self._all_components.add(platform)
return super().add(component) return super().add(component)
def remove(self, component: str) -> None: def remove(self, component: str) -> None:
@ -2831,8 +2843,14 @@ class Config:
# and should not be modified directly # and should not be modified directly
self.top_level_components: set[str] = set() self.top_level_components: set[str] = set()
# Set of all loaded components including platform
# based components
self.all_components: set[str] = set()
# Set of loaded components # Set of loaded components
self.components: _ComponentSet = _ComponentSet(self.top_level_components) self.components: _ComponentSet = _ComponentSet(
self.top_level_components, self.all_components
)
# API (HTTP) server configuration # API (HTTP) server configuration
self.api: ApiConfig | None = None self.api: ApiConfig | None = None

View File

@ -16,10 +16,10 @@ from typing import Any, Final, TypedDict
from . import config as conf_util, core, loader, requirements from . import config as conf_util, core, loader, requirements
from .const import ( from .const import (
BASE_PLATFORMS, # noqa: F401
EVENT_COMPONENT_LOADED, EVENT_COMPONENT_LOADED,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_START,
PLATFORM_FORMAT, PLATFORM_FORMAT,
Platform,
) )
from .core import ( from .core import (
CALLBACK_TYPE, CALLBACK_TYPE,
@ -44,7 +44,6 @@ _LOGGER = logging.getLogger(__name__)
ATTR_COMPONENT: Final = "component" ATTR_COMPONENT: Final = "component"
BASE_PLATFORMS = {platform.value for platform in Platform}
# DATA_SETUP is a dict, indicating domains which are currently # DATA_SETUP is a dict, indicating domains which are currently
# being setup or which failed to setup: # being setup or which failed to setup:
@ -637,15 +636,7 @@ def _async_when_setup(
@core.callback @core.callback
def async_get_loaded_integrations(hass: core.HomeAssistant) -> set[str]: def async_get_loaded_integrations(hass: core.HomeAssistant) -> set[str]:
"""Return the complete list of loaded integrations.""" """Return the complete list of loaded integrations."""
integrations = set() return hass.config.all_components
for component in hass.config.components:
if "." not in component:
integrations.add(component)
continue
platform, _, domain = component.partition(".")
if domain in BASE_PLATFORMS:
integrations.add(platform)
return integrations
class SetupPhases(StrEnum): class SetupPhases(StrEnum):

View File

@ -246,7 +246,7 @@ async def test_send_usage(
assert analytics.preferences[ATTR_BASE] assert analytics.preferences[ATTR_BASE]
assert analytics.preferences[ATTR_USAGE] assert analytics.preferences[ATTR_USAGE]
hass.config.components = ["default_config"] hass.config.components.add("default_config")
with patch( with patch(
"homeassistant.config.load_yaml_config_file", "homeassistant.config.load_yaml_config_file",
@ -280,7 +280,7 @@ async def test_send_usage_with_supervisor(
await analytics.save_preferences({ATTR_BASE: True, ATTR_USAGE: True}) await analytics.save_preferences({ATTR_BASE: True, ATTR_USAGE: True})
assert analytics.preferences[ATTR_BASE] assert analytics.preferences[ATTR_BASE]
assert analytics.preferences[ATTR_USAGE] assert analytics.preferences[ATTR_USAGE]
hass.config.components = ["default_config"] hass.config.components.add("default_config")
with ( with (
patch( patch(
@ -344,7 +344,7 @@ async def test_send_statistics(
await analytics.save_preferences({ATTR_BASE: True, ATTR_STATISTICS: True}) await analytics.save_preferences({ATTR_BASE: True, ATTR_STATISTICS: True})
assert analytics.preferences[ATTR_BASE] assert analytics.preferences[ATTR_BASE]
assert analytics.preferences[ATTR_STATISTICS] assert analytics.preferences[ATTR_STATISTICS]
hass.config.components = ["default_config"] hass.config.components.add("default_config")
with patch( with patch(
"homeassistant.config.load_yaml_config_file", "homeassistant.config.load_yaml_config_file",