Keep track of top level components (#115586)

* Keep track of top level components

Currently we have to do a set comp for icons, translations,
and integration platforms every time to split the top level
components from the platforms. Keep track of the top level
components in a seperate set so avoid having to do the setcomp
every time.

* remove impossible paths

* remove unused code

* preen

* preen

* fix

* coverage and fixes

* Update homeassistant/core.py

* Update homeassistant/core.py

* Update tests/test_core.py
This commit is contained in:
J. Nick Koston 2024-04-17 06:23:20 -05:00 committed by GitHub
parent fee1f2833d
commit cb16465539
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 83 additions and 45 deletions

View file

@ -6,6 +6,7 @@ import asyncio
from collections.abc import Iterable
from functools import lru_cache
import logging
import pathlib
from typing import Any
from homeassistant.core import HomeAssistant, callback
@ -20,23 +21,17 @@ _LOGGER = logging.getLogger(__name__)
@callback
def _component_icons_path(component: str, integration: Integration) -> str | None:
def _component_icons_path(integration: Integration) -> pathlib.Path:
"""Return the icons json file location for a component.
Ex: components/hue/icons.json
If component is just a single file, will return None.
"""
domain = component.rpartition(".")[-1]
# If it's a component that is just one file, we don't support icons
# Example custom_components/my_component.py
if integration.file_path.name != domain:
return None
return str(integration.file_path / "icons.json")
return integration.file_path / "icons.json"
def _load_icons_files(icons_files: dict[str, str]) -> dict[str, dict[str, Any]]:
def _load_icons_files(
icons_files: dict[str, pathlib.Path],
) -> dict[str, dict[str, Any]]:
"""Load and parse icons.json files."""
return {
component: load_json_object(icons_file)
@ -53,19 +48,15 @@ async def _async_get_component_icons(
icons: dict[str, Any] = {}
# Determine files to load
files_to_load = {}
for loaded in components:
domain = loaded.rpartition(".")[-1]
if (path := _component_icons_path(loaded, integrations[domain])) is None:
icons[loaded] = {}
else:
files_to_load[loaded] = path
files_to_load = {
comp: _component_icons_path(integrations[comp]) for comp in components
}
# Load files
if files_to_load and (
load_icons_job := hass.async_add_executor_job(_load_icons_files, files_to_load)
):
icons |= await load_icons_job
if files_to_load:
icons.update(
await hass.async_add_executor_job(_load_icons_files, files_to_load)
)
return icons
@ -108,8 +99,7 @@ class _IconsCache:
_LOGGER.debug("Cache miss for: %s", components)
integrations: dict[str, Integration] = {}
domains = {loaded.rpartition(".")[-1] for loaded in components}
ints_or_excs = await async_get_integrations(self._hass, domains)
ints_or_excs = await async_get_integrations(self._hass, components)
for domain, int_or_exc in ints_or_excs.items():
if isinstance(int_or_exc, Exception):
raise int_or_exc
@ -127,11 +117,9 @@ class _IconsCache:
icons: dict[str, dict[str, Any]],
) -> None:
"""Extract resources into the cache."""
categories: set[str] = set()
for resource in icons.values():
categories.update(resource)
categories = {
category for component in icons.values() for category in component
}
for category in categories:
self._cache.setdefault(category, {}).update(
build_resources(icons, components, category)
@ -151,9 +139,7 @@ async def async_get_icons(
if integrations:
components = set(integrations)
else:
components = {
component for component in hass.config.components if "." not in component
}
components = hass.config.top_level_components
if ICON_CACHE in hass.data:
cache: _IconsCache = hass.data[ICON_CACHE]