Skip to content

Commit 1d7efbf

Browse files
committed
Use aiohasupervisor for all calls from hassio/coordinator
1 parent 492b542 commit 1d7efbf

22 files changed

Lines changed: 838 additions & 1514 deletions

homeassistant/components/hassio/__init__.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -504,27 +504,33 @@ async def update_info_data(_: datetime | None = None) -> None:
504504

505505
try:
506506
(
507-
hass.data[DATA_INFO],
508-
hass.data[DATA_HOST_INFO],
507+
root_info,
508+
host_info,
509509
store_info,
510-
hass.data[DATA_CORE_INFO],
511-
hass.data[DATA_SUPERVISOR_INFO],
512-
hass.data[DATA_OS_INFO],
513-
hass.data[DATA_NETWORK_INFO],
510+
homeassistant_info,
511+
supervisor_info,
512+
os_info,
513+
network_info,
514514
) = await asyncio.gather(
515-
create_eager_task(hassio.get_info()),
516-
create_eager_task(hassio.get_host_info()),
515+
create_eager_task(supervisor_client.info()),
516+
create_eager_task(supervisor_client.host.info()),
517517
create_eager_task(supervisor_client.store.info()),
518-
create_eager_task(hassio.get_core_info()),
519-
create_eager_task(hassio.get_supervisor_info()),
520-
create_eager_task(hassio.get_os_info()),
521-
create_eager_task(hassio.get_network_info()),
518+
create_eager_task(supervisor_client.homeassistant.info()),
519+
create_eager_task(supervisor_client.supervisor.info()),
520+
create_eager_task(supervisor_client.os.info()),
521+
create_eager_task(supervisor_client.network.info()),
522522
)
523523

524-
except HassioAPIError as err:
524+
except SupervisorError as err:
525525
_LOGGER.warning("Can't read Supervisor data: %s", err)
526526
else:
527+
hass.data[DATA_INFO] = root_info.to_dict()
528+
hass.data[DATA_HOST_INFO] = host_info.to_dict()
527529
hass.data[DATA_STORE] = store_info.to_dict()
530+
hass.data[DATA_CORE_INFO] = homeassistant_info.to_dict()
531+
hass.data[DATA_SUPERVISOR_INFO] = supervisor_info.to_dict()
532+
hass.data[DATA_OS_INFO] = os_info.to_dict()
533+
hass.data[DATA_NETWORK_INFO] = network_info.to_dict()
528534

