Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7da339b
Add quality scale for GIOS (#155603)
mik-laj Feb 13, 2026
490514c
Add fixture to give tests their own unique copy of testing_config (#1…
emontnemery Feb 13, 2026
f9f2f39
OpenAI: Increase max iterations for AI Task (#162599)
balloob Feb 13, 2026
815c708
Block redirect to localhost (#162941)
edenhaus Feb 13, 2026
23e88a2
Add remove item intent for todo component (#152922)
mistic100 Feb 13, 2026
c15da19
Log remaining token duration in onedrive (#162933)
zweckj Feb 13, 2026
a0af35f
Improve MCP SSE fallback error handling (#162655)
allenporter Feb 13, 2026
36ff750
Fix handling when FRITZ!Box reboots in FRITZ!Box Tools (#162679)
mib1185 Feb 13, 2026
6b3a7e4
Fix handling when FRITZ!Box reboots in FRITZ!Smarthome (#162676)
mib1185 Feb 13, 2026
88c6cb3
add OnOffLight without brightness control to velux integration (#162835)
wollew Feb 13, 2026
ff4ff98
Parametrize yeelight test_device_types test (#161838)
epenet Feb 13, 2026
e80bb87
Bump ruff to 0.15.1 (#162903)
Thomas55555 Feb 13, 2026
a301a9c
Always include homeassistant translations in tests (#162850)
epenet Feb 13, 2026
95df5b9
Fix incorrect type HDFury select platform (#162948)
glenndehaan Feb 13, 2026
b11a75d
Add Switcher heater support (#162588)
YogevBokobza Feb 13, 2026
e16a8ed
Don't mock out filesystem operations in backup tests (#162877)
emontnemery Feb 13, 2026
d888579
Bump pysmarlaapi to 1.0.1 and compatibility changes (#162911)
rlint-explicatis Feb 13, 2026
462d958
Bump hdfury to 1.5.0 (#162944)
glenndehaan Feb 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.0
rev: v0.15.1
hooks:
- id: ruff-check
args:
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/fritz/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,12 @@ async def _async_update_data(self) -> UpdateCoordinatorDataType:
"call_deflections"
] = await self.async_update_call_deflections()
except FRITZ_EXCEPTIONS as ex:
_LOGGER.debug(
"Reload %s due to error '%s' to ensure proper re-login",
self.config_entry.title,
ex,
)
self.hass.config_entries.async_schedule_reload(self.config_entry.entry_id)
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="update_failed",
Expand Down
7 changes: 6 additions & 1 deletion homeassistant/components/fritzbox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from __future__ import annotations

from requests.exceptions import ConnectionError as RequestConnectionError, HTTPError

from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, UnitOfTemperature
from homeassistant.core import Event, HomeAssistant
Expand Down Expand Up @@ -57,7 +59,10 @@ def logout_fritzbox(event: Event) -> None:

async def async_unload_entry(hass: HomeAssistant, entry: FritzboxConfigEntry) -> bool:
"""Unloading the AVM FRITZ!SmartHome platforms."""
await hass.async_add_executor_job(entry.runtime_data.fritz.logout)
try:
await hass.async_add_executor_job(entry.runtime_data.fritz.logout)
except (RequestConnectionError, HTTPError) as ex:
LOGGER.debug("logout failed with '%s', anyway continue with unload", ex)

return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

Expand Down
38 changes: 17 additions & 21 deletions homeassistant/components/fritzbox/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,26 +121,11 @@ def cleanup_removed_devices(self, data: FritzboxCoordinatorData) -> None:

def _update_fritz_devices(self) -> FritzboxCoordinatorData:
"""Update all fritzbox device data."""
try:
self.fritz.update_devices(ignore_removed=False)
if self.has_templates:
self.fritz.update_templates(ignore_removed=False)
if self.has_triggers:
self.fritz.update_triggers(ignore_removed=False)

except RequestConnectionError as ex:
raise UpdateFailed from ex
except HTTPError:
# If the device rebooted, login again
try:
self.fritz.login()
except LoginError as ex:
raise ConfigEntryAuthFailed from ex
self.fritz.update_devices(ignore_removed=False)
if self.has_templates:
self.fritz.update_templates(ignore_removed=False)
if self.has_triggers:
self.fritz.update_triggers(ignore_removed=False)
self.fritz.update_devices(ignore_removed=False)
if self.has_templates:
self.fritz.update_templates(ignore_removed=False)
if self.has_triggers:
self.fritz.update_triggers(ignore_removed=False)

devices = self.fritz.get_devices()
device_data = {}
Expand Down Expand Up @@ -193,7 +178,18 @@ def _update_fritz_devices(self) -> FritzboxCoordinatorData:

async def _async_update_data(self) -> FritzboxCoordinatorData:
"""Fetch all device data."""
new_data = await self.hass.async_add_executor_job(self._update_fritz_devices)
try:
new_data = await self.hass.async_add_executor_job(
self._update_fritz_devices
)
except (RequestConnectionError, HTTPError) as ex:
LOGGER.debug(
"Reload %s due to error '%s' to ensure proper re-login",
self.config_entry.title,
ex,
)
self.hass.config_entries.async_schedule_reload(self.config_entry.entry_id)
raise UpdateFailed from ex

for device in new_data.devices.values():
# create device registry entry for new main devices
Expand Down
101 changes: 101 additions & 0 deletions homeassistant/components/gios/quality_scale.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
rules:
# Other comments:
# - we could consider removing the air quality entity removal

# Bronze
action-setup:
status: exempt
comment: No custom actions are defined.
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage:
status: todo
comment:
We should have the happy flow as the first test, which can be merged with test_show_form.
The config flow tests are missing adding a duplicate entry test.
config-flow:
status: todo
comment: Limit the scope of the try block in the user step
dependency-transparency: done
docs-actions:
status: exempt
comment: No custom actions are defined.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
entity-event-setup: done
entity-unique-id: done
has-entity-name: done
runtime-data:
status: todo
comment: No direct need to wrap the coordinator in a dataclass to store in the config entry
test-before-configure: done
test-before-setup: done
unique-config-entry: done

# Silver
action-exceptions:
status: exempt
comment: No custom actions are defined.
config-entry-unloading: done
docs-configuration-parameters:
status: exempt
comment: No options flow
docs-installation-parameters: done
entity-unavailable: done
integration-owner: done
log-when-unavailable: done
parallel-updates: done
reauthentication-flow:
status: exempt
comment: This integration does not require authentication.
test-coverage:
status: todo
comment:
The `test_async_setup_entry` should test the state of the mock config entry, instead of an entity state
The `test_availability` doesn't really do what it says it does, and this is now already tested via the snapshot tests.

# Gold
devices: done
diagnostics: done
discovery-update-info:
status: exempt
comment: This integration is a cloud service and thus does not support discovery.
discovery:
status: exempt
comment: This integration is a cloud service and thus does not support discovery.
docs-data-update: done
docs-examples: done
docs-known-limitations: done
docs-supported-devices:
status: exempt
comment: This is an service, which doesn't integrate with any devices.
docs-supported-functions: done
docs-troubleshooting: done
docs-use-cases: done
dynamic-devices:
status: exempt
comment: This integration does not have devices.
entity-category: done
entity-device-class:
status: todo
comment: We can use the CO device class for the carbon monoxide sensor
entity-disabled-by-default: done
entity-translations:
status: todo
comment: We can remove the options state_attributes.
exception-translations: done
icon-translations: done
reconfiguration-flow:
status: exempt
comment: Only parameter that could be changed station_id would force a new config entry.
repair-issues: done
stale-devices:
status: exempt
comment: This integration does not have devices.

# Platinum
async-dependency: done
inject-websession: done
strict-typing: done
2 changes: 1 addition & 1 deletion homeassistant/components/hdfury/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"integration_type": "device",
"iot_class": "local_polling",
"quality_scale": "gold",
"requirements": ["hdfury==1.4.2"],
"requirements": ["hdfury==1.5.0"],
"zeroconf": [
{ "name": "diva-*", "type": "_http._tcp.local." },
{ "name": "vertex2-*", "type": "_http._tcp.local." },
Expand Down
10 changes: 2 additions & 8 deletions homeassistant/components/hdfury/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@
from collections.abc import Awaitable, Callable
from dataclasses import dataclass

from hdfury import (
OPERATION_MODES,
TX0_INPUT_PORTS,
TX1_INPUT_PORTS,
HDFuryAPI,
HDFuryError,
)
from hdfury import OPERATION_MODES, TX0_INPUT_PORTS, TX1_INPUT_PORTS, HDFuryError

from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.core import HomeAssistant
Expand All @@ -27,7 +21,7 @@
class HDFurySelectEntityDescription(SelectEntityDescription):
"""Description for HDFury select entities."""

set_value_fn: Callable[[HDFuryAPI, str], Awaitable[None]]
set_value_fn: Callable[[HDFuryCoordinator, str], Awaitable[None]]


SELECT_PORTS: tuple[HDFurySelectEntityDescription, ...] = (
Expand Down
8 changes: 7 additions & 1 deletion homeassistant/components/mcp/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import logging

import httpx
from mcp import McpError
from mcp.client.session import ClientSession
from mcp.client.sse import sse_client
from mcp.client.streamable_http import streamable_http_client
Expand Down Expand Up @@ -63,10 +64,15 @@ async def mcp_client(
# Method not Allowed likely means this is not a streamable HTTP server,
# but it may be an SSE server. This is part of the MCP Transport
# backwards compatibility specification.
# We also handle other generic McpErrors since proxies may not respond
# consistently with a 405.
if (
isinstance(main_error, httpx.HTTPStatusError)
and main_error.response.status_code == 405
):
) or isinstance(main_error, McpError):
_LOGGER.debug(
"Streamable HTTP client failed, attempting SSE client: %s", main_error
)
try:
async with (
sse_client(url=url, headers=headers) as streams,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mqtt/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5280,7 +5280,7 @@ async def _async_validate_broker_settings(
)
schema = vol.Schema({cv.string: cv.template})
schema(validated_user_input[CONF_WS_HEADERS])
except (*JSON_DECODE_EXCEPTIONS, vol.MultipleInvalid): # fmt: off
except (*JSON_DECODE_EXCEPTIONS, vol.MultipleInvalid):
errors["base"] = "bad_ws_headers"
return False
return True
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/onedrive/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ async def async_upload_backup(
**kwargs: Any,
) -> None:
"""Upload a backup."""
expires_at = self._entry.data["token"]["expires_at"]
_LOGGER.debug(
"Starting backup upload, token expiry: %s (in %s seconds)",
expires_at,
expires_at - time(),
)
backup_filename, metadata_filename = suggested_filenames(backup)
file = FileInfo(
backup_filename,
Expand Down
7 changes: 7 additions & 0 deletions homeassistant/components/onedrive/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from dataclasses import dataclass
from datetime import timedelta
import logging
from time import time

from onedrive_personal_sdk import OneDriveClient
from onedrive_personal_sdk.const import DriveState
Expand Down Expand Up @@ -58,6 +59,12 @@ def __init__(

async def _async_update_data(self) -> Drive:
"""Fetch data from API endpoint."""
expires_at = self.config_entry.data["token"]["expires_at"]
_LOGGER.debug(
"Token expiry: %s (in %s seconds)",
expires_at,
expires_at - time(),
)

try:
drive = await self._client.get_drive()
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/openai_conversation/ai_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ async def _async_generate_data(
chat_log: conversation.ChatLog,
) -> ai_task.GenDataTaskResult:
"""Handle a generate data task."""
await self._async_handle_chat_log(chat_log, task.name, task.structure)
await self._async_handle_chat_log(
chat_log, task.name, task.structure, max_iterations=1000
)

if not isinstance(chat_log.content[-1], conversation.AssistantContent):
raise HomeAssistantError(
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/openai_conversation/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@ async def _async_handle_chat_log(
structure_name: str | None = None,
structure: vol.Schema | None = None,
force_image: bool = False,
max_iterations: int = MAX_TOOL_ITERATIONS,
) -> None:
"""Generate an answer for the chat log."""
options = self.subentry.data
Expand Down Expand Up @@ -632,7 +633,7 @@ async def _async_handle_chat_log(
client = self.entry.runtime_data

# To prevent infinite loops, we limit the number of iterations
for _iteration in range(MAX_TOOL_ITERATIONS):
for _iteration in range(max_iterations):
try:
stream = await client.responses.create(**model_args)

Expand Down
10 changes: 8 additions & 2 deletions homeassistant/components/smarla/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"""The Swing2Sleep Smarla integration."""

from pysmarlaapi import Connection, Federwiege
from pysmarlaapi.connection.exceptions import (
AuthenticationException,
ConnectionException,
)

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ACCESS_TOKEN
Expand All @@ -17,8 +21,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: FederwiegeConfigEntry) -
connection = Connection(HOST, token_b64=entry.data[CONF_ACCESS_TOKEN])

# Check if token still has access
if not await connection.refresh_token():
raise ConfigEntryError("Invalid authentication")
try:
await connection.refresh_token()
except (ConnectionException, AuthenticationException) as e:
raise ConfigEntryError("Invalid authentication") from e

federwiege = Federwiege(hass.loop, connection)
federwiege.register()
Expand Down
8 changes: 7 additions & 1 deletion homeassistant/components/smarla/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
from typing import Any

from pysmarlaapi import Connection
from pysmarlaapi.connection.exceptions import (
AuthenticationException,
ConnectionException,
)
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
Expand All @@ -30,7 +34,9 @@ async def _handle_token(self, token: str) -> tuple[dict[str, str], str | None]:
errors["base"] = "malformed_token"
return errors, None

if not await conn.refresh_token():
try:
await conn.refresh_token()
except ConnectionException, AuthenticationException:
errors["base"] = "invalid_auth"
return errors, None

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/smarla/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"iot_class": "cloud_push",
"loggers": ["pysmarlaapi", "pysignalr"],
"quality_scale": "bronze",
"requirements": ["pysmarlaapi==0.13.0"]
"requirements": ["pysmarlaapi==1.0.1"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/smarla/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from dataclasses import dataclass

from pysmarlaapi.federwiege.classes import Property
from pysmarlaapi.federwiege.services.classes import Property

from homeassistant.components.number import (
NumberEntity,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/smarla/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from dataclasses import dataclass

from pysmarlaapi.federwiege.classes import Property
from pysmarlaapi.federwiege.services.classes import Property

from homeassistant.components.sensor import (
SensorEntity,
Expand Down
Loading
Loading