Skip to content

Commit

Permalink
2025.2.5 (#139012)
Browse files Browse the repository at this point in the history
  • Loading branch information
frenck authored Feb 21, 2025
2 parents 2d8a619 + 3d2ab3b commit cc79240
Show file tree
Hide file tree
Showing 60 changed files with 1,600 additions and 192 deletions.
2 changes: 1 addition & 1 deletion homeassistant/components/airgradient/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/airgradient",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["airgradient==0.9.1"],
"requirements": ["airgradient==0.9.2"],
"zeroconf": ["_airgradient._tcp.local."]
}
3 changes: 3 additions & 0 deletions homeassistant/components/backup/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
BackupAgentPlatformProtocol,
LocalBackupAgent,
)
from .config import BackupConfig, CreateBackupParametersDict
from .const import DATA_MANAGER, DOMAIN
from .http import async_register_http_views
from .manager import (
Expand Down Expand Up @@ -47,12 +48,14 @@
"BackupAgent",
"BackupAgentError",
"BackupAgentPlatformProtocol",
"BackupConfig",
"BackupManagerError",
"BackupNotFound",
"BackupPlatformProtocol",
"BackupReaderWriter",
"BackupReaderWriterError",
"CreateBackupEvent",
"CreateBackupParametersDict",
"CreateBackupStage",
"CreateBackupState",
"Folder",
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/backup/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ def load(self, stored_config: StoredBackupConfig) -> None:
self.data.retention.apply(self._manager)
self.data.schedule.apply(self._manager)

async def update(
@callback
def update(
self,
*,
agents: dict[str, AgentParametersDict] | UndefinedType = UNDEFINED,
Expand Down
49 changes: 48 additions & 1 deletion homeassistant/components/backup/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@
BackupAgentPlatformProtocol,
LocalBackupAgent,
)
from .config import BackupConfig, delete_backups_exceeding_configured_count
from .config import (
BackupConfig,
CreateBackupParametersDict,
delete_backups_exceeding_configured_count,
)
from .const import (
BUF_SIZE,
DATA_MANAGER,
Expand Down Expand Up @@ -282,6 +286,10 @@ async def async_resume_restore_progress_after_restart(
) -> None:
"""Get restore events after core restart."""

@abc.abstractmethod
async def async_validate_config(self, *, config: BackupConfig) -> None:
"""Validate backup config."""


class IncorrectPasswordError(BackupReaderWriterError):
"""Raised when the password is incorrect."""
Expand Down Expand Up @@ -333,6 +341,7 @@ async def async_setup(self) -> None:
self.config.load(stored["config"])
self.known_backups.load(stored["backups"])

await self._reader_writer.async_validate_config(config=self.config)
await self._reader_writer.async_resume_restore_progress_after_restart(
on_progress=self.async_on_backup_event
)
Expand Down Expand Up @@ -1832,6 +1841,44 @@ def _read_restore_file() -> json_util.JsonObjectType | None:
)
on_progress(IdleEvent())

async def async_validate_config(self, *, config: BackupConfig) -> None:
"""Validate backup config.
Update automatic backup settings to not include addons or folders and remove
hassio agents in case a backup created by supervisor was restored.
"""
create_backup = config.data.create_backup
if (
not create_backup.include_addons
and not create_backup.include_all_addons
and not create_backup.include_folders
and not any(a_id.startswith("hassio.") for a_id in create_backup.agent_ids)
):
LOGGER.debug("Backup settings don't need to be adjusted")
return

LOGGER.info(
"Adjusting backup settings to not include addons, folders or supervisor locations"
)
automatic_agents = [
agent_id
for agent_id in create_backup.agent_ids
if not agent_id.startswith("hassio.")
]
if (
self._local_agent_id not in automatic_agents
and "hassio.local" in create_backup.agent_ids
):
automatic_agents = [self._local_agent_id, *automatic_agents]
config.update(
create_backup=CreateBackupParametersDict(
agent_ids=automatic_agents,
include_addons=None,
include_all_addons=False,
include_folders=None,
)
)


def _generate_backup_id(date: str, name: str) -> str:
"""Generate a backup ID."""
Expand Down
9 changes: 8 additions & 1 deletion homeassistant/components/backup/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
STORE_DELAY_SAVE = 30
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
STORAGE_VERSION_MINOR = 3
STORAGE_VERSION_MINOR = 4


class StoredBackupData(TypedDict):
Expand Down Expand Up @@ -60,6 +60,13 @@ async def _async_migrate_func(
else:
data["config"]["schedule"]["days"] = [state]
data["config"]["schedule"]["recurrence"] = "custom_days"
if old_minor_version < 4:
# Workaround for a bug in frontend which incorrectly set days to 0
# instead of to None for unlimited retention.
if data["config"]["retention"]["copies"] == 0:
data["config"]["retention"]["copies"] = None
if data["config"]["retention"]["days"] == 0:
data["config"]["retention"]["days"] = None

# Note: We allow reading data with major version 2.
# Reject if major version is higher than 2.
Expand Down
7 changes: 5 additions & 2 deletions homeassistant/components/backup/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,15 @@ def read_backup(backup_path: Path) -> AgentBackup:
bool, homeassistant.get("exclude_database", False)
)

extra_metadata = cast(dict[str, bool | str], data.get("extra", {}))
date = extra_metadata.get("supervisor.backup_request_date", data["date"])

return AgentBackup(
addons=addons,
backup_id=cast(str, data["slug"]),
database_included=database_included,
date=cast(str, data["date"]),
extra_metadata=cast(dict[str, bool | str], data.get("extra", {})),
date=cast(str, date),
extra_metadata=extra_metadata,
folders=folders,
homeassistant_included=homeassistant_included,
homeassistant_version=homeassistant_version,
Expand Down
12 changes: 7 additions & 5 deletions homeassistant/components/backup/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ async def handle_config_info(
)


@callback
@websocket_api.require_admin
@websocket_api.websocket_command(
{
Expand All @@ -368,8 +369,10 @@ async def handle_config_info(
),
vol.Optional("retention"): vol.Schema(
{
vol.Optional("copies"): vol.Any(int, None),
vol.Optional("days"): vol.Any(int, None),
# Note: We can't use cv.positive_int because it allows 0 even
# though 0 is not positive.
vol.Optional("copies"): vol.Any(vol.All(int, vol.Range(min=1)), None),
vol.Optional("days"): vol.Any(vol.All(int, vol.Range(min=1)), None),
},
),
vol.Optional("schedule"): vol.Schema(
Expand All @@ -385,8 +388,7 @@ async def handle_config_info(
),
}
)
@websocket_api.async_response
async def handle_config_update(
def handle_config_update(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
Expand All @@ -396,7 +398,7 @@ async def handle_config_update(
changes = dict(msg)
changes.pop("id")
changes.pop("type")
await manager.config.update(**changes)
manager.config.update(**changes)
connection.send_result(msg["id"])


Expand Down
43 changes: 28 additions & 15 deletions homeassistant/components/cloud/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
from aiohttp import ClientError
from hass_nabucasa import Cloud, CloudError
from hass_nabucasa.api import CloudApiNonRetryableError
from hass_nabucasa.cloud_api import async_files_delete_file, async_files_list
from hass_nabucasa.cloud_api import (
FilesHandlerListEntry,
async_files_delete_file,
async_files_list,
)
from hass_nabucasa.files import FilesError, StorageType, calculate_b64md5

from homeassistant.components.backup import AgentBackup, BackupAgent, BackupAgentError
Expand Down Expand Up @@ -76,11 +80,6 @@ def __init__(self, hass: HomeAssistant, cloud: Cloud[CloudClient]) -> None:
self._cloud = cloud
self._hass = hass

@callback
def _get_backup_filename(self) -> str:
"""Return the backup filename."""
return f"{self._cloud.client.prefs.instance_id}.tar"

async def async_download_backup(
self,
backup_id: str,
Expand All @@ -91,13 +90,13 @@ async def async_download_backup(
:param backup_id: The ID of the backup that was returned in async_list_backups.
:return: An async iterator that yields bytes.
"""
if not await self.async_get_backup(backup_id):
if not (backup := await self._async_get_backup(backup_id)):
raise BackupAgentError("Backup not found")

try:
content = await self._cloud.files.download(
storage_type=StorageType.BACKUP,
filename=self._get_backup_filename(),
filename=backup["Key"],
)
except CloudError as err:
raise BackupAgentError(f"Failed to download backup: {err}") from err
Expand All @@ -124,7 +123,7 @@ async def async_upload_backup(
base64md5hash = await calculate_b64md5(open_stream, size)
except FilesError as err:
raise BackupAgentError(err) from err
filename = self._get_backup_filename()
filename = f"{self._cloud.client.prefs.instance_id}.tar"
metadata = backup.as_dict()

tries = 1
Expand Down Expand Up @@ -172,40 +171,54 @@ async def async_delete_backup(
:param backup_id: The ID of the backup that was returned in async_list_backups.
"""
if not await self.async_get_backup(backup_id):
if not (backup := await self._async_get_backup(backup_id)):
return

try:
await async_files_delete_file(
self._cloud,
storage_type=StorageType.BACKUP,
filename=self._get_backup_filename(),
filename=backup["Key"],
)
except (ClientError, CloudError) as err:
raise BackupAgentError("Failed to delete backup") from err

async def async_list_backups(self, **kwargs: Any) -> list[AgentBackup]:
"""List backups."""
backups = await self._async_list_backups()
return [AgentBackup.from_dict(backup["Metadata"]) for backup in backups]

async def _async_list_backups(self) -> list[FilesHandlerListEntry]:
"""List backups."""
try:
backups = await async_files_list(
self._cloud, storage_type=StorageType.BACKUP
)
_LOGGER.debug("Cloud backups: %s", backups)
except (ClientError, CloudError) as err:
raise BackupAgentError("Failed to list backups") from err

return [AgentBackup.from_dict(backup["Metadata"]) for backup in backups]
_LOGGER.debug("Cloud backups: %s", backups)
return backups

async def async_get_backup(
self,
backup_id: str,
**kwargs: Any,
) -> AgentBackup | None:
"""Return a backup."""
backups = await self.async_list_backups()
if not (backup := await self._async_get_backup(backup_id)):
return None
return AgentBackup.from_dict(backup["Metadata"])

async def _async_get_backup(
self,
backup_id: str,
) -> FilesHandlerListEntry | None:
"""Return a backup."""
backups = await self._async_list_backups()

for backup in backups:
if backup.backup_id == backup_id:
if backup["Metadata"]["backup_id"] == backup_id:
return backup

return None
2 changes: 1 addition & 1 deletion homeassistant/components/ecovacs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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==12.1.0"]
"requirements": ["py-sucks==0.9.10", "deebot-client==12.2.0"]
}
26 changes: 10 additions & 16 deletions homeassistant/components/fritzbox/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ def _add_entities(devices: set[str] | None = None) -> None:
class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
"""The thermostat class for FRITZ!SmartHome thermostats."""

_attr_max_temp = MAX_TEMPERATURE
_attr_min_temp = MIN_TEMPERATURE
_attr_precision = PRECISION_HALVES
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_translation_key = "thermostat"
Expand Down Expand Up @@ -135,11 +137,13 @@ def target_temperature(self) -> float:

async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
hvac_mode = kwargs.get(ATTR_HVAC_MODE)
if hvac_mode == HVACMode.OFF:
if (hvac_mode := kwargs.get(ATTR_HVAC_MODE)) is HVACMode.OFF:
await self.async_set_hvac_mode(hvac_mode)
elif target_temp is not None:
elif (target_temp := kwargs.get(ATTR_TEMPERATURE)) is not None:
if target_temp == OFF_API_TEMPERATURE:
target_temp = OFF_REPORT_SET_TEMPERATURE
elif target_temp == ON_API_TEMPERATURE:
target_temp = ON_REPORT_SET_TEMPERATURE
await self.hass.async_add_executor_job(
self.data.set_target_temperature, target_temp, True
)
Expand Down Expand Up @@ -169,12 +173,12 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
translation_domain=DOMAIN,
translation_key="change_hvac_while_active_mode",
)
if self.hvac_mode == hvac_mode:
if self.hvac_mode is hvac_mode:
LOGGER.debug(
"%s is already in requested hvac mode %s", self.name, hvac_mode
)
return
if hvac_mode == HVACMode.OFF:
if hvac_mode is HVACMode.OFF:
await self.async_set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE)
else:
if value_scheduled_preset(self.data) == PRESET_ECO:
Expand Down Expand Up @@ -208,16 +212,6 @@ async def async_set_preset_mode(self, preset_mode: str) -> None:
elif preset_mode == PRESET_ECO:
await self.async_set_temperature(temperature=self.data.eco_temperature)

@property
def min_temp(self) -> int:
"""Return the minimum temperature."""
return MIN_TEMPERATURE

@property
def max_temp(self) -> int:
"""Return the maximum temperature."""
return MAX_TEMPERATURE

@property
def extra_state_attributes(self) -> ClimateExtraAttributes:
"""Return the device specific state attributes."""
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/fritzbox/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["pyfritzhome"],
"requirements": ["pyfritzhome==0.6.14"],
"requirements": ["pyfritzhome==0.6.15"],
"ssdp": [
{
"st": "urn:schemas-upnp-org:device:fritzbox:1"
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/frontend/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20250214.0"]
"requirements": ["home-assistant-frontend==20250221.0"]
}
Loading

0 comments on commit cc79240

Please sign in to comment.