529535
async_call_later(
530536
hass,

homeassistant/components/hassio/coordinator.py

Lines changed: 79 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@
66
from collections import defaultdict
77
from copy import deepcopy
88
import logging
9-
from typing import TYPE_CHECKING, Any
9+
from typing import TYPE_CHECKING, Any, cast
1010

1111
from aiohasupervisor import SupervisorError, SupervisorNotFoundError
12-
from aiohasupervisor.models import StoreInfo
13-
from aiohasupervisor.models.mounts import CIFSMountResponse, NFSMountResponse
12+
from aiohasupervisor.models import (
13+
AddonState,
14+
CIFSMountResponse,
15+
NFSMountResponse,
16+
StoreInfo,
17+
)
18+
from aiohasupervisor.models.base import ResponseData
1419

1520
from homeassistant.config_entries import ConfigEntry
1621
from homeassistant.const import ATTR_MANUFACTURER, ATTR_NAME
@@ -22,11 +27,8 @@
2227
from homeassistant.loader import bind_hass
2328

2429
from .const import (
25-
ATTR_AUTO_UPDATE,
2630
ATTR_REPOSITORY,
2731
ATTR_SLUG,
28-
ATTR_STARTED,
29-
ATTR_STATE,
3032
ATTR_URL,
3133
ATTR_VERSION,
3234
CONTAINER_INFO,
@@ -57,7 +59,7 @@
5759
SUPERVISOR_CONTAINER,
5860
SupervisorEntityModel,
5961
)
60-
from .handler import HassioAPIError, get_supervisor_client
62+
from .handler import get_supervisor_client
6163
from .jobs import SupervisorJobs
6264

6365
if TYPE_CHECKING:
@@ -341,7 +343,7 @@ async def _async_update_data(self) -> dict[str, Any]:
341343

342344
try:
343345
await self.force_data_refresh(is_first_update)
344-
except HassioAPIError as err:
346+
except SupervisorError as err:
345347
raise UpdateFailed(f"Error on Supervisor API: {err}") from err
346348

347349
new_data: dict[str, Any] = {}
@@ -360,17 +362,14 @@ async def _async_update_data(self) -> dict[str, Any]:
360362
repositories = {}
361363

362364
new_data[DATA_KEY_ADDONS] = {
363-
addon[ATTR_SLUG]: {
365+
slug: {
364366
**addon,
365-
**((addons_stats or {}).get(addon[ATTR_SLUG]) or {}),
366-
ATTR_AUTO_UPDATE: (addons_info.get(addon[ATTR_SLUG]) or {}).get(
367-
ATTR_AUTO_UPDATE, False
368-
),
367+
**addons_stats.get(slug, {}),
369368
ATTR_REPOSITORY: repositories.get(
370-
addon.get(ATTR_REPOSITORY), addon.get(ATTR_REPOSITORY, "")
369+
repo_slug := addon.get(ATTR_REPOSITORY, ""), repo_slug
371370
),
372371
}
373-
for addon in supervisor_info.get("addons", [])
372+
for slug, addon in addons_info.items()
374373
}
375374
if self.is_hass_os:
376375
new_data[DATA_KEY_OS] = get_os_info(self.hass)
@@ -462,32 +461,37 @@ async def force_data_refresh(self, first_update: bool) -> None:
462461
container_updates = self._container_updates
463462

464463
data = self.hass.data
465-
hassio = self.hassio
464+
client = self.supervisor_client
465+
466466
updates = {
467-
DATA_INFO: hassio.get_info(),
468-
DATA_CORE_INFO: hassio.get_core_info(),
469-
DATA_SUPERVISOR_INFO: hassio.get_supervisor_info(),
470-
DATA_OS_INFO: hassio.get_os_info(),
467+
DATA_INFO: client.info(),
468+
DATA_CORE_INFO: client.homeassistant.info(),
469+
DATA_SUPERVISOR_INFO: client.supervisor.info(),
470+
DATA_OS_INFO: client.os.info(),
471471
}
472472
if CONTAINER_STATS in container_updates[CORE_CONTAINER]:
473-
updates[DATA_CORE_STATS] = hassio.get_core_stats()
473+
updates[DATA_CORE_STATS] = client.homeassistant.stats()
474474
if CONTAINER_STATS in container_updates[SUPERVISOR_CONTAINER]:
475-
updates[DATA_SUPERVISOR_STATS] = hassio.get_supervisor_stats()
476-
477-
results = await asyncio.gather(*updates.values())
478-
for key, result in zip(updates, results, strict=False):
479-
data[key] = result
480-
481-
_addon_data = data[DATA_SUPERVISOR_INFO].get("addons", [])
482-
all_addons: list[str] = []
483-
started_addons: list[str] = []
484-
for addon in _addon_data:
485-
slug = addon[ATTR_SLUG]
486-
all_addons.append(slug)
487-
if addon[ATTR_STATE] == ATTR_STARTED:
488-
started_addons.append(slug)
475+
updates[DATA_SUPERVISOR_STATS] = client.supervisor.stats()
476+
477+
results = await asyncio.gather(client.addons.list(), *updates.values())
478+
# Pull off addons.list results for further processing before caching
479+
apps_list = results.pop(0)
480+
# Zip the rest and cache in hass.data
481+
for key, result in zip(
482+
updates, cast(list[ResponseData], results), strict=False
483+
):
484+
data[key] = result.to_dict()
485+
486+
all_apps = {app.slug: app for app in apps_list}
487+
started_apps = {
488+
app.slug
489+
for app in apps_list
490+
if app.state in {AddonState.STARTED, AddonState.STARTUP}
491+
}
492+
489493
#
490-
# Update add-on info if its the first update or
494+
# Update app info if its the first update or
491495
# there is at least one entity that needs the data.
492496
#
493497
# When entities are added they call async_enable_container_updates
@@ -497,50 +501,57 @@ async def force_data_refresh(self, first_update: bool) -> None:
497501
# API calls since otherwise we would fetch stats for all containers
498502
# and throw them away.
499503
#
500-
for data_key, update_func, enabled_key, wanted_addons, needs_first_update in (
501-
(
502-
DATA_ADDONS_STATS,
503-
self._update_addon_stats,
504-
CONTAINER_STATS,
505-
started_addons,
506-
False,
507-
),
508-
(
509-
DATA_ADDONS_INFO,
510-
self._update_addon_info,
511-
CONTAINER_INFO,
512-
all_addons,
513-
True,
514-
),
515-
):
516-
container_data: dict[str, Any] = data.setdefault(data_key, {})
517-
container_data.update(
518-
dict(
519-
await asyncio.gather(
520-
*[
521-
update_func(slug)
522-
for slug in wanted_addons
523-
if (first_update and needs_first_update)
524-
or enabled_key in container_updates[slug]
525-
]
526-
)
504+
stats_data: dict[str, Any] = data.setdefault(DATA_ADDONS_STATS, {})
505+
info_data: dict[str, Any] = data.setdefault(DATA_ADDONS_INFO, {})
506+
507+
# Clean up caches
508+
for slug in stats_data.keys() - started_apps:
509+
del stats_data[slug]
510+
for slug in info_data.keys() - all_apps:
511+
del info_data[slug]
512+
513+
# Update stats cache from API
514+
stats_data.update(
515+
dict(
516+
await asyncio.gather(
517+
*[
518+
self._update_app_stats(slug)
519+
for slug in started_apps
520+
if CONTAINER_STATS in container_updates[slug]
521+
]
527522
)
528523
)
524+
)
525+
526+
# Update info cache from API. On failure keep old data if we have any.
527+
# If we have none, use the data that came back from addons.list API instead
528+
all_app_info = await asyncio.gather(
529+
*[
530+
self._update_app_info(slug)
531+
for slug in all_apps
532+
if first_update or CONTAINER_INFO in container_updates[slug]
533+
]
534+
)
535+
for slug, app_info in all_app_info:
536+
if app_info:
537+
info_data[slug] = app_info
538+
elif slug not in info_data:
539+
info_data[slug] = all_apps[slug].to_dict()
529540

530541
# Refresh jobs data
531542
await self.jobs.refresh_data(first_update)
532543

533-
async def _update_addon_stats(self, slug: str) -> tuple[str, dict[str, Any] | None]:
534-
"""Update single addon stats."""
544+
async def _update_app_stats(self, slug: str) -> tuple[str, dict[str, Any] | None]:
545+
"""Update single app stats."""
535546
try:
536547
stats = await self.supervisor_client.addons.addon_stats(slug)
537548
except SupervisorError as err:
538549
_LOGGER.warning("Could not fetch stats for %s: %s", slug, err)
539550
return (slug, None)
540551
return (slug, stats.to_dict())
541552

542-
async def _update_addon_info(self, slug: str) -> tuple[str, dict[str, Any] | None]:
543-
"""Return the info for an add-on."""
553+
async def _update_app_info(self, slug: str) -> tuple[str, dict[str, Any] | None]:
554+
"""Return the info for an app."""
544555
try:
545556
info = await self.supervisor_client.addons.addon_info(slug)
546557
except SupervisorError as err:
@@ -595,7 +606,7 @@ async def _async_refresh(
595606
async def force_addon_info_data_refresh(self, addon_slug: str) -> None:
596607
"""Force refresh of addon info data for a specific addon."""
597608
try:
598-
slug, info = await self._update_addon_info(addon_slug)
609+
slug, info = await self._update_app_info(addon_slug)
599610
if info is not None and DATA_KEY_ADDONS in self.data:
600611
if slug in self.data[DATA_KEY_ADDONS]:
601612
data = deepcopy(self.data)

homeassistant/components/hassio/handler.py

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -87,70 +87,6 @@ def base_url(self) -> URL:
8787
"""Return base url for Supervisor."""
8888
return self._base_url
8989

90-
@api_data
91-
def get_info(self) -> Coroutine:
92-
"""Return generic Supervisor information.
93-
94-
This method returns a coroutine.
95-
"""
96-
return self.send_command("/info", method="get")
97-
98-
@api_data
99-
def get_host_info(self) -> Coroutine:
100-
"""Return data for Host.
101-
102-
This method returns a coroutine.
103-
"""
104-
return self.send_command("/host/info", method="get")
105-
106-
@api_data
107-
def get_os_info(self) -> Coroutine:
108-
"""Return data for the OS.
109-
110-
This method returns a coroutine.
111-
"""
112-
return self.send_command("/os/info", method="get")
113-
114-
@api_data
115-
def get_core_info(self) -> Coroutine:
116-
"""Return data for Home Asssistant Core.
117-
118-
This method returns a coroutine.
119-
"""
120-
return self.send_command("/core/info", method="get")
121-
122-
@api_data
123-
def get_supervisor_info(self) -> Coroutine:
124-
"""Return data for the Supervisor.
125-
126-
This method returns a coroutine.
127-
"""
128-
return self.send_command("/supervisor/info", method="get")
129-
130-
@api_data
131-
def get_network_info(self) -> Coroutine:
132-
"""Return data for the Host Network.
133-
134-
This method returns a coroutine.
135-
"""
136-
return self.send_command("/network/info", method="get")
137-
138-
@api_data
139-
def get_core_stats(self) -> Coroutine:
140-
"""Return stats for the core.
141-
142-
This method returns a coroutine.
143-
"""
144-
return self.send_command("/core/stats", method="get")
145-
146-
@api_data
147-
def get_supervisor_stats(self) -> Coroutine:
148-
"""Return stats for the supervisor.
149-
150-
This method returns a coroutine.
151-
"""
152-
return self.send_command("/supervisor/stats", method="get")
153-
15490
@api_data
15591
def get_ingress_panels(self) -> Coroutine:
15692
"""Return data for Add-on ingress panels.

homeassistant/components/hassio/issues.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -275,13 +275,9 @@ def add_issue(self, issue: Issue) -> None:
275275

276276
elif issue.key == ISSUE_KEY_SYSTEM_FREE_SPACE:
277277
host_info = get_host_info(self._hass)
278-
if (
279-
host_info
280-
and "data" in host_info
281-
and "disk_free" in host_info["data"]
282-
):
278+
if host_info and "disk_free" in host_info:
283279
placeholders[PLACEHOLDER_KEY_FREE_SPACE] = str(
284-
host_info["data"]["disk_free"]
280+
host_info["disk_free"]
285281
)
286282
else:
287283
placeholders[PLACEHOLDER_KEY_FREE_SPACE] = "<2"

homeassistant/components/hassio/system_health.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from homeassistant.core import HomeAssistant, callback
1010

1111
from .coordinator import (
12+
get_addons_info,
1213
get_host_info,
1314
get_info,
1415
get_network_info,
@@ -35,6 +36,7 @@ async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
3536
host_info = get_host_info(hass) or {}
3637
supervisor_info = get_supervisor_info(hass)
3738
network_info = get_network_info(hass) or {}
39+
addons_info = get_addons_info(hass) or {}
3840

3941
healthy: bool | dict[str, str]
4042
if supervisor_info is not None and supervisor_info.get("healthy"):
@@ -84,6 +86,8 @@ async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
8486
os_info = get_os_info(hass) or {}
8587
information["board"] = os_info.get("board")
8688

89+
# Not using aiohasupervisor for ping call below intentionally. Given system health
90+
# context seems preferable this check be done with minimal dependencies
8791
information["supervisor_api"] = system_health.async_check_can_reach_url(
8892
hass,
8993
SUPERVISOR_PING.format(ip_address=ip_address),
@@ -95,8 +99,7 @@ async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
9599
)
96100

97101
information["installed_addons"] = ", ".join(
98-
f"{addon['name']} ({addon['version']})"
99-
for addon in (supervisor_info or {}).get("addons", [])
102+
f"{addon['name']} ({addon['version']})" for addon in addons_info.values()
100103
)
101104

102105
return information

0 commit comments

Comments
 (0)