Skip to content

Commit

Permalink
Merge branch 'dev' into cloud/define-default-tts-voices
Browse files Browse the repository at this point in the history
  • Loading branch information
ludeeus authored Feb 4, 2025
2 parents 378d111 + 9a56588 commit be05f4f
Show file tree
Hide file tree
Showing 35 changed files with 555 additions and 1,108 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repos:
- id: ruff-format
files: ^((homeassistant|pylint|script|tests)/.+)?[^/]+\.(py|pyi)$
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
rev: v2.4.1
hooks:
- id: codespell
args:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/apple_tv/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def device_identifier(self) -> str | None:
unique_id for said entry. When a new (zeroconf) service or device is
discovered, the identifier is first used to look up if it belongs to an
existing config entry. If that's the case, the unique_id from that entry is
re-used, otherwise the newly discovered identifier is used instead.
reused, otherwise the newly discovered identifier is used instead.
"""
assert self.atv
all_identifiers = set(self.atv.all_identifiers)
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/backup/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
BackupReaderWriterError,
CoreBackupReaderWriter,
CreateBackupEvent,
CreateBackupStage,
CreateBackupState,
IdleEvent,
IncorrectPasswordError,
ManagerBackup,
Expand All @@ -49,6 +51,8 @@
"BackupReaderWriter",
"BackupReaderWriterError",
"CreateBackupEvent",
"CreateBackupStage",
"CreateBackupState",
"Folder",
"IdleEvent",
"IncorrectPasswordError",
Expand Down
56 changes: 46 additions & 10 deletions homeassistant/components/backup/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import asyncio
from collections.abc import AsyncIterator, Callable, Coroutine
from concurrent.futures import CancelledError, Future
import copy
from dataclasses import dataclass, replace
from io import BytesIO
Expand All @@ -12,6 +13,7 @@
from pathlib import Path, PurePath
from queue import SimpleQueue
import tarfile
import threading
from typing import IO, Any, Self, cast

import aiohttp
Expand All @@ -22,7 +24,6 @@
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util import dt as dt_util
from homeassistant.util.json import JsonObjectType, json_loads_object
from homeassistant.util.thread import ThreadWithException

from .const import BUF_SIZE, LOGGER
from .models import AddonInfo, AgentBackup, Folder
Expand Down Expand Up @@ -167,23 +168,38 @@ class AsyncIteratorReader:

def __init__(self, hass: HomeAssistant, stream: AsyncIterator[bytes]) -> None:
"""Initialize the wrapper."""
self._aborted = False
self._hass = hass
self._stream = stream
self._buffer: bytes | None = None
self._next_future: Future[bytes | None] | None = None
self._pos: int = 0

async def _next(self) -> bytes | None:
"""Get the next chunk from the iterator."""
return await anext(self._stream, None)

def abort(self) -> None:
"""Abort the reader."""
self._aborted = True
if self._next_future is not None:
self._next_future.cancel()

def read(self, n: int = -1, /) -> bytes:
"""Read data from the iterator."""
result = bytearray()
while n < 0 or len(result) < n:
if not self._buffer:
self._buffer = asyncio.run_coroutine_threadsafe(
self._next_future = asyncio.run_coroutine_threadsafe(
self._next(), self._hass.loop
).result()
)
if self._aborted:
self._next_future.cancel()
raise AbortCipher
try:
self._buffer = self._next_future.result()
except CancelledError as err:
raise AbortCipher from err
self._pos = 0
if not self._buffer:
# The stream is exhausted
Expand All @@ -205,9 +221,11 @@ class AsyncIteratorWriter:

def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the wrapper."""
self._aborted = False
self._hass = hass
self._pos: int = 0
self._queue: asyncio.Queue[bytes | None] = asyncio.Queue(maxsize=1)
self._write_future: Future[bytes | None] | None = None

def __aiter__(self) -> Self:
"""Return the iterator."""
Expand All @@ -219,13 +237,28 @@ async def __anext__(self) -> bytes:
return data
raise StopAsyncIteration

def abort(self) -> None:
"""Abort the writer."""
self._aborted = True
if self._write_future is not None:
self._write_future.cancel()

def tell(self) -> int:
"""Return the current position in the iterator."""
return self._pos

def write(self, s: bytes, /) -> int:
"""Write data to the iterator."""
asyncio.run_coroutine_threadsafe(self._queue.put(s), self._hass.loop).result()
self._write_future = asyncio.run_coroutine_threadsafe(
self._queue.put(s), self._hass.loop
)
if self._aborted:
self._write_future.cancel()
raise AbortCipher
try:
self._write_future.result()
except CancelledError as err:
raise AbortCipher from err
self._pos += len(s)
return len(s)

