Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ee1976d
Add l/min unit mapping to nibe_heatpump sensors (#181433)
frankkopp Sep 7, 2026
ce022ea
Don't mutate the module level Roomba SENSORS list (#181107)
jasondillingham Sep 7, 2026
4f98b35
Bump gcal-sync to 9.1.1 (#181499)
allenporter Sep 7, 2026
ecd2f8e
Fix small issues in Google Weather (#181483)
tronikos Sep 7, 2026
b990c65
Bump python-google-weather-api to 0.0.7 (#181482)
tronikos Sep 7, 2026
8bc7232
Bump python-google-drive-api to 0.2.0 (#181477)
tronikos Sep 7, 2026
efac25f
Make Google Drive backup listing resilient to unreadable metadata (#1…
tronikos Sep 7, 2026
b6dea9c
Fix OpenAI prompt caching (#181472)
Shulyaka Sep 7, 2026
43d30cd
Bump pysenz to 1.1.2 (#181435)
astrandb Sep 7, 2026
a09b33a
Fix delayed Duco bypass target updates (#181367)
ronaldvdmeer Sep 7, 2026
00e0267
victron_gx: Map Victron timestamp sensor device class (#181501)
tomer-w Sep 7, 2026
1c71fe9
Drop redundant token error handling (#181079)
zweckj Sep 7, 2026
32fa3b3
Fix flaky test in sonos (#181506)
zweckj Sep 7, 2026
88d5dab
Refactor tuya CZ/KG sensor descriptions (#181504)
epenet Sep 7, 2026
9afabaa
Trim cached orjson fragments kept by registries (#181240)
emontnemery Sep 7, 2026
b29766a
Add Circuit Breaker (ZNJDQ) Fixture in Tuya integration (#181509)
tmunzer Sep 7, 2026
dac6307
Type the dlna_dms domain data with a HassKey (#181100)
David-Wu1119 Sep 7, 2026
f5f2848
Type the dlna_dmr domain data with a HassKey (#181101)
David-Wu1119 Sep 7, 2026
ec82dee
Use PressureConverter for Teslemetry TPMS streaming conversion (#181508)
Bre77 Sep 7, 2026
2da171b
Add switch platform to Tuya ZNJDQ (circuit breaker) (#181517)
tmunzer Sep 7, 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
14 changes: 1 addition & 13 deletions homeassistant/components/aladdin_connect/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
"""The Aladdin Connect Genie integration."""

import aiohttp
from genie_partner_sdk.client import AladdinConnectClient

from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
OAuth2TokenRequestError,
OAuth2TokenRequestReauthError,
)
from homeassistant.helpers import (
aiohttp_client,
config_entry_oauth2_flow,
Expand All @@ -36,12 +29,7 @@ async def async_setup_entry(

session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation)

try:
await session.async_ensure_token_valid()
except OAuth2TokenRequestReauthError as err:
raise ConfigEntryAuthFailed(err) from err
except (OAuth2TokenRequestError, aiohttp.ClientError) as err:
raise ConfigEntryNotReady from err
await session.async_ensure_token_valid()

client = AladdinConnectClient(
api.AsyncConfigEntryAuth(aiohttp_client.async_get_clientsession(hass), session)
Expand Down
8 changes: 3 additions & 5 deletions homeassistant/components/august/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
OAuth2TokenRequestError,
OAuth2TokenRequestReauthError,
OAuth2TokenRequestBaseError,
)
from homeassistant.helpers import device_registry as dr, issue_registry as ir
from homeassistant.helpers.config_entry_oauth2_flow import (
Expand Down Expand Up @@ -44,15 +43,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: AugustConfigEntry) -> bo
august_gateway = AugustGateway(Path(hass.config.config_dir), session, oauth_session)
try:
await async_setup_august(hass, entry, august_gateway)
except OAuth2TokenRequestReauthError as err:
raise ConfigEntryAuthFailed from err
except OAuth2TokenRequestBaseError:
raise
except (RequireValidation, InvalidAuth) as err:
raise ConfigEntryAuthFailed from err
except TimeoutError as err:
raise ConfigEntryNotReady("Timed out connecting to august api") from err
except (
AugustApiAIOHTTPError,
OAuth2TokenRequestError,
ClientError,
CannotConnect,
) as err:
Expand Down
40 changes: 3 additions & 37 deletions homeassistant/components/cloud/account_link.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Account linking via the cloud."""

from datetime import datetime
from http import HTTPStatus
import logging
from typing import Any, override

Expand All @@ -11,11 +10,6 @@

from homeassistant.const import __version__ as HA_VERSION
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import (
OAuth2TokenRequestError,
OAuth2TokenRequestReauthError,
OAuth2TokenRequestTransientError,
)
from homeassistant.helpers import config_entry_oauth2_flow, event

from .const import DATA_CLOUD, DOMAIN
Expand Down Expand Up @@ -163,35 +157,7 @@ async def async_resolve_external_data(self, external_data: Any) -> dict:
@override
async def _async_refresh_token(self, token: dict) -> dict:
"""Refresh a token."""
try:
new_token = await account_link.async_fetch_access_token(
self.hass.data[DATA_CLOUD], self.service, token["refresh_token"]
)
except aiohttp.ClientResponseError as err:
if err.status == HTTPStatus.TOO_MANY_REQUESTS or 500 <= err.status <= 599:
raise OAuth2TokenRequestTransientError(
request_info=err.request_info,
history=err.history,
status=err.status,
message=err.message,
headers=err.headers,
domain=self.service,
) from err
if 400 <= err.status <= 499:
raise OAuth2TokenRequestReauthError(
request_info=err.request_info,
history=err.history,
status=err.status,
message=err.message,
headers=err.headers,
domain=self.service,
) from err
raise OAuth2TokenRequestError(
request_info=err.request_info,
history=err.history,
status=err.status,
message=err.message,
headers=err.headers,
domain=self.service,
) from err
new_token = await account_link.async_fetch_access_token(
self.hass.data[DATA_CLOUD], self.service, token["refresh_token"]
)
return {**token, **new_token}
10 changes: 9 additions & 1 deletion homeassistant/components/dlna_dmr/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@

from collections.abc import Mapping
import logging
from typing import Final
from typing import TYPE_CHECKING, Final

from async_upnp_client.profiles.dlna import PlayMode as _PlayMode

from homeassistant.components.media_player import MediaType, RepeatMode
from homeassistant.util.hass_dict import HassKey

if TYPE_CHECKING:
from .data import DlnaDmrData

LOGGER = logging.getLogger(__package__)

DOMAIN: Final = "dlna_dmr"

# One DlnaDmrData owns the shared UPnP requester and event notifiers used by
# every config entry, so it is not per-entry state.
DOMAIN_DATA: HassKey[DlnaDmrData] = HassKey(DOMAIN)

CONF_LISTEN_PORT: Final = "listen_port"
CONF_CALLBACK_URL_OVERRIDE: Final = "callback_url_override"
CONF_POLL_AVAILABILITY: Final = "poll_availability"
Expand Down
20 changes: 12 additions & 8 deletions homeassistant/components/dlna_dmr/data.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
"""Data used by this integration."""
# pylint: disable=home-assistant-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern

import asyncio
from collections import defaultdict
from typing import NamedTuple, cast
from typing import NamedTuple

from async_upnp_client.aiohttp import AiohttpNotifyServer, AiohttpSessionRequester
from async_upnp_client.client import UpnpRequester
Expand All @@ -14,7 +13,7 @@
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant
from homeassistant.helpers import aiohttp_client

from .const import DOMAIN, LOGGER
from .const import DOMAIN_DATA, LOGGER


class EventListenAddr(NamedTuple):
Expand Down Expand Up @@ -117,10 +116,15 @@ async def async_release_event_notifier(self, listen_addr: EventListenAddr) -> No


def get_domain_data(hass: HomeAssistant) -> DlnaDmrData:
"""Obtain this integration's domain data, creating it if needed."""
if DOMAIN in hass.data:
return cast(DlnaDmrData, hass.data[DOMAIN])
"""Obtain this integration's domain data, creating it if needed.

data = DlnaDmrData(hass)
hass.data[DOMAIN] = data
Creation is deferred to the first caller rather than done at setup, to
avoid building DlnaDmrData and its dependencies until a device is
actually connected to. This module is imported to run the config flow
for any DMR device discovered on the network, including ignored ones.
"""
if (data := hass.data.get(DOMAIN_DATA)) is not None:
return data

data = hass.data[DOMAIN_DATA] = DlnaDmrData(hass)
return data
10 changes: 9 additions & 1 deletion homeassistant/components/dlna_dms/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,23 @@

from collections.abc import Mapping
import logging
from typing import Final
from typing import TYPE_CHECKING, Final

from homeassistant.components.media_player import MediaClass
from homeassistant.util.hass_dict import HassKey

if TYPE_CHECKING:
from .dms import DlnaDmsData

LOGGER = logging.getLogger(__package__)

DOMAIN: Final = "dlna_dms"
DEFAULT_NAME: Final = "DLNA Media Server"

# One DlnaDmsData holds the device and source registries for every config
# entry, so it is shared rather than owned by any one entry.
DOMAIN_DATA: HassKey[DlnaDmsData] = HassKey(DOMAIN)

CONF_SOURCE_ID: Final = "source_id"
CONFIG_VERSION: Final = 1

Expand Down
19 changes: 12 additions & 7 deletions homeassistant/components/dlna_dms/dms.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"""Wrapper for media_source around async_upnp_client's DmsDevice ."""
# pylint: disable=home-assistant-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern

import asyncio
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from enum import StrEnum
import functools
from typing import Any, cast
from typing import Any

from async_upnp_client.aiohttp import AiohttpSessionRequester
from async_upnp_client.client import UpnpRequester
Expand Down Expand Up @@ -37,6 +36,7 @@
DLNA_RESOLVE_FILTER,
DLNA_SORT_CRITERIA,
DOMAIN,
DOMAIN_DATA,
LOGGER,
MEDIA_CLASS_MAP,
PATH_OBJECT_ID_FLAG,
Expand Down Expand Up @@ -91,12 +91,17 @@ async def async_unload_entry(self, config_entry: ConfigEntry) -> bool:

@callback
def get_domain_data(hass: HomeAssistant) -> DlnaDmsData:
"""Obtain this integration's domain data, creating it if needed."""
if DOMAIN in hass.data:
return cast(DlnaDmsData, hass.data[DOMAIN])
"""Obtain this integration's domain data, creating it if needed.

data = DlnaDmsData(hass)
hass.data[DOMAIN] = data
Creation is deferred to the first caller rather than done at setup, to
avoid building DlnaDmsData and its dependencies until a device is
actually connected to. This module is imported to run the config flow
for any DMS device discovered on the network, including ignored ones.
"""
if (data := hass.data.get(DOMAIN_DATA)) is not None:
return data

data = hass.data[DOMAIN_DATA] = DlnaDmsData(hass)
return data


Expand Down
15 changes: 13 additions & 2 deletions homeassistant/components/duco/number.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Number platform for the Duco integration."""

from dataclasses import replace
import logging
from typing import override

Expand Down Expand Up @@ -129,7 +130,7 @@ async def async_set_native_value(self, value: float) -> None:
try:
if self.unit_of_measurement != self.native_unit_of_measurement:
value = target.normalize_value(value)
await self.coordinator.client.async_set_bypass_supply_temperature_target(
updated_target = await self.coordinator.client.async_set_bypass_supply_temperature_target(
self._zone_id, value, target=target
)
except ValueError as err:
Expand Down Expand Up @@ -157,4 +158,14 @@ async def async_set_native_value(self, value: float) -> None:
translation_key="failed_to_set_bypass_supply_temperature_target",
) from err

await self.coordinator.async_request_refresh()
# Do not let a completed write mask a concurrent coordinator refresh failure.
if self.coordinator.last_update_success:
self.coordinator.async_set_updated_data(
replace(
self.coordinator.data,
bypass_supply_temperature_targets={
**self.coordinator.data.bypass_supply_temperature_targets,
self._zone_id: updated_target,
},
)
)
15 changes: 2 additions & 13 deletions homeassistant/components/electric_kiwi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
"""The Electric Kiwi integration."""

import aiohttp
from electrickiwi_api import ElectricKiwiApi
from electrickiwi_api.exceptions import ApiException, AuthException

from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
OAuth2TokenRequestError,
OAuth2TokenRequestReauthError,
)
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import (
aiohttp_client,
config_entry_oauth2_flow,
Expand Down Expand Up @@ -41,12 +35,7 @@ async def async_setup_entry(

session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation)

try:
await session.async_ensure_token_valid()
except OAuth2TokenRequestReauthError as err:
raise ConfigEntryAuthFailed(err) from err
except (OAuth2TokenRequestError, aiohttp.ClientError) as err:
raise ConfigEntryNotReady from err
await session.async_ensure_token_valid()

ek_api = ElectricKiwiApi(
api.ConfigEntryElectricKiwiAuth(
Expand Down
15 changes: 2 additions & 13 deletions homeassistant/components/google/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import time
from typing import Any

import aiohttp
from gcal_sync.api import GoogleCalendarService
from gcal_sync.exceptions import ApiException, AuthException
import voluptuous as vol
Expand All @@ -20,12 +19,7 @@
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
OAuth2TokenRequestError,
OAuth2TokenRequestReauthError,
)
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_entry_oauth2_flow, config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import generate_entity_id
Expand Down Expand Up @@ -105,12 +99,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: GoogleConfigEntry) -> bo
if session.token["expires_at"] >= now + timedelta(days=365).total_seconds():
session.token["expires_in"] = 0
session.token["expires_at"] = now
try:
await session.async_ensure_token_valid()
except OAuth2TokenRequestReauthError as err:
raise ConfigEntryAuthFailed from err
except (OAuth2TokenRequestError, aiohttp.ClientError) as err:
raise ConfigEntryNotReady from err
await session.async_ensure_token_valid()

if not async_entry_has_scopes(entry):
raise ConfigEntryAuthFailed(
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/google/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["googleapiclient"],
"requirements": ["gcal-sync==9.1.0", "oauth2client==4.1.3", "ical==14.1.1"]
"requirements": ["gcal-sync==9.1.1", "oauth2client==4.1.3", "ical==14.1.1"]
}
16 changes: 1 addition & 15 deletions homeassistant/components/google_assistant_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,12 @@
import asyncio
from typing import override

from aiohttp import ClientError
from gassist_text import TextAssistantAsync
from google.oauth2.credentials import Credentials

from homeassistant.components import conversation
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_NAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
OAuth2TokenRequestError,
OAuth2TokenRequestReauthError,
)
from homeassistant.helpers import config_validation as cv, discovery, intent
from homeassistant.helpers.config_entry_oauth2_flow import (
OAuth2Session,
Expand Down Expand Up @@ -55,14 +48,7 @@ async def async_setup_entry(
"""Set up Google Assistant SDK from a config entry."""
implementation = await async_get_config_entry_implementation(hass, entry)
session = OAuth2Session(hass, entry, implementation)
try:
await session.async_ensure_token_valid()
except OAuth2TokenRequestReauthError as err:
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN, translation_key="reauth_required"
) from err
except (OAuth2TokenRequestError, ClientError) as err:
raise ConfigEntryNotReady from err
await session.async_ensure_token_valid()

mem_storage = InMemoryStorage(hass)
hass.http.register_view(GoogleAssistantSDKAudioView(mem_storage))
Expand Down
Loading
Loading