mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 17:27:52 +00:00
Add config flow to version integration (#54642)
This commit is contained in:
parent
0ec2978698
commit
13e3ca6ab1
@ -1 +1,46 @@
|
||||
"""The version integration."""
|
||||
"""The Version integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pyhaversion import HaVersion
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import (
|
||||
BOARD_MAP,
|
||||
CONF_BOARD,
|
||||
CONF_CHANNEL,
|
||||
CONF_IMAGE,
|
||||
CONF_SOURCE,
|
||||
DOMAIN,
|
||||
PLATFORMS,
|
||||
)
|
||||
from .coordinator import VersionDataUpdateCoordinator
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up the version integration from a config entry."""
|
||||
coordinator = VersionDataUpdateCoordinator(
|
||||
hass=hass,
|
||||
api=HaVersion(
|
||||
session=async_get_clientsession(hass),
|
||||
source=entry.data[CONF_SOURCE],
|
||||
image=entry.data[CONF_IMAGE],
|
||||
board=BOARD_MAP[entry.data[CONF_BOARD]],
|
||||
channel=entry.data[CONF_CHANNEL].lower(),
|
||||
),
|
||||
)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload the config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
return unload_ok
|
||||
|
202
homeassistant/components/version/config_flow.py
Normal file
202
homeassistant/components/version/config_flow.py
Normal file
@ -0,0 +1,202 @@
|
||||
"""Config flow for Version integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pyhaversion.consts import HaVersionChannel, HaVersionSource
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_NAME, CONF_SOURCE
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from .const import (
|
||||
ATTR_VERSION_SOURCE,
|
||||
CONF_BETA,
|
||||
CONF_BOARD,
|
||||
CONF_CHANNEL,
|
||||
CONF_IMAGE,
|
||||
CONF_VERSION_SOURCE,
|
||||
DEFAULT_BOARD,
|
||||
DEFAULT_CHANNEL,
|
||||
DEFAULT_CONFIGURATION,
|
||||
DEFAULT_IMAGE,
|
||||
DEFAULT_NAME,
|
||||
DEFAULT_NAME_CURRENT,
|
||||
DEFAULT_NAME_LATEST,
|
||||
DEFAULT_SOURCE,
|
||||
DOMAIN,
|
||||
POSTFIX_CONTAINER_NAME,
|
||||
SOURCE_DOKCER,
|
||||
SOURCE_HASSIO,
|
||||
STEP_USER,
|
||||
STEP_VERSION_SOURCE,
|
||||
VALID_BOARDS,
|
||||
VALID_CHANNELS,
|
||||
VALID_CONTAINER_IMAGES,
|
||||
VALID_IMAGES,
|
||||
VERSION_SOURCE_DOCKER_HUB,
|
||||
VERSION_SOURCE_LOCAL,
|
||||
VERSION_SOURCE_MAP,
|
||||
VERSION_SOURCE_MAP_INVERTED,
|
||||
VERSION_SOURCE_VERSIONS,
|
||||
)
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Version."""
|
||||
|
||||
_entry_data: dict[str, Any] = DEFAULT_CONFIGURATION.copy()
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(
|
||||
self,
|
||||
user_input: dict[str, Any] | None = None,
|
||||
) -> FlowResult:
|
||||
"""Handle the initial user step."""
|
||||
if user_input is None:
|
||||
self._entry_data = DEFAULT_CONFIGURATION.copy()
|
||||
return self.async_show_form(
|
||||
step_id=STEP_USER,
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
CONF_VERSION_SOURCE,
|
||||
default=VERSION_SOURCE_LOCAL,
|
||||
): vol.In(VERSION_SOURCE_MAP.keys())
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
user_input[CONF_SOURCE] = VERSION_SOURCE_MAP[user_input[CONF_VERSION_SOURCE]]
|
||||
self._entry_data.update(user_input)
|
||||
|
||||
if not self.show_advanced_options or user_input[CONF_SOURCE] in (
|
||||
HaVersionSource.LOCAL,
|
||||
HaVersionSource.HAIO,
|
||||
):
|
||||
return self.async_create_entry(
|
||||
title=self._config_entry_name,
|
||||
data=self._entry_data,
|
||||
)
|
||||
|
||||
return await self.async_step_version_source()
|
||||
|
||||
async def async_step_version_source(
|
||||
self,
|
||||
user_input: dict[str, Any] | None = None,
|
||||
) -> FlowResult:
|
||||
"""Handle the version_source step."""
|
||||
if user_input is None:
|
||||
if self._entry_data[CONF_SOURCE] in (
|
||||
HaVersionSource.SUPERVISOR,
|
||||
HaVersionSource.CONTAINER,
|
||||
):
|
||||
data_schema = vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
CONF_CHANNEL, default=DEFAULT_CHANNEL.title()
|
||||
): vol.In(VALID_CHANNELS),
|
||||
}
|
||||
)
|
||||
if self._entry_data[CONF_SOURCE] == HaVersionSource.SUPERVISOR:
|
||||
data_schema = data_schema.extend(
|
||||
{
|
||||
vol.Required(CONF_IMAGE, default=DEFAULT_IMAGE): vol.In(
|
||||
VALID_IMAGES
|
||||
),
|
||||
vol.Required(CONF_BOARD, default=DEFAULT_BOARD): vol.In(
|
||||
VALID_BOARDS
|
||||
),
|
||||
}
|
||||
)
|
||||
else:
|
||||
data_schema = data_schema.extend(
|
||||
{
|
||||
vol.Required(CONF_IMAGE, default=DEFAULT_IMAGE): vol.In(
|
||||
VALID_CONTAINER_IMAGES
|
||||
)
|
||||
}
|
||||
)
|
||||
else:
|
||||
data_schema = vol.Schema({vol.Required(CONF_BETA, default=False): bool})
|
||||
|
||||
return self.async_show_form(
|
||||
step_id=STEP_VERSION_SOURCE,
|
||||
data_schema=data_schema,
|
||||
description_placeholders={
|
||||
ATTR_VERSION_SOURCE: self._entry_data[CONF_VERSION_SOURCE]
|
||||
},
|
||||
)
|
||||
self._entry_data.update(user_input)
|
||||
self._entry_data[CONF_CHANNEL] = self._entry_data[CONF_CHANNEL].lower()
|
||||
|
||||
return self.async_create_entry(
|
||||
title=self._config_entry_name, data=self._entry_data
|
||||
)
|
||||
|
||||
async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult:
|
||||
"""Import a config entry from configuration.yaml."""
|
||||
self._entry_data = _convert_imported_configuration(import_config)
|
||||
|
||||
for entry in self._async_current_entries():
|
||||
if _fingerprint(entry.data) == _fingerprint(self._entry_data):
|
||||
return self.async_abort(reason="already_configured")
|
||||
|
||||
return self.async_create_entry(
|
||||
title=self._config_entry_name, data=self._entry_data
|
||||
)
|
||||
|
||||
@property
|
||||
def _config_entry_name(self) -> str:
|
||||
"""Return the name of the config entry."""
|
||||
if self._entry_data[CONF_SOURCE] == HaVersionSource.LOCAL:
|
||||
return DEFAULT_NAME_CURRENT
|
||||
|
||||
name = self._entry_data[CONF_VERSION_SOURCE]
|
||||
|
||||
if (channel := self._entry_data[CONF_CHANNEL]) != DEFAULT_CHANNEL:
|
||||
return f"{name} {channel.title()}"
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def _fingerprint(data) -> str:
|
||||
"""Return a fingerprint of the configuration."""
|
||||
configuration = {**DEFAULT_CONFIGURATION, **data}
|
||||
return slugify("_".join(configuration.values()))
|
||||
|
||||
|
||||
def _convert_imported_configuration(config: dict[str, Any]) -> Any:
|
||||
"""Convert a key from the imported configuration."""
|
||||
data = DEFAULT_CONFIGURATION.copy()
|
||||
if config.get(CONF_BETA):
|
||||
data[CONF_CHANNEL] = HaVersionChannel.BETA
|
||||
|
||||
if (source := config.get(CONF_SOURCE)) and source != DEFAULT_SOURCE:
|
||||
if source == SOURCE_HASSIO:
|
||||
data[CONF_SOURCE] = HaVersionSource.SUPERVISOR
|
||||
data[CONF_VERSION_SOURCE] = VERSION_SOURCE_VERSIONS
|
||||
elif source == SOURCE_DOKCER:
|
||||
data[CONF_SOURCE] = HaVersionSource.CONTAINER
|
||||
data[CONF_VERSION_SOURCE] = VERSION_SOURCE_DOCKER_HUB
|
||||
else:
|
||||
data[CONF_SOURCE] = source
|
||||
data[CONF_VERSION_SOURCE] = VERSION_SOURCE_MAP_INVERTED[source]
|
||||
|
||||
if (image := config.get(CONF_IMAGE)) and image != DEFAULT_IMAGE:
|
||||
if data[CONF_SOURCE] == HaVersionSource.CONTAINER:
|
||||
data[CONF_IMAGE] = f"{config[CONF_IMAGE]}{POSTFIX_CONTAINER_NAME}"
|
||||
else:
|
||||
data[CONF_IMAGE] = config[CONF_IMAGE]
|
||||
|
||||
if (name := config.get(CONF_NAME)) and name != DEFAULT_NAME:
|
||||
data[CONF_NAME] = config[CONF_NAME]
|
||||
else:
|
||||
if data[CONF_SOURCE] == HaVersionSource.LOCAL:
|
||||
data[CONF_NAME] = DEFAULT_NAME_CURRENT
|
||||
else:
|
||||
data[CONF_NAME] = DEFAULT_NAME_LATEST
|
||||
return data
|
128
homeassistant/components/version/const.py
Normal file
128
homeassistant/components/version/const.py
Normal file
@ -0,0 +1,128 @@
|
||||
"""Constants for the Version integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from logging import Logger, getLogger
|
||||
from typing import Any, Final
|
||||
|
||||
from pyhaversion.consts import HaVersionChannel, HaVersionSource
|
||||
|
||||
from homeassistant.const import CONF_NAME, Platform
|
||||
|
||||
DOMAIN: Final = "version"
|
||||
LOGGER: Final[Logger] = getLogger(__package__)
|
||||
PLATFORMS: Final[list[Platform]] = [Platform.SENSOR]
|
||||
UPDATE_COORDINATOR_UPDATE_INTERVAL: Final[timedelta] = timedelta(minutes=5)
|
||||
|
||||
ENTRY_TYPE_SERVICE: Final = "service"
|
||||
HOME_ASSISTANT: Final = "Home Assistant"
|
||||
POSTFIX_CONTAINER_NAME: Final = "-homeassistant"
|
||||
|
||||
|
||||
CONF_BETA: Final = "beta"
|
||||
CONF_BOARD: Final = "board"
|
||||
CONF_CHANNEL: Final = "channel"
|
||||
CONF_IMAGE: Final = "image"
|
||||
CONF_VERSION_SOURCE: Final = "version_source"
|
||||
CONF_SOURCE: Final = "source"
|
||||
|
||||
ATTR_CHANNEL: Final = CONF_CHANNEL
|
||||
ATTR_VERSION_SOURCE: Final = CONF_VERSION_SOURCE
|
||||
ATTR_SOURCE: Final = CONF_SOURCE
|
||||
|
||||
SOURCE_DOKCER: Final = "docker" # Kept to not break existing configurations
|
||||
SOURCE_HASSIO: Final = "hassio" # Kept to not break existing configurations
|
||||
|
||||
VERSION_SOURCE_DOCKER_HUB: Final = "Docker Hub"
|
||||
VERSION_SOURCE_HAIO: Final = "Home Assistant Website"
|
||||
VERSION_SOURCE_LOCAL: Final = "Local installation"
|
||||
VERSION_SOURCE_PYPI: Final = "Python Package Index (PyPI)"
|
||||
VERSION_SOURCE_VERSIONS: Final = "Home Assistant Versions"
|
||||
|
||||
DEFAULT_BETA: Final = False
|
||||
DEFAULT_BOARD: Final = "OVA"
|
||||
DEFAULT_CHANNEL: Final[HaVersionChannel] = HaVersionChannel.STABLE
|
||||
DEFAULT_IMAGE: Final = "default"
|
||||
DEFAULT_NAME_CURRENT: Final = "Current Version"
|
||||
DEFAULT_NAME_LATEST: Final = "Latest Version"
|
||||
DEFAULT_NAME: Final = ""
|
||||
DEFAULT_SOURCE: Final[HaVersionSource] = HaVersionSource.LOCAL
|
||||
DEFAULT_CONFIGURATION: Final[dict[str, Any]] = {
|
||||
CONF_NAME: DEFAULT_NAME,
|
||||
CONF_CHANNEL: DEFAULT_CHANNEL,
|
||||
CONF_IMAGE: DEFAULT_IMAGE,
|
||||
CONF_BOARD: DEFAULT_BOARD,
|
||||
CONF_VERSION_SOURCE: VERSION_SOURCE_LOCAL,
|
||||
CONF_SOURCE: DEFAULT_SOURCE,
|
||||
}
|
||||
|
||||
STEP_VERSION_SOURCE: Final = "version_source"
|
||||
STEP_USER: Final = "user"
|
||||
|
||||
HA_VERSION_SOURCES: Final[list[str]] = [source.value for source in HaVersionSource]
|
||||
|
||||
BOARD_MAP: Final[dict[str, str]] = {
|
||||
"OVA": "ova",
|
||||
"RaspberryPi": "rpi",
|
||||
"RaspberryPi Zero-W": "rpi0-w",
|
||||
"RaspberryPi 2": "rpi2",
|
||||
"RaspberryPi 3": "rpi3",
|
||||
"RaspberryPi 3 64bit": "rpi3-64",
|
||||
"RaspberryPi 4": "rpi4",
|
||||
"RaspberryPi 4 64bit": "rpi4-64",
|
||||
"ASUS Tinkerboard": "tinker",
|
||||
"ODROID C2": "odroid-c2",
|
||||
"ODROID C4": "odroid-c4",
|
||||
"ODROID N2": "odroid-n2",
|
||||
"ODROID XU4": "odroid-xu4",
|
||||
"Generic x86-64": "generic-x86-64",
|
||||
"Intel NUC": "intel-nuc",
|
||||
}
|
||||
|
||||
VALID_BOARDS: Final[list[str]] = list(BOARD_MAP)
|
||||
|
||||
VERSION_SOURCE_MAP: Final[dict[str, HaVersionSource]] = {
|
||||
VERSION_SOURCE_LOCAL: HaVersionSource.LOCAL,
|
||||
VERSION_SOURCE_VERSIONS: HaVersionSource.SUPERVISOR,
|
||||
VERSION_SOURCE_HAIO: HaVersionSource.HAIO,
|
||||
VERSION_SOURCE_DOCKER_HUB: HaVersionSource.CONTAINER,
|
||||
VERSION_SOURCE_PYPI: HaVersionSource.PYPI,
|
||||
}
|
||||
|
||||
VERSION_SOURCE_MAP_INVERTED: Final[dict[HaVersionSource, str]] = {
|
||||
value: key for key, value in VERSION_SOURCE_MAP.items()
|
||||
}
|
||||
|
||||
|
||||
VALID_SOURCES: Final[list[str]] = HA_VERSION_SOURCES + [
|
||||
SOURCE_HASSIO, # Kept to not break existing configurations
|
||||
SOURCE_DOKCER, # Kept to not break existing configurations
|
||||
]
|
||||
|
||||
VALID_IMAGES: Final = [
|
||||
"default",
|
||||
"generic-x86-64",
|
||||
"intel-nuc",
|
||||
"odroid-c2",
|
||||
"odroid-n2",
|
||||
"odroid-xu",
|
||||
"qemuarm-64",
|
||||
"qemuarm",
|
||||
"qemux86-64",
|
||||
"qemux86",
|
||||
"raspberrypi",
|
||||
"raspberrypi2",
|
||||
"raspberrypi3-64",
|
||||
"raspberrypi3",
|
||||
"raspberrypi4-64",
|
||||
"raspberrypi4",
|
||||
"tinker",
|
||||
]
|
||||
|
||||
VALID_CONTAINER_IMAGES: Final[list[str]] = [
|
||||
f"{image}{POSTFIX_CONTAINER_NAME}" if image != DEFAULT_IMAGE else image
|
||||
for image in VALID_IMAGES
|
||||
]
|
||||
VALID_CHANNELS: Final[list[str]] = [
|
||||
str(channel.value).title() for channel in HaVersionChannel
|
||||
]
|
54
homeassistant/components/version/coordinator.py
Normal file
54
homeassistant/components/version/coordinator.py
Normal file
@ -0,0 +1,54 @@
|
||||
"""Data update coordinator for Version entities."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from awesomeversion import AwesomeVersion
|
||||
from pyhaversion import HaVersion
|
||||
from pyhaversion.exceptions import HaVersionException
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN, LOGGER, UPDATE_COORDINATOR_UPDATE_INTERVAL
|
||||
|
||||
|
||||
class VersionDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Data update coordinator for Version entities."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
api: HaVersion,
|
||||
) -> None:
|
||||
"""Initialize the coordinator."""
|
||||
super().__init__(
|
||||
hass=hass,
|
||||
logger=LOGGER,
|
||||
name=DOMAIN,
|
||||
update_method=self._async_update_version_data,
|
||||
update_interval=UPDATE_COORDINATOR_UPDATE_INTERVAL,
|
||||
)
|
||||
self._api = api
|
||||
self._version: AwesomeVersion | None = None
|
||||
self._version_data: dict[str, Any] | None = None
|
||||
|
||||
@property
|
||||
def version(self) -> str | None:
|
||||
"""Return the latest version."""
|
||||
return str(self._version) if self._version else None
|
||||
|
||||
@property
|
||||
def version_data(self) -> dict[str, Any]:
|
||||
"""Return the version data."""
|
||||
return self._version_data or {}
|
||||
|
||||
async def _async_update_version_data(self) -> None:
|
||||
"""Update version data."""
|
||||
try:
|
||||
self._version, self._version_data = await self._api.get_version()
|
||||
except HaVersionException as exception:
|
||||
raise UpdateFailed(exception) from exception
|
@ -10,5 +10,6 @@
|
||||
"@ludeeus"
|
||||
],
|
||||
"quality_scale": "internal",
|
||||
"iot_class": "local_push"
|
||||
"iot_class": "local_push",
|
||||
"config_flow": true
|
||||
}
|
@ -1,156 +1,127 @@
|
||||
"""Sensor that can display the current Home Assistant versions."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Final
|
||||
|
||||
from pyhaversion import (
|
||||
HaVersion,
|
||||
HaVersionChannel,
|
||||
HaVersionSource,
|
||||
exceptions as pyhaversionexceptions,
|
||||
)
|
||||
import voluptuous as vol
|
||||
from voluptuous.schema_builder import Schema
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
PLATFORM_SCHEMA,
|
||||
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.const import CONF_NAME, CONF_SOURCE
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
ALL_IMAGES = [
|
||||
"default",
|
||||
"generic-x86-64",
|
||||
"intel-nuc",
|
||||
"odroid-c2",
|
||||
"odroid-n2",
|
||||
"odroid-xu",
|
||||
"qemuarm-64",
|
||||
"qemuarm",
|
||||
"qemux86-64",
|
||||
"qemux86",
|
||||
"raspberrypi",
|
||||
"raspberrypi2",
|
||||
"raspberrypi3-64",
|
||||
"raspberrypi3",
|
||||
"raspberrypi4-64",
|
||||
"raspberrypi4",
|
||||
"tinker",
|
||||
]
|
||||
from .const import (
|
||||
ATTR_SOURCE,
|
||||
CONF_BETA,
|
||||
CONF_IMAGE,
|
||||
CONF_SOURCE,
|
||||
DEFAULT_BETA,
|
||||
DEFAULT_IMAGE,
|
||||
DEFAULT_NAME,
|
||||
DEFAULT_SOURCE,
|
||||
DOMAIN,
|
||||
HOME_ASSISTANT,
|
||||
LOGGER,
|
||||
VALID_IMAGES,
|
||||
VALID_SOURCES,
|
||||
)
|
||||
from .coordinator import VersionDataUpdateCoordinator
|
||||
|
||||
HA_VERSION_SOURCES = [source.value for source in HaVersionSource]
|
||||
|
||||
ALL_SOURCES = HA_VERSION_SOURCES + [
|
||||
"hassio", # Kept to not break existing configurations
|
||||
"docker", # Kept to not break existing configurations
|
||||
]
|
||||
|
||||
CONF_BETA = "beta"
|
||||
CONF_IMAGE = "image"
|
||||
|
||||
DEFAULT_IMAGE = "default"
|
||||
DEFAULT_NAME_LATEST = "Latest Version"
|
||||
DEFAULT_NAME_LOCAL = "Current Version"
|
||||
DEFAULT_SOURCE = "local"
|
||||
|
||||
TIME_BETWEEN_UPDATES = timedelta(minutes=5)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
PLATFORM_SCHEMA: Final[Schema] = SENSOR_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Optional(CONF_BETA, default=False): cv.boolean,
|
||||
vol.Optional(CONF_IMAGE, default=DEFAULT_IMAGE): vol.In(ALL_IMAGES),
|
||||
vol.Optional(CONF_NAME, default=""): cv.string,
|
||||
vol.Optional(CONF_SOURCE, default=DEFAULT_SOURCE): vol.In(ALL_SOURCES),
|
||||
vol.Optional(CONF_BETA, default=DEFAULT_BETA): cv.boolean,
|
||||
vol.Optional(CONF_IMAGE, default=DEFAULT_IMAGE): vol.In(VALID_IMAGES),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_SOURCE, default=DEFAULT_SOURCE): vol.In(VALID_SOURCES),
|
||||
}
|
||||
)
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the Version sensor platform."""
|
||||
|
||||
beta = config.get(CONF_BETA)
|
||||
image = config.get(CONF_IMAGE)
|
||||
name = config.get(CONF_NAME)
|
||||
source = config.get(CONF_SOURCE)
|
||||
|
||||
channel = HaVersionChannel.BETA if beta else HaVersionChannel.STABLE
|
||||
session = async_get_clientsession(hass)
|
||||
|
||||
if source in HA_VERSION_SOURCES:
|
||||
source = HaVersionSource(source)
|
||||
elif source == "hassio":
|
||||
source = HaVersionSource.SUPERVISOR
|
||||
elif source == "docker":
|
||||
source = HaVersionSource.CONTAINER
|
||||
|
||||
if (
|
||||
source == HaVersionSource.CONTAINER
|
||||
and image is not None
|
||||
and image != DEFAULT_IMAGE
|
||||
):
|
||||
image = f"{image}-homeassistant"
|
||||
|
||||
if not (name := config.get(CONF_NAME)):
|
||||
if source == HaVersionSource.LOCAL:
|
||||
name = DEFAULT_NAME_LOCAL
|
||||
else:
|
||||
name = DEFAULT_NAME_LATEST
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
VersionSensor(
|
||||
VersionData(
|
||||
HaVersion(
|
||||
session=session, source=source, image=image, channel=channel
|
||||
)
|
||||
),
|
||||
SensorEntityDescription(key=source, name=name),
|
||||
)
|
||||
],
|
||||
True,
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the legacy version sensor platform."""
|
||||
LOGGER.warning(
|
||||
"Configuration of the Version platform in YAML is deprecated and will be "
|
||||
"removed in Home Assistant 2022.4; Your existing configuration "
|
||||
"has been imported into the UI automatically and can be safely removed "
|
||||
"from your configuration.yaml file"
|
||||
)
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={ATTR_SOURCE: SOURCE_IMPORT}, data=config
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class VersionData:
|
||||
"""Get the latest data and update the states."""
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up version sensors."""
|
||||
coordinator: VersionDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
if (entity_name := entry.data[CONF_NAME]) == DEFAULT_NAME:
|
||||
entity_name = entry.title
|
||||
|
||||
def __init__(self, api: HaVersion) -> None:
|
||||
"""Initialize the data object."""
|
||||
self.api = api
|
||||
version_sensor_entities: list[VersionSensorEntity] = [
|
||||
VersionSensorEntity(
|
||||
coordinator=coordinator,
|
||||
entity_description=SensorEntityDescription(
|
||||
key=str(entry.data[CONF_SOURCE]),
|
||||
name=entity_name,
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
@Throttle(TIME_BETWEEN_UPDATES)
|
||||
async def async_update(self):
|
||||
"""Get the latest version information."""
|
||||
try:
|
||||
await self.api.get_version()
|
||||
except pyhaversionexceptions.HaVersionFetchException as exception:
|
||||
_LOGGER.warning(exception)
|
||||
except pyhaversionexceptions.HaVersionParseException as exception:
|
||||
_LOGGER.warning(
|
||||
"Could not parse data received for %s - %s", self.api.source, exception
|
||||
)
|
||||
async_add_entities(version_sensor_entities)
|
||||
|
||||
|
||||
class VersionSensor(SensorEntity):
|
||||
"""Representation of a Home Assistant version sensor."""
|
||||
class VersionSensorEntity(CoordinatorEntity, SensorEntity):
|
||||
"""Version sensor entity class."""
|
||||
|
||||
_attr_icon = "mdi:package-up"
|
||||
_attr_device_info = DeviceInfo(
|
||||
name=f"{HOME_ASSISTANT} {DOMAIN.title()}",
|
||||
identifiers={(HOME_ASSISTANT, DOMAIN)},
|
||||
manufacturer=HOME_ASSISTANT,
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
)
|
||||
|
||||
coordinator: VersionDataUpdateCoordinator
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: VersionData,
|
||||
description: SensorEntityDescription,
|
||||
coordinator: VersionDataUpdateCoordinator,
|
||||
entity_description: SensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the Version sensor."""
|
||||
self.data = data
|
||||
self.entity_description = description
|
||||
"""Initialize version sensor entities."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.config_entry.unique_id}_{entity_description.key}"
|
||||
)
|
||||
|
||||
async def async_update(self):
|
||||
"""Get the latest version information."""
|
||||
await self.data.async_update()
|
||||
self._attr_native_value = self.data.api.version
|
||||
self._attr_extra_state_attributes = self.data.api.version_data
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the native value of this sensor."""
|
||||
return self.coordinator.version
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
"""Return extra state attributes of this sensor."""
|
||||
return self.coordinator.version_data
|
||||
|
26
homeassistant/components/version/strings.json
Normal file
26
homeassistant/components/version/strings.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Select installation type",
|
||||
"description": "Select the source you want to track versions from",
|
||||
"data": {
|
||||
"version_source": "Version source"
|
||||
}
|
||||
},
|
||||
"version_source": {
|
||||
"title": "Configure",
|
||||
"description": "Configure {version_source} version tracking",
|
||||
"data": {
|
||||
"beta": "Include beta versions",
|
||||
"board": "Which board should be tracked",
|
||||
"channel": "Which channel should be tracked",
|
||||
"image": "Which image should be tracked"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
homeassistant/components/version/translations/en.json
Normal file
26
homeassistant/components/version/translations/en.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Device is already configured"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"version_source": "Version source"
|
||||
},
|
||||
"description": "Select the source you want to track versions from",
|
||||
"title": "Select installation type"
|
||||
},
|
||||
"version_source": {
|
||||
"data": {
|
||||
"beta": "Include beta versions",
|
||||
"board": "Which board should be tracked",
|
||||
"channel": "Which channel should be tracked",
|
||||
"image": "Which image should be tracked"
|
||||
},
|
||||
"description": "Configure {version_source} version tracking",
|
||||
"title": "Configure"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -331,6 +331,7 @@ FLOWS = [
|
||||
"venstar",
|
||||
"vera",
|
||||
"verisure",
|
||||
"version",
|
||||
"vesync",
|
||||
"vicare",
|
||||
"vilfo",
|
||||
|
73
tests/components/version/common.py
Normal file
73
tests/components/version/common.py
Normal file
@ -0,0 +1,73 @@
|
||||
"""Fixtures for version integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Final
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.version.const import (
|
||||
DEFAULT_CONFIGURATION,
|
||||
DEFAULT_NAME_CURRENT,
|
||||
DOMAIN,
|
||||
UPDATE_COORDINATOR_UPDATE_INTERVAL,
|
||||
VERSION_SOURCE_LOCAL,
|
||||
)
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
MOCK_VERSION: Final = "1970.1.0"
|
||||
MOCK_VERSION_DATA: Final = {"source": "local", "channel": "stable"}
|
||||
|
||||
|
||||
MOCK_VERSION_CONFIG_ENTRY_DATA: Final[dict[str, Any]] = {
|
||||
"domain": DOMAIN,
|
||||
"title": VERSION_SOURCE_LOCAL,
|
||||
"data": DEFAULT_CONFIGURATION,
|
||||
"source": config_entries.SOURCE_USER,
|
||||
}
|
||||
|
||||
TEST_DEFAULT_IMPORT_CONFIG: Final = {
|
||||
**DEFAULT_CONFIGURATION,
|
||||
CONF_NAME: DEFAULT_NAME_CURRENT,
|
||||
}
|
||||
|
||||
|
||||
async def mock_get_version_update(
|
||||
hass: HomeAssistant,
|
||||
version: str = MOCK_VERSION,
|
||||
data: dict[str, Any] = MOCK_VERSION_DATA,
|
||||
side_effect: Exception = None,
|
||||
) -> None:
|
||||
"""Mock getting version."""
|
||||
with patch(
|
||||
"pyhaversion.HaVersion.get_version",
|
||||
return_value=(version, data),
|
||||
side_effect=side_effect,
|
||||
):
|
||||
|
||||
async_fire_time_changed(hass, dt.utcnow() + UPDATE_COORDINATOR_UPDATE_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def setup_version_integration(hass: HomeAssistant) -> MockConfigEntry:
|
||||
"""Set up the Version integration."""
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
mock_entry = MockConfigEntry(**MOCK_VERSION_CONFIG_ENTRY_DATA)
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"pyhaversion.HaVersion.get_version",
|
||||
return_value=(MOCK_VERSION, MOCK_VERSION_DATA),
|
||||
):
|
||||
|
||||
assert await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("sensor.local_installation").state == MOCK_VERSION
|
||||
assert mock_entry.state == config_entries.ConfigEntryState.LOADED
|
||||
|
||||
return mock_entry
|
236
tests/components/version/test_config_flow.py
Normal file
236
tests/components/version/test_config_flow.py
Normal file
@ -0,0 +1,236 @@
|
||||
"""Test the Version config flow."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from pyhaversion.consts import HaVersionChannel, HaVersionSource
|
||||
|
||||
from homeassistant import config_entries, setup
|
||||
from homeassistant.components.version.const import (
|
||||
CONF_BETA,
|
||||
CONF_BOARD,
|
||||
CONF_CHANNEL,
|
||||
CONF_IMAGE,
|
||||
CONF_VERSION_SOURCE,
|
||||
DEFAULT_CONFIGURATION,
|
||||
DOMAIN,
|
||||
UPDATE_COORDINATOR_UPDATE_INTERVAL,
|
||||
VERSION_SOURCE_DOCKER_HUB,
|
||||
VERSION_SOURCE_PYPI,
|
||||
VERSION_SOURCE_VERSIONS,
|
||||
)
|
||||
from homeassistant.const import CONF_SOURCE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import (
|
||||
RESULT_TYPE_ABORT,
|
||||
RESULT_TYPE_CREATE_ENTRY,
|
||||
RESULT_TYPE_FORM,
|
||||
)
|
||||
from homeassistant.util import dt
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
from tests.components.version.common import (
|
||||
MOCK_VERSION,
|
||||
MOCK_VERSION_DATA,
|
||||
setup_version_integration,
|
||||
)
|
||||
|
||||
|
||||
async def test_reload(hass: HomeAssistant):
|
||||
"""Test the Version sensor with different sources."""
|
||||
config_entry = await setup_version_integration(hass)
|
||||
|
||||
with patch(
|
||||
"pyhaversion.HaVersion.get_version",
|
||||
return_value=(MOCK_VERSION, MOCK_VERSION_DATA),
|
||||
):
|
||||
assert await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
async_fire_time_changed(hass, dt.utcnow() + UPDATE_COORDINATOR_UPDATE_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entry = hass.config_entries.async_get_entry(config_entry.entry_id)
|
||||
assert entry.state == config_entries.ConfigEntryState.LOADED
|
||||
assert hass.states.get("sensor.local_installation").state == MOCK_VERSION
|
||||
|
||||
|
||||
async def test_basic_form(hass: HomeAssistant) -> None:
|
||||
"""Test we get the form."""
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER, "show_advanced_options": False},
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.version.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_VERSION_SOURCE: VERSION_SOURCE_DOCKER_HUB},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result2["title"] == VERSION_SOURCE_DOCKER_HUB
|
||||
assert result2["data"] == {
|
||||
**DEFAULT_CONFIGURATION,
|
||||
CONF_SOURCE: HaVersionSource.CONTAINER,
|
||||
CONF_VERSION_SOURCE: VERSION_SOURCE_DOCKER_HUB,
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_advanced_form_pypi(hass: HomeAssistant) -> None:
|
||||
"""Show advanced form when pypi is selected."""
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER, "show_advanced_options": True},
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_VERSION_SOURCE: VERSION_SOURCE_PYPI},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "version_source"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "version_source"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.version.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_BETA: True}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == VERSION_SOURCE_PYPI
|
||||
assert result["data"] == {
|
||||
**DEFAULT_CONFIGURATION,
|
||||
CONF_BETA: True,
|
||||
CONF_SOURCE: HaVersionSource.PYPI,
|
||||
CONF_VERSION_SOURCE: VERSION_SOURCE_PYPI,
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_advanced_form_container(hass: HomeAssistant) -> None:
|
||||
"""Show advanced form when container source is selected."""
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER, "show_advanced_options": True},
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_VERSION_SOURCE: VERSION_SOURCE_DOCKER_HUB},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "version_source"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "version_source"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.version.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_IMAGE: "odroid-n2-homeassistant"}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == VERSION_SOURCE_DOCKER_HUB
|
||||
assert result["data"] == {
|
||||
**DEFAULT_CONFIGURATION,
|
||||
CONF_IMAGE: "odroid-n2-homeassistant",
|
||||
CONF_SOURCE: HaVersionSource.CONTAINER,
|
||||
CONF_VERSION_SOURCE: VERSION_SOURCE_DOCKER_HUB,
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_advanced_form_supervisor(hass: HomeAssistant) -> None:
|
||||
"""Show advanced form when docker source is selected."""
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER, "show_advanced_options": True},
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_VERSION_SOURCE: VERSION_SOURCE_VERSIONS},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "version_source"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["step_id"] == "version_source"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.version.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_CHANNEL: "Dev", CONF_IMAGE: "odroid-n2", CONF_BOARD: "ODROID N2"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["title"] == f"{VERSION_SOURCE_VERSIONS} Dev"
|
||||
assert result["data"] == {
|
||||
**DEFAULT_CONFIGURATION,
|
||||
CONF_IMAGE: "odroid-n2",
|
||||
CONF_BOARD: "ODROID N2",
|
||||
CONF_CHANNEL: HaVersionChannel.DEV,
|
||||
CONF_SOURCE: HaVersionSource.SUPERVISOR,
|
||||
CONF_VERSION_SOURCE: VERSION_SOURCE_VERSIONS,
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_import_existing(hass: HomeAssistant) -> None:
|
||||
"""Test importing existing configuration."""
|
||||
with patch(
|
||||
"homeassistant.components.version.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] == RESULT_TYPE_ABORT
|
||||
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
@ -1,130 +1,134 @@
|
||||
"""The test for the version sensor platform."""
|
||||
from datetime import timedelta
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
from pyhaversion import HaVersionSource, exceptions as pyhaversionexceptions
|
||||
from pyhaversion import HaVersionChannel, HaVersionSource
|
||||
from pyhaversion.exceptions import HaVersionException
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.version.sensor import HA_VERSION_SOURCES
|
||||
from homeassistant.components.version.const import (
|
||||
CONF_BETA,
|
||||
CONF_CHANNEL,
|
||||
CONF_IMAGE,
|
||||
CONF_VERSION_SOURCE,
|
||||
DEFAULT_NAME_LATEST,
|
||||
DOMAIN,
|
||||
VERSION_SOURCE_DOCKER_HUB,
|
||||
VERSION_SOURCE_VERSIONS,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_NAME, CONF_SOURCE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
from .common import (
|
||||
MOCK_VERSION,
|
||||
MOCK_VERSION_DATA,
|
||||
TEST_DEFAULT_IMPORT_CONFIG,
|
||||
mock_get_version_update,
|
||||
setup_version_integration,
|
||||
)
|
||||
|
||||
MOCK_VERSION = "10.0"
|
||||
|
||||
async def async_setup_sensor_wrapper(
|
||||
hass: HomeAssistant, config: dict[str, Any]
|
||||
) -> ConfigEntry:
|
||||
"""Set up the Version sensor platform."""
|
||||
await async_setup_component(hass, "persistent_notification", {})
|
||||
with patch(
|
||||
"pyhaversion.HaVersion.get_version",
|
||||
return_value=(MOCK_VERSION, MOCK_VERSION_DATA),
|
||||
):
|
||||
assert await async_setup_component(
|
||||
hass, "sensor", {"sensor": {"platform": DOMAIN, **config}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
config_entries = hass.config_entries.async_entries(DOMAIN)
|
||||
print(config_entries)
|
||||
config_entry = config_entries[-1]
|
||||
assert config_entry.source == "import"
|
||||
return config_entry
|
||||
|
||||
|
||||
async def test_version_sensor(hass: HomeAssistant):
|
||||
"""Test the Version sensor with different sources."""
|
||||
await setup_version_integration(hass)
|
||||
|
||||
state = hass.states.get("sensor.local_installation")
|
||||
assert state.state == MOCK_VERSION
|
||||
assert state.attributes["source"] == "local"
|
||||
assert state.attributes["channel"] == "stable"
|
||||
|
||||
|
||||
async def test_update(hass: HomeAssistant, caplog: pytest.LogCaptureFixture):
|
||||
"""Test updates."""
|
||||
await setup_version_integration(hass)
|
||||
assert hass.states.get("sensor.local_installation").state == MOCK_VERSION
|
||||
|
||||
await mock_get_version_update(hass, version="1970.1.1")
|
||||
assert hass.states.get("sensor.local_installation").state == "1970.1.1"
|
||||
|
||||
assert "Error fetching version data" not in caplog.text
|
||||
await mock_get_version_update(hass, side_effect=HaVersionException)
|
||||
assert hass.states.get("sensor.local_installation").state == "unavailable"
|
||||
assert "Error fetching version data" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"source,target_source,name",
|
||||
"yaml,converted",
|
||||
(
|
||||
(
|
||||
("local", HaVersionSource.LOCAL, "current_version"),
|
||||
("docker", HaVersionSource.CONTAINER, "latest_version"),
|
||||
("hassio", HaVersionSource.SUPERVISOR, "latest_version"),
|
||||
)
|
||||
+ tuple(
|
||||
(source, HaVersionSource(source), "latest_version")
|
||||
for source in HA_VERSION_SOURCES
|
||||
if source != HaVersionSource.LOCAL
|
||||
)
|
||||
{},
|
||||
TEST_DEFAULT_IMPORT_CONFIG,
|
||||
),
|
||||
(
|
||||
{CONF_NAME: "test"},
|
||||
{**TEST_DEFAULT_IMPORT_CONFIG, CONF_NAME: "test"},
|
||||
),
|
||||
(
|
||||
{CONF_SOURCE: "hassio", CONF_IMAGE: "odroid-n2"},
|
||||
{
|
||||
**TEST_DEFAULT_IMPORT_CONFIG,
|
||||
CONF_NAME: DEFAULT_NAME_LATEST,
|
||||
CONF_SOURCE: HaVersionSource.SUPERVISOR,
|
||||
CONF_VERSION_SOURCE: VERSION_SOURCE_VERSIONS,
|
||||
CONF_IMAGE: "odroid-n2",
|
||||
},
|
||||
),
|
||||
(
|
||||
{CONF_SOURCE: "docker"},
|
||||
{
|
||||
**TEST_DEFAULT_IMPORT_CONFIG,
|
||||
CONF_NAME: DEFAULT_NAME_LATEST,
|
||||
CONF_SOURCE: HaVersionSource.CONTAINER,
|
||||
CONF_VERSION_SOURCE: VERSION_SOURCE_DOCKER_HUB,
|
||||
},
|
||||
),
|
||||
(
|
||||
{CONF_BETA: True},
|
||||
{
|
||||
**TEST_DEFAULT_IMPORT_CONFIG,
|
||||
CONF_CHANNEL: HaVersionChannel.BETA,
|
||||
},
|
||||
),
|
||||
(
|
||||
{CONF_SOURCE: "container", CONF_IMAGE: "odroid-n2"},
|
||||
{
|
||||
**TEST_DEFAULT_IMPORT_CONFIG,
|
||||
CONF_NAME: DEFAULT_NAME_LATEST,
|
||||
CONF_SOURCE: HaVersionSource.CONTAINER,
|
||||
CONF_VERSION_SOURCE: VERSION_SOURCE_DOCKER_HUB,
|
||||
CONF_IMAGE: "odroid-n2-homeassistant",
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
async def test_version_source(hass, source, target_source, name):
|
||||
"""Test the Version sensor with different sources."""
|
||||
config = {
|
||||
"sensor": {"platform": "version", "source": source, "image": "qemux86-64"}
|
||||
}
|
||||
|
||||
with patch("homeassistant.components.version.sensor.HaVersion.get_version"), patch(
|
||||
"homeassistant.components.version.sensor.HaVersion.version", MOCK_VERSION
|
||||
):
|
||||
assert await async_setup_component(hass, "sensor", config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(f"sensor.{name}")
|
||||
assert state
|
||||
assert state.attributes["source"] == target_source
|
||||
|
||||
assert state.state == MOCK_VERSION
|
||||
|
||||
|
||||
async def test_version_fetch_exception(hass, caplog):
|
||||
"""Test fetch exception thrown during updates."""
|
||||
config = {"sensor": {"platform": "version"}}
|
||||
with patch(
|
||||
"homeassistant.components.version.sensor.HaVersion.get_version",
|
||||
side_effect=pyhaversionexceptions.HaVersionFetchException(
|
||||
"Fetch exception from pyhaversion"
|
||||
),
|
||||
):
|
||||
assert await async_setup_component(hass, "sensor", config)
|
||||
await hass.async_block_till_done()
|
||||
assert "Fetch exception from pyhaversion" in caplog.text
|
||||
|
||||
|
||||
async def test_version_parse_exception(hass, caplog):
|
||||
"""Test parse exception thrown during updates."""
|
||||
config = {"sensor": {"platform": "version"}}
|
||||
with patch(
|
||||
"homeassistant.components.version.sensor.HaVersion.get_version",
|
||||
side_effect=pyhaversionexceptions.HaVersionParseException,
|
||||
):
|
||||
assert await async_setup_component(hass, "sensor", config)
|
||||
await hass.async_block_till_done()
|
||||
assert "Could not parse data received for HaVersionSource.LOCAL" in caplog.text
|
||||
|
||||
|
||||
async def test_update(hass):
|
||||
"""Test updates."""
|
||||
config = {"sensor": {"platform": "version"}}
|
||||
|
||||
with patch("homeassistant.components.version.sensor.HaVersion.get_version"), patch(
|
||||
"homeassistant.components.version.sensor.HaVersion.version", MOCK_VERSION
|
||||
):
|
||||
assert await async_setup_component(hass, "sensor", config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.current_version")
|
||||
assert state
|
||||
assert state.state == MOCK_VERSION
|
||||
|
||||
with patch("homeassistant.components.version.sensor.HaVersion.get_version"), patch(
|
||||
"homeassistant.components.version.sensor.HaVersion.version", "1234"
|
||||
):
|
||||
|
||||
async_fire_time_changed(hass, dt.utcnow() + timedelta(minutes=5))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.current_version")
|
||||
assert state
|
||||
assert state.state == "1234"
|
||||
|
||||
|
||||
async def test_image_name_container(hass):
|
||||
"""Test the Version sensor with image name for container."""
|
||||
config = {
|
||||
"sensor": {"platform": "version", "source": "docker", "image": "qemux86-64"}
|
||||
}
|
||||
|
||||
with patch("homeassistant.components.version.sensor.HaVersion") as haversion:
|
||||
assert await async_setup_component(hass, "sensor", config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
constructor = haversion.call_args[1]
|
||||
assert constructor["source"] == "container"
|
||||
assert constructor["image"] == "qemux86-64-homeassistant"
|
||||
|
||||
|
||||
async def test_image_name_supervisor(hass):
|
||||
"""Test the Version sensor with image name for supervisor."""
|
||||
config = {
|
||||
"sensor": {"platform": "version", "source": "hassio", "image": "qemux86-64"}
|
||||
}
|
||||
|
||||
with patch("homeassistant.components.version.sensor.HaVersion") as haversion:
|
||||
assert await async_setup_component(hass, "sensor", config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
constructor = haversion.call_args[1]
|
||||
assert constructor["source"] == "supervisor"
|
||||
assert constructor["image"] == "qemux86-64"
|
||||
async def test_config_import(
|
||||
hass: HomeAssistant, yaml: dict[str, Any], converted: dict[str, Any]
|
||||
) -> None:
|
||||
"""Test importing YAML configuration."""
|
||||
config_entry = await async_setup_sensor_wrapper(hass, yaml)
|
||||
assert config_entry.data == converted
|
||||
|
Loading…
x
Reference in New Issue
Block a user