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
This commit is contained in:
Franck Nijhof 2020-06-18 11:41:13 +02:00 committed by GitHub
parent 351c45da75
commit 44416edfd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 73 additions and 83 deletions

View File

@ -130,12 +130,9 @@ class Addon(AddonModel):
return {**self.data[ATTR_OPTIONS], **self.persist[ATTR_OPTIONS]} return {**self.data[ATTR_OPTIONS], **self.persist[ATTR_OPTIONS]}
@options.setter @options.setter
def options(self, value: Optional[Dict[str, Any]]): def options(self, value: Optional[Dict[str, Any]]) -> None:
"""Store user add-on options.""" """Store user add-on options."""
if value is None: self.persist[ATTR_OPTIONS] = {} if value is None else deepcopy(value)
self.persist[ATTR_OPTIONS] = {}
else:
self.persist[ATTR_OPTIONS] = deepcopy(value)
@property @property
def boot(self) -> bool: def boot(self) -> bool:

View File

@ -539,13 +539,16 @@ class AddonModel(CoreSysAttributes, ABC):
return False return False
# Home Assistant # Home Assistant
version = config.get(ATTR_HOMEASSISTANT) or self.sys_homeassistant.version version = config.get(ATTR_HOMEASSISTANT)
if pkg_version.parse(self.sys_homeassistant.version) < pkg_version.parse( if version is None or self.sys_homeassistant.version is None:
version return True
):
return False
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: def _image(self, config) -> str:
"""Generate image name from data.""" """Generate image name from data."""

View File

@ -148,39 +148,36 @@ class APIAddons(CoreSysAttributes):
@api_process @api_process
async def list(self, request: web.Request) -> Dict[str, Any]: async def list(self, request: web.Request) -> Dict[str, Any]:
"""Return all add-ons or repositories.""" """Return all add-ons or repositories."""
data_addons = [] data_addons = [
for addon in self.sys_addons.all: {
data_addons.append( ATTR_NAME: addon.name,
{ ATTR_SLUG: addon.slug,
ATTR_NAME: addon.name, ATTR_DESCRIPTON: addon.description,
ATTR_SLUG: addon.slug, ATTR_ADVANCED: addon.advanced,
ATTR_DESCRIPTON: addon.description, ATTR_STAGE: addon.stage,
ATTR_ADVANCED: addon.advanced, ATTR_VERSION: addon.latest_version,
ATTR_STAGE: addon.stage, ATTR_INSTALLED: addon.version if addon.is_installed else None,
ATTR_VERSION: addon.latest_version, ATTR_AVAILABLE: addon.available,
ATTR_INSTALLED: addon.version if addon.is_installed else None, ATTR_DETACHED: addon.is_detached,
ATTR_AVAILABLE: addon.available, ATTR_REPOSITORY: addon.repository,
ATTR_DETACHED: addon.is_detached, ATTR_BUILD: addon.need_build,
ATTR_REPOSITORY: addon.repository, ATTR_URL: addon.url,
ATTR_BUILD: addon.need_build, ATTR_ICON: addon.with_icon,
ATTR_URL: addon.url, ATTR_LOGO: addon.with_logo,
ATTR_ICON: addon.with_icon, }
ATTR_LOGO: addon.with_logo, for addon in self.sys_addons.all
} ]
)
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_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} return {ATTR_ADDONS: data_addons, ATTR_REPOSITORIES: data_repositories}
@api_process @api_process
@ -449,7 +446,4 @@ def _pretty_devices(addon: AnyAddon) -> List[str]:
def _pretty_services(addon: AnyAddon) -> List[str]: def _pretty_services(addon: AnyAddon) -> List[str]:
"""Return a simplified services role list.""" """Return a simplified services role list."""
services = [] return [f"{name}:{access}" for name, access in addon.services_role.items()]
for name, access in addon.services_role.items():
services.append(f"{name}:{access}")
return services

View File

@ -5,8 +5,8 @@ from ..const import (
ATTR_ADDON, ATTR_ADDON,
ATTR_CONFIG, ATTR_CONFIG,
ATTR_DISCOVERY, ATTR_DISCOVERY,
ATTR_SERVICES,
ATTR_SERVICE, ATTR_SERVICE,
ATTR_SERVICES,
ATTR_UUID, ATTR_UUID,
REQUEST_FROM, REQUEST_FROM,
) )

View File

@ -1,7 +1,7 @@
"""Init file for Supervisor hardware RESTful API.""" """Init file for Supervisor hardware RESTful API."""
import asyncio import asyncio
import logging import logging
from typing import Any, Dict from typing import Any, Awaitable, Dict
from aiohttp import web from aiohttp import web
@ -52,6 +52,6 @@ class APIHardware(CoreSysAttributes):
} }
@api_process @api_process
def trigger(self, request: web.Request) -> None: def trigger(self, request: web.Request) -> Awaitable[None]:
"""Trigger a udev device reload.""" """Trigger a udev device reload."""
return asyncio.shield(self.sys_hardware.udev_trigger()) return asyncio.shield(self.sys_hardware.udev_trigger())

