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 .const import (
BASE_PLATFORMS,
FORMAT_DATETIME,
KEY_DATA_LOGGING as DATA_LOGGING,
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.typing import ConfigType
from .setup import (
BASE_PLATFORMS,
# _setup_started is marked as protected to make it clear
# that it is not part of the public API and should not be used
# by integrations. It is only used for internal tracking of

View file

@ -83,6 +83,9 @@ class Platform(StrEnum):
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.
MATCH_ALL: Final = "*"

View file

@ -55,6 +55,7 @@ from .const import (
ATTR_FRIENDLY_NAME,
ATTR_SERVICE,
ATTR_SERVICE_DATA,
BASE_PLATFORMS,
COMPRESSED_STATE_ATTRIBUTES,
COMPRESSED_STATE_CONTEXT,
COMPRESSED_STATE_LAST_CHANGED,
@ -2769,16 +2770,27 @@ class _ComponentSet(set[str]):
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."""
self._top_level_components = top_level_components
self._all_components = all_components
def add(self, component: str) -> None:
"""Add a component to the store."""
if "." not in 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)
def remove(self, component: str) -> None:
@ -2831,8 +2843,14 @@ class Config:
# and should not be modified directly
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
self.components: _ComponentSet = _ComponentSet(self.top_level_components)
self.components: _ComponentSet = _ComponentSet(
self.top_level_components, self.all_components
)
# API (HTTP) server configuration
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 .const import (
BASE_PLATFORMS, # noqa: F401
EVENT_COMPONENT_LOADED,
EVENT_HOMEASSISTANT_START,
PLATFORM_FORMAT,
Platform,
)
from .core import (
CALLBACK_TYPE,
@ -44,7 +44,6 @@ _LOGGER = logging.getLogger(__name__)
ATTR_COMPONENT: Final = "component"
BASE_PLATFORMS = {platform.value for platform in Platform}
# DATA_SETUP is a dict, indicating domains which are currently
# being setup or which failed to setup:
@ -637,15 +636,7 @@ def _async_when_setup(
@core.callback
def async_get_loaded_integrations(hass: core.HomeAssistant) -> set[str]:
"""Return the complete list of loaded integrations."""
integrations = set()
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
return hass.config.all_components
class SetupPhases(StrEnum):

View file

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