From 44416edfd24b832a9bf46f9b247896681f7c8ded Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 18 Jun 2020 11:41:13 +0200 Subject: [PATCH] Collection of minor changes (#1793) * Collection of minor changes * Process smaller comments * Fix HA version handling * More type hints * Review comment from Martin * Add protection against TypeError in version parsing --- supervisor/addons/addon.py | 7 +--- supervisor/addons/model.py | 15 +++++--- supervisor/api/addons.py | 66 +++++++++++++++------------------ supervisor/api/discovery.py | 2 +- supervisor/api/hardware.py | 4 +- supervisor/api/homeassistant.py | 12 +++--- supervisor/bootstrap.py | 2 +- supervisor/dbus/interface.py | 5 ++- supervisor/docker/__init__.py | 6 +-- supervisor/docker/addon.py | 6 +-- supervisor/docker/interface.py | 13 ++----- supervisor/homeassistant.py | 4 +- supervisor/plugins/__init__.py | 4 +- supervisor/services/__init__.py | 4 +- supervisor/utils/tar.py | 4 +- supervisor/validate.py | 2 +- 16 files changed, 73 insertions(+), 83 deletions(-) diff --git a/supervisor/addons/addon.py b/supervisor/addons/addon.py index 87be7d012..79f688dc0 100644 --- a/supervisor/addons/addon.py +++ b/supervisor/addons/addon.py @@ -130,12 +130,9 @@ class Addon(AddonModel): return {**self.data[ATTR_OPTIONS], **self.persist[ATTR_OPTIONS]} @options.setter - def options(self, value: Optional[Dict[str, Any]]): + def options(self, value: Optional[Dict[str, Any]]) -> None: """Store user add-on options.""" - if value is None: - self.persist[ATTR_OPTIONS] = {} - else: - self.persist[ATTR_OPTIONS] = deepcopy(value) + self.persist[ATTR_OPTIONS] = {} if value is None else deepcopy(value) @property def boot(self) -> bool: diff --git a/supervisor/addons/model.py b/supervisor/addons/model.py index 8e7e8c5c2..a2c6bd7a0 100644 --- a/supervisor/addons/model.py +++ b/supervisor/addons/model.py @@ -539,13 +539,16 @@ class AddonModel(CoreSysAttributes, ABC): return False # Home Assistant - version = config.get(ATTR_HOMEASSISTANT) or self.sys_homeassistant.version - if pkg_version.parse(self.sys_homeassistant.version) < pkg_version.parse( - version - ): - return False + version = config.get(ATTR_HOMEASSISTANT) + if version is None or self.sys_homeassistant.version is None: + return True - return True + try: + return pkg_version.parse( + self.sys_homeassistant.version + ) >= pkg_version.parse(version) + except pkg_version.InvalidVersion: + return True def _image(self, config) -> str: """Generate image name from data.""" diff --git a/supervisor/api/addons.py b/supervisor/api/addons.py index f827e506b..9800f73f3 100644 --- a/supervisor/api/addons.py +++ b/supervisor/api/addons.py @@ -148,39 +148,36 @@ class APIAddons(CoreSysAttributes): @api_process async def list(self, request: web.Request) -> Dict[str, Any]: """Return all add-ons or repositories.""" - data_addons = [] - for addon in self.sys_addons.all: - data_addons.append( - { - ATTR_NAME: addon.name, - ATTR_SLUG: addon.slug, - ATTR_DESCRIPTON: addon.description, - ATTR_ADVANCED: addon.advanced, - ATTR_STAGE: addon.stage, - ATTR_VERSION: addon.latest_version, - ATTR_INSTALLED: addon.version if addon.is_installed else None, - ATTR_AVAILABLE: addon.available, - ATTR_DETACHED: addon.is_detached, - ATTR_REPOSITORY: addon.repository, - ATTR_BUILD: addon.need_build, - ATTR_URL: addon.url, - ATTR_ICON: addon.with_icon, - ATTR_LOGO: addon.with_logo, - } - ) - - data_repositories = [] - for repository in self.sys_store.all: - data_repositories.append( - { - ATTR_SLUG: repository.slug, - ATTR_NAME: repository.name, - ATTR_SOURCE: repository.source, - ATTR_URL: repository.url, - ATTR_MAINTAINER: repository.maintainer, - } - ) + data_addons = [ + { + ATTR_NAME: addon.name, + ATTR_SLUG: addon.slug, + ATTR_DESCRIPTON: addon.description, + ATTR_ADVANCED: addon.advanced, + ATTR_STAGE: addon.stage, + ATTR_VERSION: addon.latest_version, + ATTR_INSTALLED: addon.version if addon.is_installed else None, + ATTR_AVAILABLE: addon.available, + ATTR_DETACHED: addon.is_detached, + ATTR_REPOSITORY: addon.repository, + ATTR_BUILD: addon.need_build, + ATTR_URL: addon.url, + ATTR_ICON: addon.with_icon, + ATTR_LOGO: addon.with_logo, + } + for addon in self.sys_addons.all + ] + data_repositories = [ + { + ATTR_SLUG: repository.slug, + ATTR_NAME: repository.name, + ATTR_SOURCE: repository.source, + ATTR_URL: repository.url, + ATTR_MAINTAINER: repository.maintainer, + } + for repository in self.sys_store.all + ] return {ATTR_ADDONS: data_addons, ATTR_REPOSITORIES: data_repositories} @api_process @@ -449,7 +446,4 @@ def _pretty_devices(addon: AnyAddon) -> List[str]: def _pretty_services(addon: AnyAddon) -> List[str]: """Return a simplified services role list.""" - services = [] - for name, access in addon.services_role.items(): - services.append(f"{name}:{access}") - return services + return [f"{name}:{access}" for name, access in addon.services_role.items()] diff --git a/supervisor/api/discovery.py b/supervisor/api/discovery.py index d606e6136..c76896243 100644 --- a/supervisor/api/discovery.py +++ b/supervisor/api/discovery.py @@ -5,8 +5,8 @@ from ..const import ( ATTR_ADDON, ATTR_CONFIG, ATTR_DISCOVERY, - ATTR_SERVICES, ATTR_SERVICE, + ATTR_SERVICES, ATTR_UUID, REQUEST_FROM, ) diff --git a/supervisor/api/hardware.py b/supervisor/api/hardware.py index 828e76a4b..e66c84a52 100644 --- a/supervisor/api/hardware.py +++ b/supervisor/api/hardware.py @@ -1,7 +1,7 @@ """Init file for Supervisor hardware RESTful API.""" import asyncio import logging -from typing import Any, Dict +from typing import Any, Awaitable, Dict from aiohttp import web @@ -52,6 +52,6 @@ class APIHardware(CoreSysAttributes): } @api_process - def trigger(self, request: web.Request) -> None: + def trigger(self, request: web.Request) -> Awaitable[None]: """Trigger a udev device reload.""" return asyncio.shield(self.sys_hardware.udev_trigger()) diff --git a/supervisor/api/homeassistant.py b/supervisor/api/homeassistant.py index b328ae800..52338fd4c 100644 --- a/supervisor/api/homeassistant.py +++ b/supervisor/api/homeassistant.py @@ -1,7 +1,7 @@ """Init file for Supervisor Home Assistant RESTful API.""" import asyncio import logging -from typing import Any, Coroutine, Dict +from typing import Any, Awaitable, Dict from aiohttp import web import voluptuous as vol @@ -141,27 +141,27 @@ class APIHomeAssistant(CoreSysAttributes): await asyncio.shield(self.sys_homeassistant.update(version)) @api_process - def stop(self, request: web.Request) -> Coroutine: + def stop(self, request: web.Request) -> Awaitable[None]: """Stop Home Assistant.""" return asyncio.shield(self.sys_homeassistant.stop()) @api_process - def start(self, request: web.Request) -> Coroutine: + def start(self, request: web.Request) -> Awaitable[None]: """Start Home Assistant.""" return asyncio.shield(self.sys_homeassistant.start()) @api_process - def restart(self, request: web.Request) -> Coroutine: + def restart(self, request: web.Request) -> Awaitable[None]: """Restart Home Assistant.""" return asyncio.shield(self.sys_homeassistant.restart()) @api_process - def rebuild(self, request: web.Request) -> Coroutine: + def rebuild(self, request: web.Request) -> Awaitable[None]: """Rebuild Home Assistant.""" return asyncio.shield(self.sys_homeassistant.rebuild()) @api_process_raw(CONTENT_TYPE_BINARY) - def logs(self, request: web.Request) -> Coroutine: + def logs(self, request: web.Request) -> Awaitable[bytes]: """Return Home Assistant Docker logs.""" return self.sys_homeassistant.logs() diff --git a/supervisor/bootstrap.py b/supervisor/bootstrap.py index 102cbfb22..4b6b413ae 100644 --- a/supervisor/bootstrap.py +++ b/supervisor/bootstrap.py @@ -46,7 +46,7 @@ _LOGGER: logging.Logger = logging.getLogger(__name__) MACHINE_ID = Path("/etc/machine-id") -async def initialize_coresys() -> None: +async def initialize_coresys() -> CoreSys: """Initialize supervisor coresys/objects.""" coresys = CoreSys() diff --git a/supervisor/dbus/interface.py b/supervisor/dbus/interface.py index 94ad6bb07..8a966b9ea 100644 --- a/supervisor/dbus/interface.py +++ b/supervisor/dbus/interface.py @@ -1,10 +1,11 @@ """Interface class for D-Bus wrappers.""" +from abc import ABC, abstractmethod from typing import Optional from ..utils.gdbus import DBus -class DBusInterface: +class DBusInterface(ABC): """Handle D-Bus interface for hostname/system.""" dbus: Optional[DBus] = None @@ -14,6 +15,6 @@ class DBusInterface: """Return True, if they is connected to D-Bus.""" return self.dbus is not None + @abstractmethod async def connect(self): """Connect to D-Bus.""" - raise NotImplementedError() diff --git a/supervisor/docker/__init__.py b/supervisor/docker/__init__.py index 7f9361ae0..985b73c56 100644 --- a/supervisor/docker/__init__.py +++ b/supervisor/docker/__init__.py @@ -107,9 +107,9 @@ class DockerAPI: Need run inside executor. """ - name: str = kwargs.get("name") - network_mode: str = kwargs.get("network_mode") - hostname: str = kwargs.get("hostname") + name: Optional[str] = kwargs.get("name") + network_mode: Optional[str] = kwargs.get("network_mode") + hostname: Optional[str] = kwargs.get("hostname") # Setup DNS if dns: diff --git a/supervisor/docker/addon.py b/supervisor/docker/addon.py index aa8d4e69d..939764b42 100644 --- a/supervisor/docker/addon.py +++ b/supervisor/docker/addon.py @@ -47,7 +47,7 @@ class DockerAddon(DockerInterface): self.addon = addon @property - def image(self) -> str: + def image(self) -> Optional[str]: """Return name of Docker image.""" return self.addon.image @@ -102,7 +102,7 @@ class DockerAddon(DockerInterface): return not self.addon.protected and self.addon.with_full_access @property - def environment(self) -> Dict[str, str]: + def environment(self) -> Dict[str, Optional[str]]: """Return environment for Docker add-on.""" addon_env = self.addon.environment or {} @@ -189,7 +189,7 @@ class DockerAddon(DockerInterface): return None @property - def network_mapping(self) -> Dict[str, str]: + def network_mapping(self) -> Dict[str, IPv4Address]: """Return hosts mapping.""" return { "supervisor": self.sys_docker.network.supervisor, diff --git a/supervisor/docker/interface.py b/supervisor/docker/interface.py index 24ffc512f..680349156 100644 --- a/supervisor/docker/interface.py +++ b/supervisor/docker/interface.py @@ -134,11 +134,7 @@ class DockerInterface(CoreSysAttributes): except docker.errors.DockerException: return False - # container is not running - if docker_container.status != "running": - return False - - return True + return docker_container.status == "running" @process_lock def attach(self, tag: str): @@ -301,6 +297,8 @@ class DockerInterface(CoreSysAttributes): except docker.errors.DockerException as err: _LOGGER.warning("Can't grep logs from %s: %s", self.image, err) + return b"" + @process_lock def cleanup(self, old_image: Optional[str] = None) -> Awaitable[None]: """Check if old version exists and cleanup.""" @@ -412,10 +410,7 @@ class DockerInterface(CoreSysAttributes): return False # Check return value - if int(docker_container.attrs["State"]["ExitCode"]) != 0: - return True - - return False + return int(docker_container.attrs["State"]["ExitCode"]) != 0 def get_latest_version(self, key: Any = int) -> Awaitable[str]: """Return latest version of local Home Asssistant image.""" diff --git a/supervisor/homeassistant.py b/supervisor/homeassistant.py index f2b43b6bf..592cb63d9 100644 --- a/supervisor/homeassistant.py +++ b/supervisor/homeassistant.py @@ -202,12 +202,12 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): return self._data[ATTR_UUID] @property - def supervisor_token(self) -> str: + def supervisor_token(self) -> Optional[str]: """Return an access token for the Supervisor API.""" return self._data.get(ATTR_ACCESS_TOKEN) @property - def refresh_token(self) -> str: + def refresh_token(self) -> Optional[str]: """Return the refresh token to authenticate with Home Assistant.""" return self._data.get(ATTR_REFRESH_TOKEN) diff --git a/supervisor/plugins/__init__.py b/supervisor/plugins/__init__.py index 81a746c30..dea5e4a8e 100644 --- a/supervisor/plugins/__init__.py +++ b/supervisor/plugins/__init__.py @@ -49,7 +49,7 @@ class PluginManager(CoreSysAttributes): """Return multicast handler.""" return self._multicast - async def load(self): + async def load(self) -> None: """Load Supervisor plugins.""" # Sequential to avoid issue on slow IO for plugin in ( @@ -94,7 +94,7 @@ class PluginManager(CoreSysAttributes): required_version, ) - async def repair(self): + async def repair(self) -> None: """Repair Supervisor plugins.""" await asyncio.wait( [ diff --git a/supervisor/services/__init__.py b/supervisor/services/__init__.py index 4d774f749..f85fdef14 100644 --- a/supervisor/services/__init__.py +++ b/supervisor/services/__init__.py @@ -1,5 +1,5 @@ """Handle internal services discovery.""" -from typing import Dict, List +from typing import Dict, List, Optional from ..coresys import CoreSys, CoreSysAttributes from .const import SERVICE_MQTT, SERVICE_MYSQL @@ -25,7 +25,7 @@ class ServiceManager(CoreSysAttributes): """Return a list of services.""" return list(self.services_obj.values()) - def get(self, slug: str) -> ServiceInterface: + def get(self, slug: str) -> Optional[ServiceInterface]: """Return service object from slug.""" return self.services_obj.get(slug) diff --git a/supervisor/utils/tar.py b/supervisor/utils/tar.py index 7c14066b2..712613071 100644 --- a/supervisor/utils/tar.py +++ b/supervisor/utils/tar.py @@ -41,7 +41,7 @@ class SecureTarFile: # Encryption/Description self._aes: Optional[Cipher] = None - self._key: bytes = key + self._key: Optional[bytes] = key # Function helper self._decrypt: Optional[CipherContext] = None @@ -101,7 +101,7 @@ class SecureTarFile: return self._name @property - def size(self) -> int: + def size(self) -> float: """Return snapshot size.""" if not self._name.is_file(): return 0 diff --git a/supervisor/validate.py b/supervisor/validate.py index 2701a94e0..fd46755bd 100644 --- a/supervisor/validate.py +++ b/supervisor/validate.py @@ -36,8 +36,8 @@ from .const import ( ATTR_VERSION, ATTR_WAIT_BOOT, ATTR_WATCHDOG, - LogLevel, SUPERVISOR_VERSION, + LogLevel, UpdateChannels, ) from .utils.validate import validate_timezone