forked from home-assistant/core
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 645cdf5692 | |||
| e388e9f396 | |||
| 96c12fdd10 | |||
| e97a5f927c | |||
| 313309a7e0 | |||
| ebe62501d6 | |||
| c54369fe93 | |||
| c89bf6a9aa | |||
| 906bdda6fa | |||
| f3708549f0 | |||
| 3f34ddd74f | |||
| b19c44b4a5 | |||
| 0cc50bc7bc | |||
| e56dec2c8e | |||
| e797149a16 | |||
| c96f1c87a6 | |||
| 388c5807ea | |||
| 41c6eeedca | |||
| 829632b0af |
@@ -35,6 +35,9 @@ RUN \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Add go2rtc binary
|
||||
COPY --from=ghcr.io/alexxit/go2rtc:latest /usr/local/bin/go2rtc /bin/go2rtc
|
||||
|
||||
# Install uv
|
||||
RUN pip3 install uv
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ PREF_GOOGLE_REPORT_STATE = "google_report_state"
|
||||
PREF_ALEXA_ENTITY_CONFIGS = "alexa_entity_configs"
|
||||
PREF_ALEXA_REPORT_STATE = "alexa_report_state"
|
||||
PREF_DISABLE_2FA = "disable_2fa"
|
||||
PREF_ENABLE_BACKUP_SYNC = "backup_sync_enabled"
|
||||
PREF_INSTANCE_ID = "instance_id"
|
||||
PREF_SHOULD_EXPOSE = "should_expose"
|
||||
PREF_GOOGLE_LOCAL_WEBHOOK_ID = "google_local_webhook_id"
|
||||
|
||||
@@ -42,7 +42,6 @@ from .const import (
|
||||
PREF_ALEXA_REPORT_STATE,
|
||||
PREF_DISABLE_2FA,
|
||||
PREF_ENABLE_ALEXA,
|
||||
PREF_ENABLE_BACKUP_SYNC,
|
||||
PREF_ENABLE_CLOUD_ICE_SERVERS,
|
||||
PREF_ENABLE_GOOGLE,
|
||||
PREF_GOOGLE_REPORT_STATE,
|
||||
@@ -443,7 +442,6 @@ def validate_language_voice(value: tuple[str, str]) -> tuple[str, str]:
|
||||
vol.Required("type"): "cloud/update_prefs",
|
||||
vol.Optional(PREF_ALEXA_REPORT_STATE): bool,
|
||||
vol.Optional(PREF_ENABLE_ALEXA): bool,
|
||||
vol.Optional(PREF_ENABLE_BACKUP_SYNC): bool,
|
||||
vol.Optional(PREF_ENABLE_CLOUD_ICE_SERVERS): bool,
|
||||
vol.Optional(PREF_ENABLE_GOOGLE): bool,
|
||||
vol.Optional(PREF_GOOGLE_REPORT_STATE): bool,
|
||||
|
||||
@@ -32,7 +32,6 @@ from .const import (
|
||||
PREF_CLOUD_USER,
|
||||
PREF_CLOUDHOOKS,
|
||||
PREF_ENABLE_ALEXA,
|
||||
PREF_ENABLE_BACKUP_SYNC,
|
||||
PREF_ENABLE_CLOUD_ICE_SERVERS,
|
||||
PREF_ENABLE_GOOGLE,
|
||||
PREF_ENABLE_REMOTE,
|
||||
@@ -167,7 +166,6 @@ class CloudPreferences:
|
||||
alexa_enabled: bool | UndefinedType = UNDEFINED,
|
||||
alexa_report_state: bool | UndefinedType = UNDEFINED,
|
||||
alexa_settings_version: int | UndefinedType = UNDEFINED,
|
||||
backup_sync_enabled: bool | UndefinedType = UNDEFINED,
|
||||
cloud_ice_servers_enabled: bool | UndefinedType = UNDEFINED,
|
||||
cloud_user: str | UndefinedType = UNDEFINED,
|
||||
cloudhooks: dict[str, dict[str, str | bool]] | UndefinedType = UNDEFINED,
|
||||
@@ -193,7 +191,6 @@ class CloudPreferences:
|
||||
(PREF_CLOUD_USER, cloud_user),
|
||||
(PREF_CLOUDHOOKS, cloudhooks),
|
||||
(PREF_ENABLE_ALEXA, alexa_enabled),
|
||||
(PREF_ENABLE_BACKUP_SYNC, backup_sync_enabled),
|
||||
(PREF_ENABLE_CLOUD_ICE_SERVERS, cloud_ice_servers_enabled),
|
||||
(PREF_ENABLE_GOOGLE, google_enabled),
|
||||
(PREF_ENABLE_REMOTE, remote_enabled),
|
||||
@@ -245,7 +242,6 @@ class CloudPreferences:
|
||||
PREF_ALEXA_REPORT_STATE: self.alexa_report_state,
|
||||
PREF_CLOUDHOOKS: self.cloudhooks,
|
||||
PREF_ENABLE_ALEXA: self.alexa_enabled,
|
||||
PREF_ENABLE_BACKUP_SYNC: self.backup_sync_enabled,
|
||||
PREF_ENABLE_CLOUD_ICE_SERVERS: self.cloud_ice_servers_enabled,
|
||||
PREF_ENABLE_GOOGLE: self.google_enabled,
|
||||
PREF_ENABLE_REMOTE: self.remote_enabled,
|
||||
@@ -378,12 +374,6 @@ class CloudPreferences:
|
||||
)
|
||||
return cloud_ice_servers_enabled
|
||||
|
||||
@property
|
||||
def backup_sync_enabled(self) -> bool:
|
||||
"""Return if backup sync is enabled."""
|
||||
backup_sync_enabled: bool = self._prefs.get(PREF_ENABLE_BACKUP_SYNC, False)
|
||||
return backup_sync_enabled
|
||||
|
||||
async def get_cloud_user(self) -> str:
|
||||
"""Return ID of Home Assistant Cloud system user."""
|
||||
user = await self._load_cloud_user()
|
||||
@@ -429,7 +419,6 @@ class CloudPreferences:
|
||||
PREF_CLOUD_USER: None,
|
||||
PREF_CLOUDHOOKS: {},
|
||||
PREF_ENABLE_ALEXA: True,
|
||||
PREF_ENABLE_BACKUP_SYNC: True,
|
||||
PREF_ENABLE_GOOGLE: True,
|
||||
PREF_ENABLE_REMOTE: False,
|
||||
PREF_ENABLE_CLOUD_ICE_SERVERS: True,
|
||||
|
||||
@@ -168,7 +168,7 @@ class ElectricityMapsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
return self.async_create_entry(
|
||||
title=get_extra_name(data) or "CO2 Signal",
|
||||
title=get_extra_name(data) or "Electricity Maps",
|
||||
data=data,
|
||||
)
|
||||
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/doods",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pydoods"],
|
||||
"requirements": ["pydoods==1.0.2", "Pillow==10.4.0"]
|
||||
"requirements": ["pydoods==1.0.2", "Pillow==11.0.0"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
|
||||
"requirements": ["py-sucks==0.9.10", "deebot-client==8.4.0"]
|
||||
"requirements": ["py-sucks==0.9.10", "deebot-client==8.4.1"]
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ from .const import SIGNAL_THERMOSTAT_CONNECTED, SIGNAL_THERMOSTAT_DISCONNECTED
|
||||
from .models import Eq3Config, Eq3ConfigEntryData
|
||||
|
||||
PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.CLIMATE,
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
"""Platform for eq3 binary sensor entities."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from eq3btsmart.models import Status
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import Eq3ConfigEntry
|
||||
from .const import ENTITY_KEY_BATTERY, ENTITY_KEY_DST, ENTITY_KEY_WINDOW
|
||||
from .entity import Eq3Entity
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class Eq3BinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||
"""Entity description for eq3 binary sensors."""
|
||||
|
||||
value_func: Callable[[Status], bool]
|
||||
|
||||
|
||||
BINARY_SENSOR_ENTITY_DESCRIPTIONS = [
|
||||
Eq3BinarySensorEntityDescription(
|
||||
value_func=lambda status: status.is_low_battery,
|
||||
key=ENTITY_KEY_BATTERY,
|
||||
device_class=BinarySensorDeviceClass.BATTERY,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
Eq3BinarySensorEntityDescription(
|
||||
value_func=lambda status: status.is_window_open,
|
||||
key=ENTITY_KEY_WINDOW,
|
||||
device_class=BinarySensorDeviceClass.WINDOW,
|
||||
),
|
||||
Eq3BinarySensorEntityDescription(
|
||||
value_func=lambda status: status.is_dst,
|
||||
key=ENTITY_KEY_DST,
|
||||
translation_key=ENTITY_KEY_DST,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: Eq3ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the entry."""
|
||||
|
||||
async_add_entities(
|
||||
Eq3BinarySensorEntity(entry, entity_description)
|
||||
for entity_description in BINARY_SENSOR_ENTITY_DESCRIPTIONS
|
||||
)
|
||||
|
||||
|
||||
class Eq3BinarySensorEntity(Eq3Entity, BinarySensorEntity):
|
||||
"""Base class for eQ-3 binary sensor entities."""
|
||||
|
||||
entity_description: Eq3BinarySensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
entry: Eq3ConfigEntry,
|
||||
entity_description: Eq3BinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
|
||||
super().__init__(entry, entity_description.key)
|
||||
self.entity_description = entity_description
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return the state of the binary sensor."""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
assert self._thermostat.status is not None
|
||||
|
||||
return self.entity_description.value_func(self._thermostat.status)
|
||||
@@ -18,6 +18,10 @@ DOMAIN = "eq3btsmart"
|
||||
MANUFACTURER = "eQ-3 AG"
|
||||
DEVICE_MODEL = "CC-RT-BLE-EQ"
|
||||
|
||||
ENTITY_KEY_DST = "dst"
|
||||
ENTITY_KEY_BATTERY = "battery"
|
||||
ENTITY_KEY_WINDOW = "window"
|
||||
|
||||
GET_DEVICE_TIMEOUT = 5 # seconds
|
||||
|
||||
EQ_TO_HA_HVAC: dict[OperationMode, HVACMode] = {
|
||||
|
||||
@@ -24,7 +24,11 @@ class Eq3Entity(Entity):
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, entry: Eq3ConfigEntry, unique_id_key: str | None = None) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
entry: Eq3ConfigEntry,
|
||||
unique_id_key: str | None = None,
|
||||
) -> None:
|
||||
"""Initialize the eq3 entity."""
|
||||
|
||||
self._eq3_config = entry.runtime_data.eq3_config
|
||||
@@ -81,3 +85,9 @@ class Eq3Entity(Entity):
|
||||
|
||||
self._attr_available = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Whether the entity is available."""
|
||||
|
||||
return self._thermostat.status is not None and self._attr_available
|
||||
|
||||
@@ -18,5 +18,12 @@
|
||||
"error": {
|
||||
"invalid_mac_address": "Invalid MAC address"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"dst": {
|
||||
"name": "Daylight saving time"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/generic",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["av==13.1.0", "Pillow==10.4.0"]
|
||||
"requirements": ["av==13.1.0", "Pillow==11.0.0"]
|
||||
}
|
||||
|
||||
@@ -116,7 +116,6 @@ from .discovery import async_setup_discovery_view # noqa: F401
|
||||
from .handler import ( # noqa: F401
|
||||
HassIO,
|
||||
HassioAPIError,
|
||||
async_create_backup,
|
||||
async_get_green_settings,
|
||||
async_get_yellow_settings,
|
||||
async_reboot_host,
|
||||
|
||||
@@ -15,13 +15,14 @@ from aiohasupervisor.models import (
|
||||
AddonsOptions,
|
||||
AddonState as SupervisorAddonState,
|
||||
InstalledAddonComplete,
|
||||
PartialBackupOptions,
|
||||
StoreAddonUpdate,
|
||||
)
|
||||
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .handler import HassioAPIError, async_create_backup, get_supervisor_client
|
||||
from .handler import get_supervisor_client
|
||||
|
||||
type _FuncType[_T, **_P, _R] = Callable[Concatenate[_T, _P], Awaitable[_R]]
|
||||
type _ReturnFuncType[_T, **_P, _R] = Callable[
|
||||
@@ -31,18 +32,15 @@ type _ReturnFuncType[_T, **_P, _R] = Callable[
|
||||
|
||||
def api_error[_AddonManagerT: AddonManager, **_P, _R](
|
||||
error_message: str,
|
||||
*,
|
||||
expected_error_type: type[HassioAPIError | SupervisorError] | None = None,
|
||||
) -> Callable[
|
||||
[_FuncType[_AddonManagerT, _P, _R]], _ReturnFuncType[_AddonManagerT, _P, _R]
|
||||
]:
|
||||
"""Handle HassioAPIError and raise a specific AddonError."""
|
||||
error_type = expected_error_type or (HassioAPIError, SupervisorError)
|
||||
"""Handle SupervisorError and raise a specific AddonError."""
|
||||
|
||||
def handle_hassio_api_error(
|
||||
def handle_supervisor_api_error(
|
||||
func: _FuncType[_AddonManagerT, _P, _R],
|
||||
) -> _ReturnFuncType[_AddonManagerT, _P, _R]:
|
||||
"""Handle a HassioAPIError."""
|
||||
"""Handle a SupervisorError."""
|
||||
|
||||
@wraps(func)
|
||||
async def wrapper(
|
||||
@@ -51,7 +49,7 @@ def api_error[_AddonManagerT: AddonManager, **_P, _R](
|
||||
"""Wrap an add-on manager method."""
|
||||
try:
|
||||
return_value = await func(self, *args, **kwargs)
|
||||
except error_type as err:
|
||||
except SupervisorError as err:
|
||||
raise AddonError(
|
||||
f"{error_message.format(addon_name=self.addon_name)}: {err}"
|
||||
) from err
|
||||
@@ -60,7 +58,7 @@ def api_error[_AddonManagerT: AddonManager, **_P, _R](
|
||||
|
||||
return wrapper
|
||||
|
||||
return handle_hassio_api_error
|
||||
return handle_supervisor_api_error
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -123,10 +121,7 @@ class AddonManager:
|
||||
)
|
||||
)
|
||||
|
||||
@api_error(
|
||||
"Failed to get the {addon_name} add-on discovery info",
|
||||
expected_error_type=SupervisorError,
|
||||
)
|
||||
@api_error("Failed to get the {addon_name} add-on discovery info")
|
||||
async def async_get_addon_discovery_info(self) -> dict:
|
||||
"""Return add-on discovery info."""
|
||||
discovery_info = next(
|
||||
@@ -143,10 +138,7 @@ class AddonManager:
|
||||
|
||||
return discovery_info.config
|
||||
|
||||
@api_error(
|
||||
"Failed to get the {addon_name} add-on info",
|
||||
expected_error_type=SupervisorError,
|
||||
)
|
||||
@api_error("Failed to get the {addon_name} add-on info")
|
||||
async def async_get_addon_info(self) -> AddonInfo:
|
||||
"""Return and cache manager add-on info."""
|
||||
addon_store_info = await self._supervisor_client.store.addon_info(
|
||||
@@ -188,10 +180,7 @@ class AddonManager:
|
||||
|
||||
return addon_state
|
||||
|
||||
@api_error(
|
||||
"Failed to set the {addon_name} add-on options",
|
||||
expected_error_type=SupervisorError,
|
||||
)
|
||||
@api_error("Failed to set the {addon_name} add-on options")
|
||||
async def async_set_addon_options(self, config: dict) -> None:
|
||||
"""Set manager add-on options."""
|
||||
await self._supervisor_client.addons.set_addon_options(
|
||||
@@ -203,9 +192,7 @@ class AddonManager:
|
||||
if not addon_info.available:
|
||||
raise AddonError(f"{self.addon_name} add-on is not available")
|
||||
|
||||
@api_error(
|
||||
"Failed to install the {addon_name} add-on", expected_error_type=SupervisorError
|
||||
)
|
||||
@api_error("Failed to install the {addon_name} add-on")
|
||||
async def async_install_addon(self) -> None:
|
||||
"""Install the managed add-on."""
|
||||
addon_info = await self.async_get_addon_info()
|
||||
@@ -214,10 +201,7 @@ class AddonManager:
|
||||
|
||||
await self._supervisor_client.store.install_addon(self.addon_slug)
|
||||
|
||||
@api_error(
|
||||
"Failed to uninstall the {addon_name} add-on",
|
||||
expected_error_type=SupervisorError,
|
||||
)
|
||||
@api_error("Failed to uninstall the {addon_name} add-on")
|
||||
async def async_uninstall_addon(self) -> None:
|
||||
"""Uninstall the managed add-on."""
|
||||
await self._supervisor_client.addons.uninstall_addon(self.addon_slug)
|
||||
@@ -240,23 +224,17 @@ class AddonManager:
|
||||
self.addon_slug, StoreAddonUpdate(backup=False)
|
||||
)
|
||||
|
||||
@api_error(
|
||||
"Failed to start the {addon_name} add-on", expected_error_type=SupervisorError
|
||||
)
|
||||
@api_error("Failed to start the {addon_name} add-on")
|
||||
async def async_start_addon(self) -> None:
|
||||
"""Start the managed add-on."""
|
||||
await self._supervisor_client.addons.start_addon(self.addon_slug)
|
||||
|
||||
@api_error(
|
||||
"Failed to restart the {addon_name} add-on", expected_error_type=SupervisorError
|
||||
)
|
||||
@api_error("Failed to restart the {addon_name} add-on")
|
||||
async def async_restart_addon(self) -> None:
|
||||
"""Restart the managed add-on."""
|
||||
await self._supervisor_client.addons.restart_addon(self.addon_slug)
|
||||
|
||||
@api_error(
|
||||
"Failed to stop the {addon_name} add-on", expected_error_type=SupervisorError
|
||||
)
|
||||
@api_error("Failed to stop the {addon_name} add-on")
|
||||
async def async_stop_addon(self) -> None:
|
||||
"""Stop the managed add-on."""
|
||||
await self._supervisor_client.addons.stop_addon(self.addon_slug)
|
||||
@@ -268,10 +246,8 @@ class AddonManager:
|
||||
name = f"addon_{self.addon_slug}_{addon_info.version}"
|
||||
|
||||
self._logger.debug("Creating backup: %s", name)
|
||||
await async_create_backup(
|
||||
self._hass,
|
||||
{"name": name, "addons": [self.addon_slug]},
|
||||
partial=True,
|
||||
await self._supervisor_client.backups.partial_backup(
|
||||
PartialBackupOptions(name=name, addons={self.addon_slug})
|
||||
)
|
||||
|
||||
async def async_configure_addon(
|
||||
|
||||
@@ -76,21 +76,6 @@ async def async_update_diagnostics(hass: HomeAssistant, diagnostics: bool) -> bo
|
||||
return await hassio.update_diagnostics(diagnostics)
|
||||
|
||||
|
||||
@bind_hass
|
||||
@api_data
|
||||
async def async_create_backup(
|
||||
hass: HomeAssistant, payload: dict, partial: bool = False
|
||||
) -> dict:
|
||||
"""Create a full or partial backup.
|
||||
|
||||
The caller of the function should handle HassioAPIError.
|
||||
"""
|
||||
hassio: HassIO = hass.data[DOMAIN]
|
||||
backup_type = "partial" if partial else "full"
|
||||
command = f"/backups/new/{backup_type}"
|
||||
return await hassio.send_command(command, payload=payload, timeout=None)
|
||||
|
||||
|
||||
@api_data
|
||||
async def async_get_green_settings(hass: HomeAssistant) -> dict[str, bool]:
|
||||
"""Return settings specific to Home Assistant Green."""
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/image_upload",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["Pillow==10.4.0"]
|
||||
"requirements": ["Pillow==11.0.0"]
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import re
|
||||
from typing import cast
|
||||
|
||||
import pypck
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
@@ -19,17 +18,12 @@ from homeassistant.const import (
|
||||
CONF_DEVICES,
|
||||
CONF_DOMAIN,
|
||||
CONF_ENTITIES,
|
||||
CONF_HOST,
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_LIGHTS,
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_RESOURCE,
|
||||
CONF_SENSORS,
|
||||
CONF_SOURCE,
|
||||
CONF_SWITCHES,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
@@ -37,19 +31,13 @@ from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
BINSENSOR_PORTS,
|
||||
CONF_ACKNOWLEDGE,
|
||||
CONF_CLIMATES,
|
||||
CONF_CONNECTIONS,
|
||||
CONF_DIM_MODE,
|
||||
CONF_DOMAIN_DATA,
|
||||
CONF_HARDWARE_SERIAL,
|
||||
CONF_HARDWARE_TYPE,
|
||||
CONF_OUTPUT,
|
||||
CONF_SCENES,
|
||||
CONF_SK_NUM_TRIES,
|
||||
CONF_SOFTWARE_SERIAL,
|
||||
CONNECTION,
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
LED_PORTS,
|
||||
LOGICOP_PORTS,
|
||||
@@ -146,110 +134,6 @@ def generate_unique_id(
|
||||
return unique_id
|
||||
|
||||
|
||||
def import_lcn_config(lcn_config: ConfigType) -> list[ConfigType]:
|
||||
"""Convert lcn settings from configuration.yaml to config_entries data.
|
||||
|
||||
Create a list of config_entry data structures like:
|
||||
|
||||
"data": {
|
||||
"host": "pchk",
|
||||
"ip_address": "192.168.2.41",
|
||||
"port": 4114,
|
||||
"username": "lcn",
|
||||
"password": "lcn,
|
||||
"sk_num_tries: 0,
|
||||
"dim_mode: "STEPS200",
|
||||
"acknowledge": False,
|
||||
"devices": [
|
||||
{
|
||||
"address": (0, 7, False)
|
||||
"name": "",
|
||||
"hardware_serial": -1,
|
||||
"software_serial": -1,
|
||||
"hardware_type": -1
|
||||
}, ...
|
||||
],
|
||||
"entities": [
|
||||
{
|
||||
"address": (0, 7, False)
|
||||
"name": "Light_Output1",
|
||||
"resource": "output1",
|
||||
"domain": "light",
|
||||
"domain_data": {
|
||||
"output": "OUTPUT1",
|
||||
"dimmable": True,
|
||||
"transition": 5000.0
|
||||
}
|
||||
}, ...
|
||||
]
|
||||
}
|
||||
"""
|
||||
data = {}
|
||||
for connection in lcn_config[CONF_CONNECTIONS]:
|
||||
host = {
|
||||
CONF_HOST: connection[CONF_NAME],
|
||||
CONF_IP_ADDRESS: connection[CONF_HOST],
|
||||
CONF_PORT: connection[CONF_PORT],
|
||||
CONF_USERNAME: connection[CONF_USERNAME],
|
||||
CONF_PASSWORD: connection[CONF_PASSWORD],
|
||||
CONF_SK_NUM_TRIES: connection[CONF_SK_NUM_TRIES],
|
||||
CONF_DIM_MODE: connection[CONF_DIM_MODE],
|
||||
CONF_ACKNOWLEDGE: False,
|
||||
CONF_DEVICES: [],
|
||||
CONF_ENTITIES: [],
|
||||
}
|
||||
data[connection[CONF_NAME]] = host
|
||||
|
||||
for confkey, domain_config in lcn_config.items():
|
||||
if confkey == CONF_CONNECTIONS:
|
||||
continue
|
||||
domain = DOMAIN_LOOKUP[confkey]
|
||||
# loop over entities in configuration.yaml
|
||||
for domain_data in domain_config:
|
||||
# remove name and address from domain_data
|
||||
entity_name = domain_data.pop(CONF_NAME)
|
||||
address, host_name = domain_data.pop(CONF_ADDRESS)
|
||||
|
||||
if host_name is None:
|
||||
host_name = DEFAULT_NAME
|
||||
|
||||
# check if we have a new device config
|
||||
for device_config in data[host_name][CONF_DEVICES]:
|
||||
if address == device_config[CONF_ADDRESS]:
|
||||
break
|
||||
else: # create new device_config
|
||||
device_config = {
|
||||
CONF_ADDRESS: address,
|
||||
CONF_NAME: "",
|
||||
CONF_HARDWARE_SERIAL: -1,
|
||||
CONF_SOFTWARE_SERIAL: -1,
|
||||
CONF_HARDWARE_TYPE: -1,
|
||||
}
|
||||
|
||||
data[host_name][CONF_DEVICES].append(device_config)
|
||||
|
||||
# insert entity config
|
||||
resource = get_resource(domain, domain_data).lower()
|
||||
for entity_config in data[host_name][CONF_ENTITIES]:
|
||||
if (
|
||||
address == entity_config[CONF_ADDRESS]
|
||||
and resource == entity_config[CONF_RESOURCE]
|
||||
and domain == entity_config[CONF_DOMAIN]
|
||||
):
|
||||
break
|
||||
else: # create new entity_config
|
||||
entity_config = {
|
||||
CONF_ADDRESS: address,
|
||||
CONF_NAME: entity_name,
|
||||
CONF_RESOURCE: resource,
|
||||
CONF_DOMAIN: domain,
|
||||
CONF_DOMAIN_DATA: domain_data.copy(),
|
||||
}
|
||||
data[host_name][CONF_ENTITIES].append(entity_config)
|
||||
|
||||
return list(data.values())
|
||||
|
||||
|
||||
def purge_entity_registry(
|
||||
hass: HomeAssistant, entry_id: str, imported_entry_data: ConfigType
|
||||
) -> None:
|
||||
@@ -436,26 +320,6 @@ def get_device_config(
|
||||
return None
|
||||
|
||||
|
||||
def has_unique_host_names(hosts: list[ConfigType]) -> list[ConfigType]:
|
||||
"""Validate that all connection names are unique.
|
||||
|
||||
Use 'pchk' as default connection_name (or add a numeric suffix if
|
||||
pchk' is already in use.
|
||||
"""
|
||||
suffix = 0
|
||||
for host in hosts:
|
||||
if host.get(CONF_NAME) is None:
|
||||
if suffix == 0:
|
||||
host[CONF_NAME] = DEFAULT_NAME
|
||||
else:
|
||||
host[CONF_NAME] = f"{DEFAULT_NAME}{suffix:d}"
|
||||
suffix += 1
|
||||
|
||||
schema = vol.Schema(vol.Unique())
|
||||
schema([host.get(CONF_NAME) for host in hosts])
|
||||
return hosts
|
||||
|
||||
|
||||
def is_address(value: str) -> tuple[AddressType, str]:
|
||||
"""Validate the given address string.
|
||||
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/lcn",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pypck"],
|
||||
"requirements": ["pypck==0.7.24", "lcn-frontend==0.2.1"]
|
||||
"requirements": ["pypck==0.7.24", "lcn-frontend==0.2.2"]
|
||||
}
|
||||
|
||||
@@ -63,18 +63,6 @@
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"authentication_error": {
|
||||
"title": "Authentication failed.",
|
||||
"description": "Configuring LCN using YAML is being removed but there was an error importing your YAML configuration.\n\nEnsure username and password are correct.\n\nConsider removing the LCN YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
|
||||
},
|
||||
"license_error": {
|
||||
"title": "Maximum number of connections was reached.",
|
||||
"description": "Configuring LCN using YAML is being removed but there was an error importing your YAML configuration.\n\nEnsure sufficient PCHK licenses are registered and restart Home Assistant.\n\nConsider removing the LCN YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
|
||||
},
|
||||
"connection_refused": {
|
||||
"title": "Unable to connect to PCHK.",
|
||||
"description": "Configuring LCN using YAML is being removed but there was an error importing your YAML configuration.\n\nEnsure the connection (IP and port) to the LCN bus coupler is correct.\n\nConsider removing the LCN YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
|
||||
},
|
||||
"deprecated_regulatorlock_sensor": {
|
||||
"title": "Deprecated LCN regulator lock binary sensor",
|
||||
"description": "Your LCN regulator lock binary sensor entity `{entity}` is beeing used in automations or scripts. A regulator lock switch entity is available and should be used going forward.\n\nPlease adjust your automations or scripts to fix this issue."
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["linkplay"],
|
||||
"requirements": ["python-linkplay==0.0.18"],
|
||||
"requirements": ["python-linkplay==0.0.20"],
|
||||
"zeroconf": ["_linkplay._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/matrix",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["matrix_client"],
|
||||
"requirements": ["matrix-nio==0.25.2", "Pillow==10.4.0"]
|
||||
"requirements": ["matrix-nio==0.25.2", "Pillow==11.0.0"]
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ from homeassistant.components.media_player import (
|
||||
from homeassistant.components.websocket_api import ActiveConnection
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.frame import report
|
||||
from homeassistant.helpers.frame import report_usage
|
||||
from homeassistant.helpers.integration_platform import (
|
||||
async_process_integration_platforms,
|
||||
)
|
||||
@@ -156,7 +156,7 @@ async def async_resolve_media(
|
||||
raise Unresolvable("Media Source not loaded")
|
||||
|
||||
if target_media_player is UNDEFINED:
|
||||
report(
|
||||
report_usage(
|
||||
"calls media_source.async_resolve_media without passing an entity_id",
|
||||
exclude_integrations={DOMAIN},
|
||||
)
|
||||
|
||||
@@ -251,8 +251,8 @@ class PowerwallConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle reauth confirmation."""
|
||||
errors: dict[str, str] | None = {}
|
||||
description_placeholders: dict[str, str] = {}
|
||||
reauth_entry = self._get_reauth_entry()
|
||||
if user_input is not None:
|
||||
reauth_entry = self._get_reauth_entry()
|
||||
errors, _, description_placeholders = await self._async_try_connect(
|
||||
{CONF_IP_ADDRESS: reauth_entry.data[CONF_IP_ADDRESS], **user_input}
|
||||
)
|
||||
@@ -261,6 +261,10 @@ class PowerwallConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
reauth_entry, data_updates=user_input
|
||||
)
|
||||
|
||||
self.context["title_placeholders"] = {
|
||||
"name": reauth_entry.title,
|
||||
"ip_address": reauth_entry.data[CONF_IP_ADDRESS],
|
||||
}
|
||||
return self.async_show_form(
|
||||
step_id="reauth_confirm",
|
||||
data_schema=vol.Schema({vol.Optional(CONF_PASSWORD): str}),
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"name": "Camera Proxy",
|
||||
"codeowners": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/proxy",
|
||||
"requirements": ["Pillow==10.4.0"]
|
||||
"requirements": ["Pillow==11.0.0"]
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/qrcode",
|
||||
"iot_class": "calculated",
|
||||
"loggers": ["pyzbar"],
|
||||
"requirements": ["Pillow==10.4.0", "pyzbar==0.1.7"]
|
||||
"requirements": ["Pillow==11.0.0", "pyzbar==0.1.7"]
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ from sqlalchemy.pool import (
|
||||
StaticPool,
|
||||
)
|
||||
|
||||
from homeassistant.helpers.frame import report
|
||||
from homeassistant.helpers.frame import ReportBehavior, report_usage
|
||||
from homeassistant.util.loop import raise_for_blocking_call
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -108,14 +108,14 @@ class RecorderPool(SingletonThreadPool, NullPool):
|
||||
# raise_for_blocking_call will raise an exception
|
||||
|
||||
def _do_get_db_connection_protected(self) -> ConnectionPoolEntry:
|
||||
report(
|
||||
report_usage(
|
||||
(
|
||||
"accesses the database without the database executor; "
|
||||
f"{ADVISE_MSG} "
|
||||
"for faster database operations"
|
||||
),
|
||||
exclude_integrations={"recorder"},
|
||||
error_if_core=False,
|
||||
core_behavior=ReportBehavior.LOG,
|
||||
)
|
||||
return NullPool._create_connection(self) # noqa: SLF001
|
||||
|
||||
|
||||
@@ -134,7 +134,6 @@ STATISTIC_UNIT_TO_UNIT_CONVERTER: dict[str | None, type[BaseUnitConverter]] = {
|
||||
for unit in BloodGlugoseConcentrationConverter.VALID_UNITS
|
||||
},
|
||||
**{unit: ConductivityConverter for unit in ConductivityConverter.VALID_UNITS},
|
||||
**{unit: ConductivityConverter for unit in ConductivityConverter.VALID_UNITS},
|
||||
**{unit: DataRateConverter for unit in DataRateConverter.VALID_UNITS},
|
||||
**{unit: DistanceConverter for unit in DistanceConverter.VALID_UNITS},
|
||||
**{unit: DurationConverter for unit in DurationConverter.VALID_UNITS},
|
||||
|
||||
@@ -17,7 +17,7 @@ RUSSOUND_RIO_EXCEPTIONS = (
|
||||
)
|
||||
|
||||
|
||||
CONNECT_TIMEOUT = 5
|
||||
CONNECT_TIMEOUT = 15
|
||||
|
||||
MP_FEATURES_BY_FLAG = {
|
||||
FeatureFlag.COMMANDS_ZONE_MUTE_OFF_ON: MediaPlayerEntityFeature.VOLUME_MUTE
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aiorussound"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["aiorussound==4.0.5"]
|
||||
"requirements": ["aiorussound==4.1.0"]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
|
||||
from aiorussound import Controller
|
||||
from aiorussound.models import Source
|
||||
from aiorussound.models import PlayStatus, Source
|
||||
from aiorussound.rio import ZoneControlSurface
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
@@ -132,20 +132,18 @@ class RussoundZoneDevice(RussoundBaseEntity, MediaPlayerEntity):
|
||||
def state(self) -> MediaPlayerState | None:
|
||||
"""Return the state of the device."""
|
||||
status = self._zone.status
|
||||
mode = self._source.mode
|
||||
if status == "ON":
|
||||
if mode == "playing":
|
||||
return MediaPlayerState.PLAYING
|
||||
if mode == "paused":
|
||||
return MediaPlayerState.PAUSED
|
||||
if mode == "transitioning":
|
||||
return MediaPlayerState.BUFFERING
|
||||
if mode == "stopped":
|
||||
return MediaPlayerState.IDLE
|
||||
return MediaPlayerState.ON
|
||||
if status == "OFF":
|
||||
play_status = self._source.play_status
|
||||
if not status:
|
||||
return MediaPlayerState.OFF
|
||||
return None
|
||||
if play_status == PlayStatus.PLAYING:
|
||||
return MediaPlayerState.PLAYING
|
||||
if play_status == PlayStatus.PAUSED:
|
||||
return MediaPlayerState.PAUSED
|
||||
if play_status == PlayStatus.TRANSITIONING:
|
||||
return MediaPlayerState.BUFFERING
|
||||
if play_status == PlayStatus.STOPPED:
|
||||
return MediaPlayerState.IDLE
|
||||
return MediaPlayerState.ON
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
@@ -184,7 +182,7 @@ class RussoundZoneDevice(RussoundBaseEntity, MediaPlayerEntity):
|
||||
Value is returned based on a range (0..50).
|
||||
Therefore float divide by 50 to get to the required range.
|
||||
"""
|
||||
return float(self._zone.volume or "0") / 50.0
|
||||
return self._zone.volume / 50.0
|
||||
|
||||
@command
|
||||
async def async_turn_off(self) -> None:
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
"codeowners": ["@fabaff"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/seven_segments",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["Pillow==10.4.0"]
|
||||
"requirements": ["Pillow==11.0.0"]
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/sighthound",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["simplehound"],
|
||||
"requirements": ["Pillow==10.4.0", "simplehound==0.3"]
|
||||
"requirements": ["Pillow==11.0.0", "simplehound==0.3"]
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["spotipy"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["spotifyaio==0.8.7"],
|
||||
"requirements": ["spotifyaio==0.8.8"],
|
||||
"zeroconf": ["_spotify-connect._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -85,6 +85,9 @@ def make_device_data(
|
||||
"Meter",
|
||||
"MeterPlus",
|
||||
"WoIOSensor",
|
||||
"Hub 2",
|
||||
"MeterPro",
|
||||
"MeterPro(CO2)",
|
||||
]:
|
||||
devices_data.sensors.append(
|
||||
prepare_device(hass, api, device, coordinators_by_id)
|
||||
|
||||
@@ -9,7 +9,11 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import PERCENTAGE, UnitOfTemperature
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
@@ -21,6 +25,7 @@ from .entity import SwitchBotCloudEntity
|
||||
SENSOR_TYPE_TEMPERATURE = "temperature"
|
||||
SENSOR_TYPE_HUMIDITY = "humidity"
|
||||
SENSOR_TYPE_BATTERY = "battery"
|
||||
SENSOR_TYPE_CO2 = "CO2"
|
||||
|
||||
METER_PLUS_SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
@@ -43,6 +48,16 @@ METER_PLUS_SENSOR_DESCRIPTIONS = (
|
||||
),
|
||||
)
|
||||
|
||||
METER_PRO_CO2_SENSOR_DESCRIPTIONS = (
|
||||
*METER_PLUS_SENSOR_DESCRIPTIONS,
|
||||
SensorEntityDescription(
|
||||
key=SENSOR_TYPE_CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@@ -55,7 +70,11 @@ async def async_setup_entry(
|
||||
async_add_entities(
|
||||
SwitchBotCloudSensor(data.api, device, coordinator, description)
|
||||
for device, coordinator in data.devices.sensors
|
||||
for description in METER_PLUS_SENSOR_DESCRIPTIONS
|
||||
for description in (
|
||||
METER_PRO_CO2_SENSOR_DESCRIPTIONS
|
||||
if device.device_type == "MeterPro(CO2)"
|
||||
else METER_PLUS_SENSOR_DESCRIPTIONS
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
"tf-models-official==2.5.0",
|
||||
"pycocotools==2.0.6",
|
||||
"numpy==2.1.3",
|
||||
"Pillow==10.4.0"
|
||||
"Pillow==11.0.0"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -146,14 +146,21 @@ class DeviceListener(SharingDeviceListener):
|
||||
self.hass = hass
|
||||
self.manager = manager
|
||||
|
||||
def update_device(self, device: CustomerDevice) -> None:
|
||||
def update_device(
|
||||
self, device: CustomerDevice, updated_status_properties: list[str] | None
|
||||
) -> None:
|
||||
"""Update device status."""
|
||||
LOGGER.debug(
|
||||
"Received update for device %s: %s",
|
||||
"Received update for device %s: %s (updated properties: %s)",
|
||||
device.id,
|
||||
self.manager.device_map[device.id].status,
|
||||
updated_status_properties,
|
||||
)
|
||||
dispatcher_send(
|
||||
self.hass,
|
||||
f"{TUYA_HA_SIGNAL_UPDATE_ENTITY}_{device.id}",
|
||||
updated_status_properties,
|
||||
)
|
||||
dispatcher_send(self.hass, f"{TUYA_HA_SIGNAL_UPDATE_ENTITY}_{device.id}")
|
||||
|
||||
def add_device(self, device: CustomerDevice) -> None:
|
||||
"""Add device added listener."""
|
||||
|
||||
@@ -283,10 +283,15 @@ class TuyaEntity(Entity):
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"{TUYA_HA_SIGNAL_UPDATE_ENTITY}_{self.device.id}",
|
||||
self.async_write_ha_state,
|
||||
self._handle_state_update,
|
||||
)
|
||||
)
|
||||
|
||||
async def _handle_state_update(
|
||||
self, updated_status_properties: list[str] | None
|
||||
) -> None:
|
||||
self.async_write_ha_state()
|
||||
|
||||
def _send_command(self, commands: list[dict[str, Any]]) -> None:
|
||||
"""Send command to the device."""
|
||||
LOGGER.debug("Sending commands for device %s: %s", self.device.id, commands)
|
||||
|
||||
@@ -43,5 +43,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["tuya_iot"],
|
||||
"requirements": ["tuya-device-sharing-sdk==0.1.9"]
|
||||
"requirements": ["tuya-device-sharing-sdk==0.2.1"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"dependencies": ["application_credentials"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/weheat",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["weheat==2024.09.23"]
|
||||
"requirements": ["weheat==2024.11.02"]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Any
|
||||
|
||||
import zeroconf
|
||||
|
||||
from homeassistant.helpers.frame import report
|
||||
from homeassistant.helpers.frame import ReportBehavior, report_usage
|
||||
|
||||
from .models import HaZeroconf
|
||||
|
||||
@@ -16,14 +16,14 @@ def install_multiple_zeroconf_catcher(hass_zc: HaZeroconf) -> None:
|
||||
"""
|
||||
|
||||
def new_zeroconf_new(self: zeroconf.Zeroconf, *k: Any, **kw: Any) -> HaZeroconf:
|
||||
report(
|
||||
report_usage(
|
||||
(
|
||||
"attempted to create another Zeroconf instance. Please use the shared"
|
||||
" Zeroconf via await"
|
||||
" homeassistant.components.zeroconf.async_get_instance(hass)"
|
||||
),
|
||||
exclude_integrations={"zeroconf"},
|
||||
error_if_core=False,
|
||||
core_behavior=ReportBehavior.LOG,
|
||||
)
|
||||
return hass_zc
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ mutagen==1.47.0
|
||||
orjson==3.10.11
|
||||
packaging>=23.1
|
||||
paho-mqtt==1.6.1
|
||||
Pillow==10.4.0
|
||||
Pillow==11.0.0
|
||||
propcache==0.2.0
|
||||
psutil-home-assistant==0.0.1
|
||||
PyJWT==2.9.0
|
||||
@@ -127,7 +127,7 @@ backoff>=2.0
|
||||
|
||||
# Required to avoid breaking (#101042).
|
||||
# v2 has breaking changes (#99218).
|
||||
pydantic==1.10.18
|
||||
pydantic==1.10.19
|
||||
|
||||
# Required for Python 3.12.4 compatibility (#119223).
|
||||
mashumaro>=3.13.1
|
||||
|
||||
@@ -25,7 +25,6 @@ except ImportError:
|
||||
from propcache import cached_property
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.frame import report
|
||||
|
||||
from .const import SECRET_YAML
|
||||
from .objects import Input, NodeDictClass, NodeListClass, NodeStrClass
|
||||
@@ -144,37 +143,6 @@ class FastSafeLoader(FastestAvailableSafeLoader, _LoaderMixin):
|
||||
self.secrets = secrets
|
||||
|
||||
|
||||
class SafeLoader(FastSafeLoader):
|
||||
"""Provided for backwards compatibility. Logs when instantiated."""
|
||||
|
||||
def __init__(*args: Any, **kwargs: Any) -> None:
|
||||
"""Log a warning and call super."""
|
||||
SafeLoader.__report_deprecated()
|
||||
FastSafeLoader.__init__(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def add_constructor(cls, tag: str, constructor: Callable) -> None:
|
||||
"""Log a warning and call super."""
|
||||
SafeLoader.__report_deprecated()
|
||||
FastSafeLoader.add_constructor(tag, constructor)
|
||||
|
||||
@classmethod
|
||||
def add_multi_constructor(
|
||||
cls, tag_prefix: str, multi_constructor: Callable
|
||||
) -> None:
|
||||
"""Log a warning and call super."""
|
||||
SafeLoader.__report_deprecated()
|
||||
FastSafeLoader.add_multi_constructor(tag_prefix, multi_constructor)
|
||||
|
||||
@staticmethod
|
||||
def __report_deprecated() -> None:
|
||||
"""Log deprecation warning."""
|
||||
report(
|
||||
"uses deprecated 'SafeLoader' instead of 'FastSafeLoader', "
|
||||
"which will stop working in HA Core 2024.6,"
|
||||
)
|
||||
|
||||
|
||||
class PythonSafeLoader(yaml.SafeLoader, _LoaderMixin):
|
||||
"""Python safe loader."""
|
||||
|
||||
@@ -184,37 +152,6 @@ class PythonSafeLoader(yaml.SafeLoader, _LoaderMixin):
|
||||
self.secrets = secrets
|
||||
|
||||
|
||||
class SafeLineLoader(PythonSafeLoader):
|
||||
"""Provided for backwards compatibility. Logs when instantiated."""
|
||||
|
||||
def __init__(*args: Any, **kwargs: Any) -> None:
|
||||
"""Log a warning and call super."""
|
||||
SafeLineLoader.__report_deprecated()
|
||||
PythonSafeLoader.__init__(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def add_constructor(cls, tag: str, constructor: Callable) -> None:
|
||||
"""Log a warning and call super."""
|
||||
SafeLineLoader.__report_deprecated()
|
||||
PythonSafeLoader.add_constructor(tag, constructor)
|
||||
|
||||
@classmethod
|
||||
def add_multi_constructor(
|
||||
cls, tag_prefix: str, multi_constructor: Callable
|
||||
) -> None:
|
||||
"""Log a warning and call super."""
|
||||
SafeLineLoader.__report_deprecated()
|
||||
PythonSafeLoader.add_multi_constructor(tag_prefix, multi_constructor)
|
||||
|
||||
@staticmethod
|
||||
def __report_deprecated() -> None:
|
||||
"""Log deprecation warning."""
|
||||
report(
|
||||
"uses deprecated 'SafeLineLoader' instead of 'PythonSafeLoader', "
|
||||
"which will stop working in HA Core 2024.6,"
|
||||
)
|
||||
|
||||
|
||||
type LoaderType = FastSafeLoader | PythonSafeLoader
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ dependencies = [
|
||||
"PyJWT==2.9.0",
|
||||
# PyJWT has loose dependency. We want the latest one.
|
||||
"cryptography==43.0.1",
|
||||
"Pillow==10.4.0",
|
||||
"Pillow==11.0.0",
|
||||
"propcache==0.2.0",
|
||||
"pyOpenSSL==24.2.1",
|
||||
"orjson==3.10.11",
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ Jinja2==3.1.4
|
||||
lru-dict==1.3.0
|
||||
PyJWT==2.9.0
|
||||
cryptography==43.0.1
|
||||
Pillow==10.4.0
|
||||
Pillow==11.0.0
|
||||
propcache==0.2.0
|
||||
pyOpenSSL==24.2.1
|
||||
orjson==3.10.11
|
||||
|
||||
@@ -33,7 +33,7 @@ Mastodon.py==1.8.1
|
||||
# homeassistant.components.seven_segments
|
||||
# homeassistant.components.sighthound
|
||||
# homeassistant.components.tensorflow
|
||||
Pillow==10.4.0
|
||||
Pillow==11.0.0
|
||||
|
||||
# homeassistant.components.plex
|
||||
PlexAPI==4.15.16
|
||||
@@ -357,7 +357,7 @@ aioridwell==2024.01.0
|
||||
aioruckus==0.41
|
||||
|
||||
# homeassistant.components.russound_rio
|
||||
aiorussound==4.0.5
|
||||
aiorussound==4.1.0
|
||||
|
||||
# homeassistant.components.ruuvi_gateway
|
||||
aioruuvigateway==0.1.0
|
||||
@@ -735,7 +735,7 @@ debugpy==1.8.6
|
||||
# decora==0.6
|
||||
|
||||
# homeassistant.components.ecovacs
|
||||
deebot-client==8.4.0
|
||||
deebot-client==8.4.1
|
||||
|
||||
# homeassistant.components.ihc
|
||||
# homeassistant.components.namecheapdns
|
||||
@@ -1268,7 +1268,7 @@ lakeside==0.13
|
||||
laundrify-aio==1.2.2
|
||||
|
||||
# homeassistant.components.lcn
|
||||
lcn-frontend==0.2.1
|
||||
lcn-frontend==0.2.2
|
||||
|
||||
# homeassistant.components.ld2410_ble
|
||||
ld2410-ble==0.1.1
|
||||
@@ -2362,7 +2362,7 @@ python-juicenet==1.1.0
|
||||
python-kasa[speedups]==0.7.7
|
||||
|
||||
# homeassistant.components.linkplay
|
||||
python-linkplay==0.0.18
|
||||
python-linkplay==0.0.20
|
||||
|
||||
# homeassistant.components.lirc
|
||||
# python-lirc==1.2.3
|
||||
@@ -2713,7 +2713,7 @@ speak2mary==1.4.0
|
||||
speedtest-cli==2.1.3
|
||||
|
||||
# homeassistant.components.spotify
|
||||
spotifyaio==0.8.7
|
||||
spotifyaio==0.8.8
|
||||
|
||||
# homeassistant.components.sql
|
||||
sqlparse==0.5.0
|
||||
@@ -2873,7 +2873,7 @@ ttls==1.8.3
|
||||
ttn_client==1.2.0
|
||||
|
||||
# homeassistant.components.tuya
|
||||
tuya-device-sharing-sdk==0.1.9
|
||||
tuya-device-sharing-sdk==0.2.1
|
||||
|
||||
# homeassistant.components.twentemilieu
|
||||
twentemilieu==2.0.1
|
||||
@@ -2987,7 +2987,7 @@ webio-api==0.1.8
|
||||
webmin-xmlrpc==0.0.2
|
||||
|
||||
# homeassistant.components.weheat
|
||||
weheat==2024.09.23
|
||||
weheat==2024.11.02
|
||||
|
||||
# homeassistant.components.whirlpool
|
||||
whirlpool-sixth-sense==0.18.8
|
||||
|
||||
@@ -14,7 +14,7 @@ license-expression==30.4.0
|
||||
mock-open==1.4.0
|
||||
mypy-dev==1.14.0a2
|
||||
pre-commit==4.0.0
|
||||
pydantic==1.10.18
|
||||
pydantic==1.10.19
|
||||
pylint==3.3.1
|
||||
pylint-per-file-ignores==1.3.2
|
||||
pipdeptree==2.23.4
|
||||
|
||||
@@ -33,7 +33,7 @@ Mastodon.py==1.8.1
|
||||
# homeassistant.components.seven_segments
|
||||
# homeassistant.components.sighthound
|
||||
# homeassistant.components.tensorflow
|
||||
Pillow==10.4.0
|
||||
Pillow==11.0.0
|
||||
|
||||
# homeassistant.components.plex
|
||||
PlexAPI==4.15.16
|
||||
@@ -339,7 +339,7 @@ aioridwell==2024.01.0
|
||||
aioruckus==0.41
|
||||
|
||||
# homeassistant.components.russound_rio
|
||||
aiorussound==4.0.5
|
||||
aiorussound==4.1.0
|
||||
|
||||
# homeassistant.components.ruuvi_gateway
|
||||
aioruuvigateway==0.1.0
|
||||
@@ -625,7 +625,7 @@ dbus-fast==2.24.3
|
||||
debugpy==1.8.6
|
||||
|
||||
# homeassistant.components.ecovacs
|
||||
deebot-client==8.4.0
|
||||
deebot-client==8.4.1
|
||||
|
||||
# homeassistant.components.ihc
|
||||
# homeassistant.components.namecheapdns
|
||||
@@ -1064,7 +1064,7 @@ lacrosse-view==1.0.3
|
||||
laundrify-aio==1.2.2
|
||||
|
||||
# homeassistant.components.lcn
|
||||
lcn-frontend==0.2.1
|
||||
lcn-frontend==0.2.2
|
||||
|
||||
# homeassistant.components.ld2410_ble
|
||||
ld2410-ble==0.1.1
|
||||
@@ -1889,7 +1889,7 @@ python-juicenet==1.1.0
|
||||
python-kasa[speedups]==0.7.7
|
||||
|
||||
# homeassistant.components.linkplay
|
||||
python-linkplay==0.0.18
|
||||
python-linkplay==0.0.20
|
||||
|
||||
# homeassistant.components.matter
|
||||
python-matter-server==6.6.0
|
||||
@@ -2165,7 +2165,7 @@ speak2mary==1.4.0
|
||||
speedtest-cli==2.1.3
|
||||
|
||||
# homeassistant.components.spotify
|
||||
spotifyaio==0.8.7
|
||||
spotifyaio==0.8.8
|
||||
|
||||
# homeassistant.components.sql
|
||||
sqlparse==0.5.0
|
||||
@@ -2286,7 +2286,7 @@ ttls==1.8.3
|
||||
ttn_client==1.2.0
|
||||
|
||||
# homeassistant.components.tuya
|
||||
tuya-device-sharing-sdk==0.1.9
|
||||
tuya-device-sharing-sdk==0.2.1
|
||||
|
||||
# homeassistant.components.twentemilieu
|
||||
twentemilieu==2.0.1
|
||||
@@ -2382,7 +2382,7 @@ webio-api==0.1.8
|
||||
webmin-xmlrpc==0.0.2
|
||||
|
||||
# homeassistant.components.weheat
|
||||
weheat==2024.09.23
|
||||
weheat==2024.11.02
|
||||
|
||||
# homeassistant.components.whirlpool
|
||||
whirlpool-sixth-sense==0.18.8
|
||||
|
||||
@@ -160,7 +160,7 @@ backoff>=2.0
|
||||
|
||||
# Required to avoid breaking (#101042).
|
||||
# v2 has breaking changes (#99218).
|
||||
pydantic==1.10.18
|
||||
pydantic==1.10.19
|
||||
|
||||
# Required for Python 3.12.4 compatibility (#119223).
|
||||
mashumaro>=3.13.1
|
||||
|
||||
@@ -84,6 +84,7 @@ OSI_APPROVED_LICENSES_SPDX = {
|
||||
"LGPL-3.0-only",
|
||||
"LGPL-3.0-or-later",
|
||||
"MIT",
|
||||
"MIT-CMU",
|
||||
"MPL-1.1",
|
||||
"MPL-2.0",
|
||||
"PSF-2.0",
|
||||
|
||||
@@ -786,7 +786,6 @@ async def test_websocket_status(
|
||||
"remote_enabled": False,
|
||||
"cloud_ice_servers_enabled": True,
|
||||
"tts_default_voice": ["en-US", "JennyNeural"],
|
||||
"backup_sync_enabled": True,
|
||||
},
|
||||
"alexa_entities": {
|
||||
"include_domains": [],
|
||||
@@ -906,7 +905,6 @@ async def test_websocket_update_preferences(
|
||||
assert cloud.client.prefs.google_secure_devices_pin is None
|
||||
assert cloud.client.prefs.remote_allow_remote_enable is True
|
||||
assert cloud.client.prefs.cloud_ice_servers_enabled is True
|
||||
assert cloud.client.prefs.backup_sync_enabled is True
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
@@ -919,7 +917,6 @@ async def test_websocket_update_preferences(
|
||||
"tts_default_voice": ["en-GB", "RyanNeural"],
|
||||
"remote_allow_remote_enable": False,
|
||||
"cloud_ice_servers_enabled": False,
|
||||
"backup_sync_enabled": False,
|
||||
}
|
||||
)
|
||||
response = await client.receive_json()
|
||||
@@ -930,7 +927,6 @@ async def test_websocket_update_preferences(
|
||||
assert cloud.client.prefs.google_secure_devices_pin == "1234"
|
||||
assert cloud.client.prefs.remote_allow_remote_enable is False
|
||||
assert cloud.client.prefs.cloud_ice_servers_enabled is False
|
||||
assert cloud.client.prefs.backup_sync_enabled is False
|
||||
assert cloud.client.prefs.tts_default_voice == ("en-GB", "RyanNeural")
|
||||
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ async def test_form_home(hass: HomeAssistant) -> None:
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result2["title"] == "CO2 Signal"
|
||||
assert result2["title"] == "Electricity Maps"
|
||||
assert result2["data"] == {
|
||||
"api_key": "api_key",
|
||||
}
|
||||
@@ -185,7 +185,7 @@ async def test_form_error_handling(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "CO2 Signal"
|
||||
assert result["title"] == "Electricity Maps"
|
||||
assert result["data"] == {
|
||||
"api_key": "api_key",
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from aiohasupervisor.models import (
|
||||
Discovery,
|
||||
NewBackup,
|
||||
Repository,
|
||||
ResolutionInfo,
|
||||
StoreAddon,
|
||||
@@ -417,13 +418,22 @@ def uninstall_addon_fixture(supervisor_client: AsyncMock) -> AsyncMock:
|
||||
return supervisor_client.addons.uninstall_addon
|
||||
|
||||
|
||||
@pytest.fixture(name="create_backup")
|
||||
def create_backup_fixture() -> Generator[AsyncMock]:
|
||||
"""Mock create backup."""
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from .hassio.common import mock_create_backup
|
||||
@pytest.fixture(name="create_partial_backup")
|
||||
def create_partial_backup_fixture(supervisor_client: AsyncMock) -> AsyncMock:
|
||||
"""Mock create partial backup."""
|
||||
supervisor_client.backups.partial_backup.return_value = NewBackup(
|
||||
"job123", "backup123"
|
||||
)
|
||||
return supervisor_client.backups.partial_backup
|
||||
|
||||
yield from mock_create_backup()
|
||||
|
||||
@pytest.fixture(name="create_full_backup")
|
||||
def create_backup_fixture(supervisor_client: AsyncMock) -> AsyncMock:
|
||||
"""Mock create full backup."""
|
||||
supervisor_client.backups.full_backup.return_value = NewBackup(
|
||||
"job123", "backup123"
|
||||
)
|
||||
return supervisor_client.backups.full_backup
|
||||
|
||||
|
||||
@pytest.fixture(name="update_addon")
|
||||
@@ -505,6 +515,7 @@ def supervisor_client() -> Generator[AsyncMock]:
|
||||
"""Mock the supervisor client."""
|
||||
supervisor_client = AsyncMock()
|
||||
supervisor_client.addons = AsyncMock()
|
||||
supervisor_client.backups = AsyncMock()
|
||||
supervisor_client.discovery = AsyncMock()
|
||||
supervisor_client.homeassistant = AsyncMock()
|
||||
supervisor_client.os = AsyncMock()
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
from dataclasses import fields
|
||||
import logging
|
||||
from types import MethodType
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
from aiohasupervisor.models import (
|
||||
AddonsOptions,
|
||||
@@ -198,14 +197,6 @@ def mock_set_addon_options_side_effect(addon_options: dict[str, Any]) -> Any | N
|
||||
return set_addon_options
|
||||
|
||||
|
||||
def mock_create_backup() -> Generator[AsyncMock]:
|
||||
"""Mock create backup."""
|
||||
with patch(
|
||||
"homeassistant.components.hassio.addon_manager.async_create_backup"
|
||||
) as create_backup:
|
||||
yield create_backup
|
||||
|
||||
|
||||
def mock_addon_stats(supervisor_client: AsyncMock) -> AsyncMock:
|
||||
"""Mock addon stats."""
|
||||
supervisor_client.addons.addon_stats.return_value = addon_stats = Mock(
|
||||
|
||||
@@ -8,7 +8,7 @@ from unittest.mock import AsyncMock, call
|
||||
from uuid import uuid4
|
||||
|
||||
from aiohasupervisor import SupervisorError
|
||||
from aiohasupervisor.models import AddonsOptions, Discovery
|
||||
from aiohasupervisor.models import AddonsOptions, Discovery, PartialBackupOptions
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.hassio.addon_manager import (
|
||||
@@ -17,7 +17,6 @@ from homeassistant.components.hassio.addon_manager import (
|
||||
AddonManager,
|
||||
AddonState,
|
||||
)
|
||||
from homeassistant.components.hassio.handler import HassioAPIError
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
@@ -502,7 +501,7 @@ async def test_update_addon(
|
||||
addon_manager: AddonManager,
|
||||
addon_info: AsyncMock,
|
||||
addon_installed: AsyncMock,
|
||||
create_backup: AsyncMock,
|
||||
create_partial_backup: AsyncMock,
|
||||
update_addon: AsyncMock,
|
||||
) -> None:
|
||||
"""Test update addon."""
|
||||
@@ -511,9 +510,8 @@ async def test_update_addon(
|
||||
await addon_manager.async_update_addon()
|
||||
|
||||
assert addon_info.call_count == 2
|
||||
assert create_backup.call_count == 1
|
||||
assert create_backup.call_args == call(
|
||||
hass, {"name": "addon_test_addon_1.0.0", "addons": ["test_addon"]}, partial=True
|
||||
create_partial_backup.assert_called_once_with(
|
||||
PartialBackupOptions(name="addon_test_addon_1.0.0", addons={"test_addon"})
|
||||
)
|
||||
assert update_addon.call_count == 1
|
||||
|
||||
@@ -522,7 +520,7 @@ async def test_update_addon_no_update(
|
||||
addon_manager: AddonManager,
|
||||
addon_info: AsyncMock,
|
||||
addon_installed: AsyncMock,
|
||||
create_backup: AsyncMock,
|
||||
create_partial_backup: AsyncMock,
|
||||
update_addon: AsyncMock,
|
||||
) -> None:
|
||||
"""Test update addon without update available."""
|
||||
@@ -531,7 +529,7 @@ async def test_update_addon_no_update(
|
||||
await addon_manager.async_update_addon()
|
||||
|
||||
assert addon_info.call_count == 1
|
||||
assert create_backup.call_count == 0
|
||||
assert create_partial_backup.call_count == 0
|
||||
assert update_addon.call_count == 0
|
||||
|
||||
|
||||
@@ -540,7 +538,7 @@ async def test_update_addon_error(
|
||||
addon_manager: AddonManager,
|
||||
addon_info: AsyncMock,
|
||||
addon_installed: AsyncMock,
|
||||
create_backup: AsyncMock,
|
||||
create_partial_backup: AsyncMock,
|
||||
update_addon: AsyncMock,
|
||||
) -> None:
|
||||
"""Test update addon raises error."""
|
||||
@@ -553,9 +551,8 @@ async def test_update_addon_error(
|
||||
assert str(err.value) == "Failed to update the Test add-on: Boom"
|
||||
|
||||
assert addon_info.call_count == 2
|
||||
assert create_backup.call_count == 1
|
||||
assert create_backup.call_args == call(
|
||||
hass, {"name": "addon_test_addon_1.0.0", "addons": ["test_addon"]}, partial=True
|
||||
create_partial_backup.assert_called_once_with(
|
||||
PartialBackupOptions(name="addon_test_addon_1.0.0", addons={"test_addon"})
|
||||
)
|
||||
assert update_addon.call_count == 1
|
||||
|
||||
@@ -565,7 +562,7 @@ async def test_schedule_update_addon(
|
||||
addon_manager: AddonManager,
|
||||
addon_info: AsyncMock,
|
||||
addon_installed: AsyncMock,
|
||||
create_backup: AsyncMock,
|
||||
create_partial_backup: AsyncMock,
|
||||
update_addon: AsyncMock,
|
||||
) -> None:
|
||||
"""Test schedule update addon."""
|
||||
@@ -591,9 +588,8 @@ async def test_schedule_update_addon(
|
||||
|
||||
assert addon_manager.task_in_progress() is False
|
||||
assert addon_info.call_count == 3
|
||||
assert create_backup.call_count == 1
|
||||
assert create_backup.call_args == call(
|
||||
hass, {"name": "addon_test_addon_1.0.0", "addons": ["test_addon"]}, partial=True
|
||||
create_partial_backup.assert_called_once_with(
|
||||
PartialBackupOptions(name="addon_test_addon_1.0.0", addons={"test_addon"})
|
||||
)
|
||||
assert update_addon.call_count == 1
|
||||
|
||||
@@ -607,15 +603,15 @@ async def test_schedule_update_addon(
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"create_backup_error",
|
||||
"create_backup_calls",
|
||||
"create_partial_backup_error",
|
||||
"create_partial_backup_calls",
|
||||
"update_addon_error",
|
||||
"update_addon_calls",
|
||||
"error_message",
|
||||
),
|
||||
[
|
||||
(
|
||||
HassioAPIError("Boom"),
|
||||
SupervisorError("Boom"),
|
||||
1,
|
||||
None,
|
||||
0,
|
||||
@@ -633,17 +629,17 @@ async def test_schedule_update_addon(
|
||||
async def test_schedule_update_addon_error(
|
||||
addon_manager: AddonManager,
|
||||
addon_installed: AsyncMock,
|
||||
create_backup: AsyncMock,
|
||||
create_partial_backup: AsyncMock,
|
||||
update_addon: AsyncMock,
|
||||
create_backup_error: Exception | None,
|
||||
create_backup_calls: int,
|
||||
create_partial_backup_error: Exception | None,
|
||||
create_partial_backup_calls: int,
|
||||
update_addon_error: Exception | None,
|
||||
update_addon_calls: int,
|
||||
error_message: str,
|
||||
) -> None:
|
||||
"""Test schedule update addon raises error."""
|
||||
addon_installed.return_value.update_available = True
|
||||
create_backup.side_effect = create_backup_error
|
||||
create_partial_backup.side_effect = create_partial_backup_error
|
||||
update_addon.side_effect = update_addon_error
|
||||
|
||||
with pytest.raises(AddonError) as err:
|
||||
@@ -651,21 +647,21 @@ async def test_schedule_update_addon_error(
|
||||
|
||||
assert str(err.value) == error_message
|
||||
|
||||
assert create_backup.call_count == create_backup_calls
|
||||
assert create_partial_backup.call_count == create_partial_backup_calls
|
||||
assert update_addon.call_count == update_addon_calls
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"create_backup_error",
|
||||
"create_backup_calls",
|
||||
"create_partial_backup_error",
|
||||
"create_partial_backup_calls",
|
||||
"update_addon_error",
|
||||
"update_addon_calls",
|
||||
"error_log",
|
||||
),
|
||||
[
|
||||
(
|
||||
HassioAPIError("Boom"),
|
||||
SupervisorError("Boom"),
|
||||
1,
|
||||
None,
|
||||
0,
|
||||
@@ -683,10 +679,10 @@ async def test_schedule_update_addon_error(
|
||||
async def test_schedule_update_addon_logs_error(
|
||||
addon_manager: AddonManager,
|
||||
addon_installed: AsyncMock,
|
||||
create_backup: AsyncMock,
|
||||
create_partial_backup: AsyncMock,
|
||||
update_addon: AsyncMock,
|
||||
create_backup_error: Exception | None,
|
||||
create_backup_calls: int,
|
||||
create_partial_backup_error: Exception | None,
|
||||
create_partial_backup_calls: int,
|
||||
update_addon_error: Exception | None,
|
||||
update_addon_calls: int,
|
||||
error_log: str,
|
||||
@@ -694,13 +690,13 @@ async def test_schedule_update_addon_logs_error(
|
||||
) -> None:
|
||||
"""Test schedule update addon logs error."""
|
||||
addon_installed.return_value.update_available = True
|
||||
create_backup.side_effect = create_backup_error
|
||||
create_partial_backup.side_effect = create_partial_backup_error
|
||||
update_addon.side_effect = update_addon_error
|
||||
|
||||
await addon_manager.async_schedule_update_addon(catch_error=True)
|
||||
|
||||
assert error_log in caplog.text
|
||||
assert create_backup.call_count == create_backup_calls
|
||||
assert create_partial_backup.call_count == create_partial_backup_calls
|
||||
assert update_addon.call_count == update_addon_calls
|
||||
|
||||
|
||||
@@ -709,15 +705,14 @@ async def test_create_backup(
|
||||
addon_manager: AddonManager,
|
||||
addon_info: AsyncMock,
|
||||
addon_installed: AsyncMock,
|
||||
create_backup: AsyncMock,
|
||||
create_partial_backup: AsyncMock,
|
||||
) -> None:
|
||||
"""Test creating a backup of the addon."""
|
||||
await addon_manager.async_create_backup()
|
||||
|
||||
assert addon_info.call_count == 1
|
||||
assert create_backup.call_count == 1
|
||||
assert create_backup.call_args == call(
|
||||
hass, {"name": "addon_test_addon_1.0.0", "addons": ["test_addon"]}, partial=True
|
||||
create_partial_backup.assert_called_once_with(
|
||||
PartialBackupOptions(name="addon_test_addon_1.0.0", addons={"test_addon"})
|
||||
)
|
||||
|
||||
|
||||
@@ -726,10 +721,10 @@ async def test_create_backup_error(
|
||||
addon_manager: AddonManager,
|
||||
addon_info: AsyncMock,
|
||||
addon_installed: AsyncMock,
|
||||
create_backup: AsyncMock,
|
||||
create_partial_backup: AsyncMock,
|
||||
) -> None:
|
||||
"""Test creating a backup of the addon raises error."""
|
||||
create_backup.side_effect = HassioAPIError("Boom")
|
||||
create_partial_backup.side_effect = SupervisorError("Boom")
|
||||
|
||||
with pytest.raises(AddonError) as err:
|
||||
await addon_manager.async_create_backup()
|
||||
@@ -737,9 +732,8 @@ async def test_create_backup_error(
|
||||
assert str(err.value) == "Failed to create a backup of the Test add-on: Boom"
|
||||
|
||||
assert addon_info.call_count == 1
|
||||
assert create_backup.call_count == 1
|
||||
assert create_backup.call_args == call(
|
||||
hass, {"name": "addon_test_addon_1.0.0", "addons": ["test_addon"]}, partial=True
|
||||
create_partial_backup.assert_called_once_with(
|
||||
PartialBackupOptions(name="addon_test_addon_1.0.0", addons={"test_addon"})
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, MagicMock, call, patch
|
||||
|
||||
from aiohasupervisor import SupervisorError
|
||||
from aiohasupervisor.models import PartialBackupOptions
|
||||
from matter_server.client.exceptions import (
|
||||
CannotConnect,
|
||||
NotConnected,
|
||||
@@ -16,7 +17,6 @@ from matter_server.client.exceptions import (
|
||||
from matter_server.common.errors import MatterError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.hassio import HassioAPIError
|
||||
from homeassistant.components.matter.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState
|
||||
from homeassistant.const import STATE_UNAVAILABLE
|
||||
@@ -377,7 +377,7 @@ async def test_addon_info_failure(
|
||||
"update_calls",
|
||||
"backup_calls",
|
||||
"update_addon_side_effect",
|
||||
"create_backup_side_effect",
|
||||
"create_partial_backup_side_effect",
|
||||
"connect_side_effect",
|
||||
),
|
||||
[
|
||||
@@ -399,7 +399,7 @@ async def test_addon_info_failure(
|
||||
0,
|
||||
1,
|
||||
None,
|
||||
HassioAPIError("Boom"),
|
||||
SupervisorError("Boom"),
|
||||
ServerVersionTooOld("Invalid version"),
|
||||
),
|
||||
],
|
||||
@@ -411,7 +411,7 @@ async def test_update_addon(
|
||||
addon_info: AsyncMock,
|
||||
install_addon: AsyncMock,
|
||||
start_addon: AsyncMock,
|
||||
create_backup: AsyncMock,
|
||||
create_partial_backup: AsyncMock,
|
||||
update_addon: AsyncMock,
|
||||
matter_client: MagicMock,
|
||||
addon_version: str,
|
||||
@@ -419,13 +419,13 @@ async def test_update_addon(
|
||||
update_calls: int,
|
||||
backup_calls: int,
|
||||
update_addon_side_effect: Exception | None,
|
||||
create_backup_side_effect: Exception | None,
|
||||
create_partial_backup_side_effect: Exception | None,
|
||||
connect_side_effect: Exception,
|
||||
) -> None:
|
||||
"""Test update the Matter add-on during entry setup."""
|
||||
addon_info.return_value.version = addon_version
|
||||
addon_info.return_value.update_available = update_available
|
||||
create_backup.side_effect = create_backup_side_effect
|
||||
create_partial_backup.side_effect = create_partial_backup_side_effect
|
||||
update_addon.side_effect = update_addon_side_effect
|
||||
matter_client.connect.side_effect = connect_side_effect
|
||||
entry = MockConfigEntry(
|
||||
@@ -442,7 +442,7 @@ async def test_update_addon(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert create_backup.call_count == backup_calls
|
||||
assert create_partial_backup.call_count == backup_calls
|
||||
assert update_addon.call_count == update_calls
|
||||
|
||||
|
||||
@@ -548,7 +548,7 @@ async def test_remove_entry(
|
||||
hass: HomeAssistant,
|
||||
addon_installed: AsyncMock,
|
||||
stop_addon: AsyncMock,
|
||||
create_backup: AsyncMock,
|
||||
create_partial_backup: AsyncMock,
|
||||
uninstall_addon: AsyncMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
@@ -581,18 +581,17 @@ async def test_remove_entry(
|
||||
|
||||
assert stop_addon.call_count == 1
|
||||
assert stop_addon.call_args == call("core_matter_server")
|
||||
assert create_backup.call_count == 1
|
||||
assert create_backup.call_args == call(
|
||||
hass,
|
||||
{"name": "addon_core_matter_server_1.0.0", "addons": ["core_matter_server"]},
|
||||
partial=True,
|
||||
create_partial_backup.assert_called_once_with(
|
||||
PartialBackupOptions(
|
||||
name="addon_core_matter_server_1.0.0", addons={"core_matter_server"}
|
||||
)
|
||||
)
|
||||
assert uninstall_addon.call_count == 1
|
||||
assert uninstall_addon.call_args == call("core_matter_server")
|
||||
assert entry.state is ConfigEntryState.NOT_LOADED
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
||||
stop_addon.reset_mock()
|
||||
create_backup.reset_mock()
|
||||
create_partial_backup.reset_mock()
|
||||
uninstall_addon.reset_mock()
|
||||
|
||||
# test add-on stop failure
|
||||
@@ -604,38 +603,37 @@ async def test_remove_entry(
|
||||
|
||||
assert stop_addon.call_count == 1
|
||||
assert stop_addon.call_args == call("core_matter_server")
|
||||
assert create_backup.call_count == 0
|
||||
assert create_partial_backup.call_count == 0
|
||||
assert uninstall_addon.call_count == 0
|
||||
assert entry.state is ConfigEntryState.NOT_LOADED
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
||||
assert "Failed to stop the Matter Server add-on" in caplog.text
|
||||
stop_addon.side_effect = None
|
||||
stop_addon.reset_mock()
|
||||
create_backup.reset_mock()
|
||||
create_partial_backup.reset_mock()
|
||||
uninstall_addon.reset_mock()
|
||||
|
||||
# test create backup failure
|
||||
entry.add_to_hass(hass)
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
create_backup.side_effect = HassioAPIError()
|
||||
create_partial_backup.side_effect = SupervisorError()
|
||||
|
||||
await hass.config_entries.async_remove(entry.entry_id)
|
||||
|
||||
assert stop_addon.call_count == 1
|
||||
assert stop_addon.call_args == call("core_matter_server")
|
||||
assert create_backup.call_count == 1
|
||||
assert create_backup.call_args == call(
|
||||
hass,
|
||||
{"name": "addon_core_matter_server_1.0.0", "addons": ["core_matter_server"]},
|
||||
partial=True,
|
||||
create_partial_backup.assert_called_once_with(
|
||||
PartialBackupOptions(
|
||||
name="addon_core_matter_server_1.0.0", addons={"core_matter_server"}
|
||||
)
|
||||
)
|
||||
assert uninstall_addon.call_count == 0
|
||||
assert entry.state is ConfigEntryState.NOT_LOADED
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
||||
assert "Failed to create a backup of the Matter Server add-on" in caplog.text
|
||||
create_backup.side_effect = None
|
||||
create_partial_backup.side_effect = None
|
||||
stop_addon.reset_mock()
|
||||
create_backup.reset_mock()
|
||||
create_partial_backup.reset_mock()
|
||||
uninstall_addon.reset_mock()
|
||||
|
||||
# test add-on uninstall failure
|
||||
@@ -647,11 +645,10 @@ async def test_remove_entry(
|
||||
|
||||
assert stop_addon.call_count == 1
|
||||
assert stop_addon.call_args == call("core_matter_server")
|
||||
assert create_backup.call_count == 1
|
||||
assert create_backup.call_args == call(
|
||||
hass,
|
||||
{"name": "addon_core_matter_server_1.0.0", "addons": ["core_matter_server"]},
|
||||
partial=True,
|
||||
create_partial_backup.assert_called_once_with(
|
||||
PartialBackupOptions(
|
||||
name="addon_core_matter_server_1.0.0", addons={"core_matter_server"}
|
||||
)
|
||||
)
|
||||
assert uninstall_addon.call_count == 1
|
||||
assert uninstall_addon.call_args == call("core_matter_server")
|
||||
|
||||
@@ -339,6 +339,11 @@ async def test_form_reauth(hass: HomeAssistant) -> None:
|
||||
result = await entry.start_reauth_flow(hass)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {}
|
||||
flow = hass.config_entries.flow.async_get(result["flow_id"])
|
||||
assert flow["context"]["title_placeholders"] == {
|
||||
"ip_address": VALID_CONFIG[CONF_IP_ADDRESS],
|
||||
"name": entry.title,
|
||||
}
|
||||
|
||||
mock_powerwall = await _mock_powerwall_site_name(hass, "My site")
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from aiorussound.models import CallbackType
|
||||
from aiorussound.models import CallbackType, PlayStatus
|
||||
import pytest
|
||||
|
||||
from homeassistant.const import (
|
||||
@@ -28,29 +28,29 @@ async def mock_state_update(client: AsyncMock) -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("zone_status", "source_mode", "media_player_state"),
|
||||
("zone_status", "source_play_status", "media_player_state"),
|
||||
[
|
||||
("ON", None, STATE_ON),
|
||||
("ON", "playing", STATE_PLAYING),
|
||||
("ON", "paused", STATE_PAUSED),
|
||||
("ON", "transitioning", STATE_BUFFERING),
|
||||
("ON", "stopped", STATE_IDLE),
|
||||
("OFF", None, STATE_OFF),
|
||||
("OFF", "stopped", STATE_OFF),
|
||||
(True, None, STATE_ON),
|
||||
(True, PlayStatus.PLAYING, STATE_PLAYING),
|
||||
(True, PlayStatus.PAUSED, STATE_PAUSED),
|
||||
(True, PlayStatus.TRANSITIONING, STATE_BUFFERING),
|
||||
(True, PlayStatus.STOPPED, STATE_IDLE),
|
||||
(False, None, STATE_OFF),
|
||||
(False, PlayStatus.STOPPED, STATE_OFF),
|
||||
],
|
||||
)
|
||||
async def test_entity_state(
|
||||
hass: HomeAssistant,
|
||||
mock_russound_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
zone_status: str,
|
||||
source_mode: str | None,
|
||||
zone_status: bool,
|
||||
source_play_status: PlayStatus | None,
|
||||
media_player_state: str,
|
||||
) -> None:
|
||||
"""Test media player state."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
mock_russound_client.controllers[1].zones[1].status = zone_status
|
||||
mock_russound_client.sources[1].mode = source_mode
|
||||
mock_russound_client.sources[1].play_status = source_play_status
|
||||
await mock_state_update(mock_russound_client)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -50,6 +50,18 @@ async def test_setup_entry_success(
|
||||
remoteType="DIY Plug",
|
||||
hubDeviceId="test-hub-id",
|
||||
),
|
||||
Remote(
|
||||
deviceId="meter-pro-1",
|
||||
deviceName="meter-pro-name-1",
|
||||
deviceType="MeterPro(CO2)",
|
||||
hubDeviceId="test-hub-id",
|
||||
),
|
||||
Remote(
|
||||
deviceId="hub2-1",
|
||||
deviceName="hub2-name-1",
|
||||
deviceType="Hub 2",
|
||||
hubDeviceId="test-hub-id",
|
||||
),
|
||||
]
|
||||
mock_get_status.return_value = {"power": PowerState.ON.value}
|
||||
entry = configure_integration(hass)
|
||||
|
||||
@@ -6,7 +6,7 @@ import logging
|
||||
from unittest.mock import AsyncMock, call, patch
|
||||
|
||||
from aiohasupervisor import SupervisorError
|
||||
from aiohasupervisor.models import AddonsOptions
|
||||
from aiohasupervisor.models import AddonsOptions, PartialBackupOptions
|
||||
import pytest
|
||||
from zwave_js_server.client import Client
|
||||
from zwave_js_server.event import Event
|
||||
@@ -14,7 +14,6 @@ from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVers
|
||||
from zwave_js_server.model.node import Node
|
||||
from zwave_js_server.model.version import VersionInfo
|
||||
|
||||
from homeassistant.components.hassio import HassioAPIError
|
||||
from homeassistant.components.logger import DOMAIN as LOGGER_DOMAIN, SERVICE_SET_LEVEL
|
||||
from homeassistant.components.persistent_notification import async_dismiss
|
||||
from homeassistant.components.zwave_js import DOMAIN
|
||||
@@ -743,13 +742,13 @@ async def test_addon_options_changed(
|
||||
"update_calls",
|
||||
"backup_calls",
|
||||
"update_addon_side_effect",
|
||||
"create_backup_side_effect",
|
||||
"create_partial_backup_side_effect",
|
||||
),
|
||||
[
|
||||
("1.0.0", True, 1, 1, None, None),
|
||||
("1.0.0", False, 0, 0, None, None),
|
||||
("1.0.0", True, 1, 1, SupervisorError("Boom"), None),
|
||||
("1.0.0", True, 0, 1, None, HassioAPIError("Boom")),
|
||||
("1.0.0", True, 0, 1, None, SupervisorError("Boom")),
|
||||
],
|
||||
)
|
||||
async def test_update_addon(
|
||||
@@ -758,7 +757,7 @@ async def test_update_addon(
|
||||
addon_info,
|
||||
addon_installed,
|
||||
addon_running,
|
||||
create_backup,
|
||||
create_partial_backup,
|
||||
update_addon,
|
||||
addon_options,
|
||||
addon_version,
|
||||
@@ -766,7 +765,7 @@ async def test_update_addon(
|
||||
update_calls,
|
||||
backup_calls,
|
||||
update_addon_side_effect,
|
||||
create_backup_side_effect,
|
||||
create_partial_backup_side_effect,
|
||||
version_state,
|
||||
) -> None:
|
||||
"""Test update the Z-Wave JS add-on during entry setup."""
|
||||
@@ -776,7 +775,7 @@ async def test_update_addon(
|
||||
addon_options["network_key"] = network_key
|
||||
addon_info.return_value.version = addon_version
|
||||
addon_info.return_value.update_available = update_available
|
||||
create_backup.side_effect = create_backup_side_effect
|
||||
create_partial_backup.side_effect = create_partial_backup_side_effect
|
||||
update_addon.side_effect = update_addon_side_effect
|
||||
client.connect.side_effect = InvalidServerVersion(
|
||||
VersionInfo("a", "b", 1, 1, 1), 1, "Invalid version"
|
||||
@@ -797,7 +796,7 @@ async def test_update_addon(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert create_backup.call_count == backup_calls
|
||||
assert create_partial_backup.call_count == backup_calls
|
||||
assert update_addon.call_count == update_calls
|
||||
|
||||
|
||||
@@ -897,7 +896,7 @@ async def test_remove_entry(
|
||||
hass: HomeAssistant,
|
||||
addon_installed,
|
||||
stop_addon,
|
||||
create_backup,
|
||||
create_partial_backup,
|
||||
uninstall_addon,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
@@ -930,18 +929,15 @@ async def test_remove_entry(
|
||||
|
||||
assert stop_addon.call_count == 1
|
||||
assert stop_addon.call_args == call("core_zwave_js")
|
||||
assert create_backup.call_count == 1
|
||||
assert create_backup.call_args == call(
|
||||
hass,
|
||||
{"name": "addon_core_zwave_js_1.0.0", "addons": ["core_zwave_js"]},
|
||||
partial=True,
|
||||
create_partial_backup.assert_called_once_with(
|
||||
PartialBackupOptions(name="addon_core_zwave_js_1.0.0", addons={"core_zwave_js"})
|
||||
)
|
||||
assert uninstall_addon.call_count == 1
|
||||
assert uninstall_addon.call_args == call("core_zwave_js")
|
||||
assert entry.state is ConfigEntryState.NOT_LOADED
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
||||
stop_addon.reset_mock()
|
||||
create_backup.reset_mock()
|
||||
create_partial_backup.reset_mock()
|
||||
uninstall_addon.reset_mock()
|
||||
|
||||
# test add-on stop failure
|
||||
@@ -953,38 +949,35 @@ async def test_remove_entry(
|
||||
|
||||
assert stop_addon.call_count == 1
|
||||
assert stop_addon.call_args == call("core_zwave_js")
|
||||
assert create_backup.call_count == 0
|
||||
assert create_partial_backup.call_count == 0
|
||||
assert uninstall_addon.call_count == 0
|
||||
assert entry.state is ConfigEntryState.NOT_LOADED
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
||||
assert "Failed to stop the Z-Wave JS add-on" in caplog.text
|
||||
stop_addon.side_effect = None
|
||||
stop_addon.reset_mock()
|
||||
create_backup.reset_mock()
|
||||
create_partial_backup.reset_mock()
|
||||
uninstall_addon.reset_mock()
|
||||
|
||||
# test create backup failure
|
||||
entry.add_to_hass(hass)
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
create_backup.side_effect = HassioAPIError()
|
||||
create_partial_backup.side_effect = SupervisorError()
|
||||
|
||||
await hass.config_entries.async_remove(entry.entry_id)
|
||||
|
||||
assert stop_addon.call_count == 1
|
||||
assert stop_addon.call_args == call("core_zwave_js")
|
||||
assert create_backup.call_count == 1
|
||||
assert create_backup.call_args == call(
|
||||
hass,
|
||||
{"name": "addon_core_zwave_js_1.0.0", "addons": ["core_zwave_js"]},
|
||||
partial=True,
|
||||
create_partial_backup.assert_called_once_with(
|
||||
PartialBackupOptions(name="addon_core_zwave_js_1.0.0", addons={"core_zwave_js"})
|
||||
)
|
||||
assert uninstall_addon.call_count == 0
|
||||
assert entry.state is ConfigEntryState.NOT_LOADED
|
||||
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
|
||||
assert "Failed to create a backup of the Z-Wave JS add-on" in caplog.text
|
||||
create_backup.side_effect = None
|
||||
create_partial_backup.side_effect = None
|
||||
stop_addon.reset_mock()
|
||||
create_backup.reset_mock()
|
||||
create_partial_backup.reset_mock()
|
||||
uninstall_addon.reset_mock()
|
||||
|
||||
# test add-on uninstall failure
|
||||
@@ -996,11 +989,8 @@ async def test_remove_entry(
|
||||
|
||||
assert stop_addon.call_count == 1
|
||||
assert stop_addon.call_args == call("core_zwave_js")
|
||||
assert create_backup.call_count == 1
|
||||
assert create_backup.call_args == call(
|
||||
hass,
|
||||
{"name": "addon_core_zwave_js_1.0.0", "addons": ["core_zwave_js"]},
|
||||
partial=True,
|
||||
create_partial_backup.assert_called_once_with(
|
||||
PartialBackupOptions(name="addon_core_zwave_js_1.0.0", addons={"core_zwave_js"})
|
||||
)
|
||||
assert uninstall_addon.call_count == 1
|
||||
assert uninstall_addon.call_args == call("core_zwave_js")
|
||||
|
||||
@@ -494,31 +494,6 @@ def mock_integration_frame() -> Generator[Mock]:
|
||||
yield correct_frame
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("loader_class", "message"),
|
||||
[
|
||||
(yaml.loader.SafeLoader, "'SafeLoader' instead of 'FastSafeLoader'"),
|
||||
(
|
||||
yaml.loader.SafeLineLoader,
|
||||
"'SafeLineLoader' instead of 'PythonSafeLoader'",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_integration_frame")
|
||||
async def test_deprecated_loaders(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
loader_class: type,
|
||||
message: str,
|
||||
) -> None:
|
||||
"""Test instantiating the deprecated yaml loaders logs a warning."""
|
||||
with (
|
||||
pytest.raises(TypeError),
|
||||
patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set()),
|
||||
):
|
||||
loader_class()
|
||||
assert (f"Detected that integration 'hue' uses deprecated {message}") in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("try_both_loaders")
|
||||
def test_string_annotated() -> None:
|
||||
"""Test strings are annotated with file + line."""
|
||||
|
||||
Reference in New Issue
Block a user