View File

@ -1,7 +1,7 @@
"""Init file for Supervisor Home Assistant RESTful API.""" """Init file for Supervisor Home Assistant RESTful API."""
import asyncio import asyncio
import logging import logging
from typing import Any, Coroutine, Dict from typing import Any, Awaitable, Dict
from aiohttp import web from aiohttp import web
import voluptuous as vol import voluptuous as vol
@ -141,27 +141,27 @@ class APIHomeAssistant(CoreSysAttributes):
await asyncio.shield(self.sys_homeassistant.update(version)) await asyncio.shield(self.sys_homeassistant.update(version))
@api_process @api_process
def stop(self, request: web.Request) -> Coroutine: def stop(self, request: web.Request) -> Awaitable[None]:
"""Stop Home Assistant.""" """Stop Home Assistant."""
return asyncio.shield(self.sys_homeassistant.stop()) return asyncio.shield(self.sys_homeassistant.stop())
@api_process @api_process
def start(self, request: web.Request) -> Coroutine: def start(self, request: web.Request) -> Awaitable[None]:
"""Start Home Assistant.""" """Start Home Assistant."""
return asyncio.shield(self.sys_homeassistant.start()) return asyncio.shield(self.sys_homeassistant.start())
@api_process @api_process
def restart(self, request: web.Request) -> Coroutine: def restart(self, request: web.Request) -> Awaitable[None]:
"""Restart Home Assistant.""" """Restart Home Assistant."""
return asyncio.shield(self.sys_homeassistant.restart()) return asyncio.shield(self.sys_homeassistant.restart())
@api_process @api_process
def rebuild(self, request: web.Request) -> Coroutine: def rebuild(self, request: web.Request) -> Awaitable[None]:
"""Rebuild Home Assistant.""" """Rebuild Home Assistant."""
return asyncio.shield(self.sys_homeassistant.rebuild()) return asyncio.shield(self.sys_homeassistant.rebuild())
@api_process_raw(CONTENT_TYPE_BINARY) @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 Home Assistant Docker logs."""
return self.sys_homeassistant.logs() return self.sys_homeassistant.logs()

View File

@ -46,7 +46,7 @@ _LOGGER: logging.Logger = logging.getLogger(__name__)
MACHINE_ID = Path("/etc/machine-id") MACHINE_ID = Path("/etc/machine-id")
async def initialize_coresys() -> None: async def initialize_coresys() -> CoreSys:
"""Initialize supervisor coresys/objects.""" """Initialize supervisor coresys/objects."""
coresys = CoreSys() coresys = CoreSys()

View File

@ -1,10 +1,11 @@
"""Interface class for D-Bus wrappers.""" """Interface class for D-Bus wrappers."""
from abc import ABC, abstractmethod
from typing import Optional from typing import Optional
from ..utils.gdbus import DBus from ..utils.gdbus import DBus
class DBusInterface: class DBusInterface(ABC):
"""Handle D-Bus interface for hostname/system.""" """Handle D-Bus interface for hostname/system."""
dbus: Optional[DBus] = None dbus: Optional[DBus] = None
@ -14,6 +15,6 @@ class DBusInterface:
"""Return True, if they is connected to D-Bus.""" """Return True, if they is connected to D-Bus."""
return self.dbus is not None return self.dbus is not None
@abstractmethod
async def connect(self): async def connect(self):
"""Connect to D-Bus.""" """Connect to D-Bus."""
raise NotImplementedError()

View File

@ -107,9 +107,9 @@ class DockerAPI:
Need run inside executor. Need run inside executor.
""" """
name: str = kwargs.get("name") name: Optional[str] = kwargs.get("name")
network_mode: str = kwargs.get("network_mode") network_mode: Optional[str] = kwargs.get("network_mode")
hostname: str = kwargs.get("hostname") hostname: Optional[str] = kwargs.get("hostname")
# Setup DNS # Setup DNS
if dns: if dns:

View File

@ -47,7 +47,7 @@ class DockerAddon(DockerInterface):
self.addon = addon self.addon = addon
@property @property
def image(self) -> str: def image(self) -> Optional[str]:
"""Return name of Docker image.""" """Return name of Docker image."""
return self.addon.image return self.addon.image
@ -102,7 +102,7 @@ class DockerAddon(DockerInterface):
return not self.addon.protected and self.addon.with_full_access return not self.addon.protected and self.addon.with_full_access
@property @property
def environment(self) -> Dict[str, str]: def environment(self) -> Dict[str, Optional[str]]:
"""Return environment for Docker add-on.""" """Return environment for Docker add-on."""
addon_env = self.addon.environment or {} addon_env = self.addon.environment or {}
@ -189,7 +189,7 @@ class DockerAddon(DockerInterface):
return None return None
@property @property
def network_mapping(self) -> Dict[str, str]: def network_mapping(self) -> Dict[str, IPv4Address]:
"""Return hosts mapping.""" """Return hosts mapping."""
return { return {
"supervisor": self.sys_docker.network.supervisor, "supervisor": self.sys_docker.network.supervisor,

View File

@ -134,11 +134,7 @@ class DockerInterface(CoreSysAttributes):
except docker.errors.DockerException: except docker.errors.DockerException:
return False return False
# container is not running return docker_container.status == "running"
if docker_container.status != "running":
return False
return True
@process_lock @process_lock
def attach(self, tag: str): def attach(self, tag: str):
@ -301,6 +297,8 @@ class DockerInterface(CoreSysAttributes):
except docker.errors.DockerException as err: except docker.errors.DockerException as err:
_LOGGER.warning("Can't grep logs from %s: %s", self.image, err) _LOGGER.warning("Can't grep logs from %s: %s", self.image, err)
return b""
@process_lock @process_lock
def cleanup(self, old_image: Optional[str] = None) -> Awaitable[None]: def cleanup(self, old_image: Optional[str] = None) -> Awaitable[None]:
"""Check if old version exists and cleanup.""" """Check if old version exists and cleanup."""
@ -412,10 +410,7 @@ class DockerInterface(CoreSysAttributes):
return False return False
# Check return value # Check return value
if int(docker_container.attrs["State"]["ExitCode"]) != 0: return int(docker_container.attrs["State"]["ExitCode"]) != 0
return True
return False
def get_latest_version(self, key: Any = int) -> Awaitable[str]: def get_latest_version(self, key: Any = int) -> Awaitable[str]:
"""Return latest version of local Home Asssistant image.""" """Return latest version of local Home Asssistant image."""

View File

@ -202,12 +202,12 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
return self._data[ATTR_UUID] return self._data[ATTR_UUID]
@property @property
def supervisor_token(self) -> str: def supervisor_token(self) -> Optional[str]:
"""Return an access token for the Supervisor API.""" """Return an access token for the Supervisor API."""
return self._data.get(ATTR_ACCESS_TOKEN) return self._data.get(ATTR_ACCESS_TOKEN)
@property @property
def refresh_token(self) -> str: def refresh_token(self) -> Optional[str]:
"""Return the refresh token to authenticate with Home Assistant.""" """Return the refresh token to authenticate with Home Assistant."""
return self._data.get(ATTR_REFRESH_TOKEN) return self._data.get(ATTR_REFRESH_TOKEN)

View File

@ -49,7 +49,7 @@ class PluginManager(CoreSysAttributes):
"""Return multicast handler.""" """Return multicast handler."""
return self._multicast return self._multicast
async def load(self): async def load(self) -> None:
"""Load Supervisor plugins.""" """Load Supervisor plugins."""
# Sequential to avoid issue on slow IO # Sequential to avoid issue on slow IO
for plugin in ( for plugin in (
@ -94,7 +94,7 @@ class PluginManager(CoreSysAttributes):
required_version, required_version,
) )
async def repair(self): async def repair(self) -> None:
"""Repair Supervisor plugins.""" """Repair Supervisor plugins."""
await asyncio.wait( await asyncio.wait(
[ [

View File

@ -1,5 +1,5 @@
"""Handle internal services discovery.""" """Handle internal services discovery."""
from typing import Dict, List from typing import Dict, List, Optional
from ..coresys import CoreSys, CoreSysAttributes from ..coresys import CoreSys, CoreSysAttributes
from .const import SERVICE_MQTT, SERVICE_MYSQL from .const import SERVICE_MQTT, SERVICE_MYSQL
@ -25,7 +25,7 @@ class ServiceManager(CoreSysAttributes):
"""Return a list of services.""" """Return a list of services."""
return list(self.services_obj.values()) 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 service object from slug."""
return self.services_obj.get(slug) return self.services_obj.get(slug)

View File

@ -41,7 +41,7 @@ class SecureTarFile:
# Encryption/Description # Encryption/Description
self._aes: Optional[Cipher] = None self._aes: Optional[Cipher] = None
self._key: bytes = key self._key: Optional[bytes] = key
# Function helper # Function helper
self._decrypt: Optional[CipherContext] = None self._decrypt: Optional[CipherContext] = None
@ -101,7 +101,7 @@ class SecureTarFile:
return self._name return self._name
@property @property
def size(self) -> int: def size(self) -> float:
"""Return snapshot size.""" """Return snapshot size."""
if not self._name.is_file(): if not self._name.is_file():
return 0 return 0

View File

@ -36,8 +36,8 @@ from .const import (
ATTR_VERSION, ATTR_VERSION,
ATTR_WAIT_BOOT, ATTR_WAIT_BOOT,
ATTR_WATCHDOG, ATTR_WATCHDOG,
LogLevel,
SUPERVISOR_VERSION, SUPERVISOR_VERSION,
LogLevel,
UpdateChannels, UpdateChannels,
) )
from .utils.validate import validate_timezone from .utils.validate import validate_timezone