1 """Provide add-on management."""
3 from __future__
import annotations
6 from collections.abc
import Awaitable, Callable, Coroutine
7 from dataclasses
import dataclass
9 from functools
import partial, wraps
11 from typing
import Any, Concatenate
13 from aiohasupervisor
import SupervisorError
14 from aiohasupervisor.models
import (
16 AddonState
as SupervisorAddonState,
17 InstalledAddonComplete,
24 from .handler
import HassioAPIError, async_create_backup, get_supervisor_client
26 type _FuncType[_T, **_P, _R] = Callable[Concatenate[_T, _P], Awaitable[_R]]
27 type _ReturnFuncType[_T, **_P, _R] = Callable[
28 Concatenate[_T, _P], Coroutine[Any, Any, _R]
32 def api_error[_AddonManagerT: AddonManager, **_P, _R](
35 expected_error_type: type[HassioAPIError | SupervisorError] |
None =
None,
37 [_FuncType[_AddonManagerT, _P, _R]], _ReturnFuncType[_AddonManagerT, _P, _R]
39 """Handle HassioAPIError and raise a specific AddonError."""
40 error_type = expected_error_type
or (HassioAPIError, SupervisorError)
42 def handle_hassio_api_error(
43 func: _FuncType[_AddonManagerT, _P, _R],
44 ) -> _ReturnFuncType[_AddonManagerT, _P, _R]:
45 """Handle a HassioAPIError."""
49 self: _AddonManagerT, *args: _P.args, **kwargs: _P.kwargs
51 """Wrap an add-on manager method."""
53 return_value = await func(self, *args, **kwargs)
54 except error_type
as err:
56 f
"{error_message.format(addon_name=self.addon_name)}: {err}"
63 return handle_hassio_api_error
68 """Represent the current add-on info state."""
72 options: dict[str, Any]
74 update_available: bool
79 """Represent the current state of the add-on."""
81 NOT_INSTALLED =
"not_installed"
82 INSTALLING =
"installing"
84 NOT_RUNNING =
"not_running"
91 Methods may raise AddonError.
92 Only one instance of this class may exist per add-on
93 to keep track of running add-on tasks.
99 logger: logging.Logger,
103 """Set up the add-on manager."""
110 self.
_start_task_start_task: asyncio.Task |
None =
None
111 self.
_update_task_update_task: asyncio.Task |
None =
None
115 """Return True if any of the add-on tasks are in progress."""
117 task
and not task.done()
126 @api_error(
"Failed to get the {addon_name} add-on discovery info",
expected_error_type=SupervisorError,
)
128 """Return add-on discovery info."""
129 discovery_info = next(
138 if not discovery_info:
139 raise AddonError(f
"Failed to get {self.addon_name} add-on discovery info")
141 return discovery_info.config
143 @api_error(
"Failed to get the {addon_name} add-on info",
expected_error_type=SupervisorError,
)
145 """Return and cache manager add-on info."""
149 self.
_logger_logger.debug(
"Add-on store info: %s", addon_store_info.to_dict())
150 if not addon_store_info.installed:
152 available=addon_store_info.available,
155 state=AddonState.NOT_INSTALLED,
156 update_available=
False,
163 available=addon_info.available,
164 hostname=addon_info.hostname,
165 options=addon_info.options,
167 update_available=addon_info.update_available,
168 version=addon_info.version,
173 """Return the current state of the managed add-on."""
174 addon_state = AddonState.NOT_RUNNING
176 if addon_info.state == SupervisorAddonState.STARTED:
177 addon_state = AddonState.RUNNING
179 addon_state = AddonState.INSTALLING
181 addon_state = AddonState.UPDATING
185 @api_error(
"Failed to set the {addon_name} add-on options",
expected_error_type=SupervisorError,
)
187 """Set manager add-on options."""
189 self.
addon_slugaddon_slug, AddonsOptions(config=config)
193 """Check if the managed add-on is available."""
194 if not addon_info.available:
195 raise AddonError(f
"{self.addon_name} add-on is not available")
197 @api_error(
"Failed to install the {addon_name} add-on", expected_error_type=SupervisorError
)
199 """Install the managed add-on."""
206 @api_error(
"Failed to uninstall the {addon_name} add-on",
expected_error_type=SupervisorError,
)
208 """Uninstall the managed add-on."""
211 @api_error("Failed to update the {addon_name} add-on")
213 """Update the managed add-on if needed."""
218 if addon_info.state
is AddonState.NOT_INSTALLED:
219 raise AddonError(f
"{self.addon_name} add-on is not installed")
221 if not addon_info.update_available:
226 self.
addon_slugaddon_slug, StoreAddonUpdate(backup=
False)
229 @api_error(
"Failed to start the {addon_name} add-on", expected_error_type=SupervisorError
)
231 """Start the managed add-on."""
234 @api_error(
"Failed to restart the {addon_name} add-on", expected_error_type=SupervisorError
)
236 """Restart the managed add-on."""
239 @api_error(
"Failed to stop the {addon_name} add-on", expected_error_type=SupervisorError
)
241 """Stop the managed add-on."""
244 @api_error("Failed to create a backup of the {addon_name} add-on")
246 """Create a partial backup of the managed add-on."""
248 name = f
"addon_{self.addon_slug}_{addon_info.version}"
250 self.
_logger_logger.debug(
"Creating backup: %s", name)
253 {
"name": name,
"addons": [self.
addon_slugaddon_slug]},
259 addon_config: dict[str, Any],
261 """Configure the manager add-on, if needed."""
264 if addon_info.state
is AddonState.NOT_INSTALLED:
265 raise AddonError(f
"{self.addon_name} add-on is not installed")
267 if addon_config != addon_info.options:
272 """Schedule a task that installs the managed add-on.
274 Only schedule a new install task if the there's no running task.
278 "%s add-on is not installed. Installing add-on", self.
addon_nameaddon_name
288 addon_config: dict[str, Any],
289 catch_error: bool =
False,
291 """Schedule a task that installs and sets up the managed add-on.
293 Only schedule a new install task if the there's no running task.
297 "%s add-on is not installed. Installing add-on", self.
addon_nameaddon_name
306 catch_error=catch_error,
312 """Schedule a task that updates and sets up the managed add-on.
314 Only schedule a new update task if the there's no running task.
317 self.
_logger_logger.info(
"Trying to update the %s add-on", self.
addon_nameaddon_name)
320 catch_error=catch_error,
326 """Schedule a task that starts the managed add-on.
328 Only schedule a new start task if the there's no running task.
332 "%s add-on is not running. Starting add-on", self.
addon_nameaddon_name
341 """Schedule a task that restarts the managed add-on.
343 Only schedule a new restart task if the there's no running task.
355 addon_config: dict[str, Any],
356 catch_error: bool =
False,
358 """Schedule a task that configures and starts the managed add-on.
360 Only schedule a new setup task if there's no running task.
364 "%s add-on is not running. Starting add-on", self.
addon_nameaddon_name
372 catch_error=catch_error,
378 self, *funcs: Callable, catch_error: bool =
False
380 """Schedule an add-on task."""
382 async
def addon_operation() -> None:
383 """Do the add-on operation and catch AddonError."""
387 except AddonError
as err:
393 return self.
_hass_hass.async_create_task(addon_operation(), eager_start=
False)
397 """Represent an error with the managed add-on."""
398
None async_set_addon_options(self, dict config)
None async_uninstall_addon(self)
asyncio.Task async_schedule_update_addon(self, bool catch_error=False)
None async_install_addon(self)
None async_create_backup(self)
None async_restart_addon(self)
None async_configure_addon(self, dict[str, Any] addon_config)
asyncio.Task _async_schedule_addon_operation(self, *Callable funcs, bool catch_error=False)
dict async_get_addon_discovery_info(self)
None async_update_addon(self)
None __init__(self, HomeAssistant hass, logging.Logger logger, str addon_name, str addon_slug)
asyncio.Task async_schedule_setup_addon(self, dict[str, Any] addon_config, bool catch_error=False)
AddonInfo async_get_addon_info(self)
AddonState async_get_addon_state(self, InstalledAddonComplete addon_info)
None async_start_addon(self)
asyncio.Task async_schedule_install_setup_addon(self, dict[str, Any] addon_config, bool catch_error=False)
None _check_addon_available(self, AddonInfo addon_info)
asyncio.Task async_schedule_install_addon(self, bool catch_error=False)
asyncio.Task async_schedule_start_addon(self, bool catch_error=False)
bool task_in_progress(self)
None async_stop_addon(self)
asyncio.Task async_schedule_restart_addon(self, bool catch_error=False)
SupervisorClient get_supervisor_client(HomeAssistant hass)