66from collections import defaultdict
77from copy import deepcopy
88import logging
9- from typing import TYPE_CHECKING , Any
9+ from typing import TYPE_CHECKING , Any , cast
1010
1111from 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
1520from homeassistant .config_entries import ConfigEntry
1621from homeassistant .const import ATTR_MANUFACTURER , ATTR_NAME
2227from homeassistant .loader import bind_hass
2328
2429from .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 ,
5759 SUPERVISOR_CONTAINER ,
5860 SupervisorEntityModel ,
5961)
60- from .handler import HassioAPIError , get_supervisor_client
62+ from .handler import get_supervisor_client
6163from .jobs import SupervisorJobs
6264
6365if 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 )
0 commit comments