Expand Down Expand Up @@ -415,7 +448,9 @@ def _encrypt_backup(
class _CipherWorkerStatus:
done: asyncio.Event
error: Exception | None = None
thread: ThreadWithException
reader: AsyncIteratorReader
thread: threading.Thread
writer: AsyncIteratorWriter


class _CipherBackupStreamer:
Expand Down Expand Up @@ -468,21 +503,22 @@ def on_done(error: Exception | None) -> None:
stream = await self._open_stream()
reader = AsyncIteratorReader(self._hass, stream)
writer = AsyncIteratorWriter(self._hass)
worker = ThreadWithException(
worker = threading.Thread(
target=self._cipher_func,
args=[reader, writer, self._password, on_done, self.size(), self._nonces],
)
worker_status = _CipherWorkerStatus(done=asyncio.Event(), thread=worker)
worker_status = _CipherWorkerStatus(
done=asyncio.Event(), reader=reader, thread=worker, writer=writer
)
self._workers.append(worker_status)
worker.start()
return writer

async def wait(self) -> None:
"""Wait for the worker threads to finish."""
for worker in self._workers:
if not worker.thread.is_alive():
continue
worker.thread.raise_exc(AbortCipher)
worker.reader.abort()
worker.writer.abort()
await asyncio.gather(*(worker.done.wait() for worker in self._workers))


Expand Down
12 changes: 6 additions & 6 deletions homeassistant/components/bluesound/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,31 +28,31 @@
"services": {
"join": {
"name": "Join",
"description": "Group player together.",
"description": "Groups players together under a single master speaker.",
"fields": {
"master": {
"name": "Master",
"description": "Entity ID of the player that should become the master of the group."
},
"entity_id": {
"name": "Entity",
"description": "Name of entity that will coordinate the grouping. Platform dependent."
"description": "Name of entity that will group to master speaker. Platform dependent."
}
}
},
"unjoin": {
"name": "Unjoin",
"description": "Unjoin the player from a group.",
"description": "Separates a player from a group.",
"fields": {
"entity_id": {
"name": "Entity",
"description": "Name of entity that will be unjoined from their group. Platform dependent."
"description": "Name of entity that will be separated from their group. Platform dependent."
}
}
},
"set_sleep_timer": {
"name": "Set sleep timer",
"description": "Set a Bluesound timer. It will increase timer in steps: 15, 30, 45, 60, 90, 0.",
"description": "Sets a Bluesound timer that will turn off the speaker. It will increase in steps: 15, 30, 45, 60, 90, 0.",
"fields": {
"entity_id": {
"name": "Entity",
Expand All @@ -62,7 +62,7 @@
},
"clear_sleep_timer": {
"name": "Clear sleep timer",
"description": "Clear a Bluesound timer.",
"description": "Clears a Bluesound timer.",
"fields": {
"entity_id": {
"name": "Entity",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/dwd_weather_warnings/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Data is fetched from DWD:
https://rcccm.dwd.de/DE/wetter/warnungen_aktuell/objekt_einbindung/objekteinbindung.html
Warnungen vor extremem Unwetter (Stufe 4) # codespell:ignore vor
Warnungen vor extremem Unwetter (Stufe 4) # codespell:ignore vor,extremem
Unwetterwarnungen (Stufe 3)
Warnungen vor markantem Wetter (Stufe 2) # codespell:ignore vor
Wetterwarnungen (Stufe 1)
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/frontier_silicon/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ async def async_volume_up(self) -> None:
"""Send volume up command."""
volume = await self.fs_device.get_volume()
volume = int(volume or 0) + 1
await self.fs_device.set_volume(min(volume, self._max_volume))
await self.fs_device.set_volume(min(volume, self._max_volume or 1))

async def async_volume_down(self) -> None:
"""Send volume down command."""
Expand Down
13 changes: 13 additions & 0 deletions homeassistant/components/hassio/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
BackupReaderWriter,
BackupReaderWriterError,
CreateBackupEvent,
CreateBackupStage,
CreateBackupState,
Folder,
IdleEvent,
IncorrectPasswordError,
Expand All @@ -47,6 +49,7 @@
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.util import dt as dt_util
from homeassistant.util.enum import try_parse_enum

from .const import DOMAIN, EVENT_SUPERVISOR_EVENT
from .handler import get_supervisor_client
Expand Down Expand Up @@ -336,6 +339,7 @@ async def async_create_backup(
self._async_wait_for_backup(
backup,
locations,
on_progress=on_progress,
remove_after_upload=locations == [LOCATION_CLOUD_BACKUP],
),
name="backup_manager_create_backup",
Expand All @@ -349,6 +353,7 @@ async def _async_wait_for_backup(
backup: supervisor_backups.NewBackup,
locations: list[str | None],
*,
on_progress: Callable[[CreateBackupEvent], None],
remove_after_upload: bool,
) -> WrittenBackup:
"""Wait for a backup to complete."""
Expand All @@ -360,6 +365,14 @@ async def _async_wait_for_backup(
def on_job_progress(data: Mapping[str, Any]) -> None:
"""Handle backup progress."""
nonlocal backup_id
if not (stage := try_parse_enum(CreateBackupStage, data.get("stage"))):
_LOGGER.debug("Unknown create stage: %s", data.get("stage"))
else:
on_progress(
CreateBackupEvent(
reason=None, stage=stage, state=CreateBackupState.IN_PROGRESS
)
)
if data.get("done") is True:
backup_id = data.get("reference")
create_errors.extend(data.get("errors", []))
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/hue/v1/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ def color_mode(self) -> str:
if self._fixed_color_mode:
return self._fixed_color_mode

# The light supports both hs/xy and white with adjustabe color_temperature
# The light supports both hs/xy and white with adjustable color_temperature
mode = self._color_mode
if mode in ("xy", "hs"):
return ColorMode.HS
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/isy994/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"CV": SensorDeviceClass.VOLTAGE,
"DEWPT": SensorDeviceClass.TEMPERATURE,
"DISTANC": SensorDeviceClass.DISTANCE,
"ETO": SensorDeviceClass.PRECIPITATION_INTENSITY,
"ETO": SensorDeviceClass.PRECIPITATION_INTENSITY, # codespell:ignore eto
"FATM": SensorDeviceClass.WEIGHT,
"FREQ": SensorDeviceClass.FREQUENCY,
"MUSCLEM": SensorDeviceClass.WEIGHT,
Expand Down
Loading

0 comments on commit be05f4f

Please sign in to comment.