Compare commits

..

3 Commits

Author SHA1 Message Date
Erwin Douna 4c4a9053dd TYPE checking 2026-06-24 17:05:24 +00:00
Erwin Douna 778640f587 Startline group executor job 2026-06-24 17:04:35 +00:00
TheJulianJES 975f2a831e Bump zha-quirks to 2.1.0 (#174662) 2026-06-24 18:42:41 +02:00
14 changed files with 52 additions and 166 deletions
+1 -14
View File
@@ -17,7 +17,7 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
)
from .auth import DropboxConfigEntryAuth
from .const import DATA_BACKUP_AGENT_LISTENERS, DOMAIN, OAUTH2_SCOPES
from .const import DATA_BACKUP_AGENT_LISTENERS, DOMAIN
type DropboxConfigEntry = ConfigEntry[DropboxAPIClient]
@@ -31,19 +31,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: DropboxConfigEntry) -> b
translation_domain=DOMAIN,
translation_key="oauth2_implementation_unavailable",
) from err
token = entry.data["token"]
if not set(token.get("scope", "").split()).issuperset(OAUTH2_SCOPES):
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="missing_scopes",
)
if "refresh_token" not in token:
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="missing_refresh_token",
)
oauth2_session = OAuth2Session(hass, entry, oauth2_implementation)
auth = DropboxConfigEntryAuth(
@@ -1,5 +1,7 @@
"""Application credentials platform for the Dropbox integration."""
from typing import override
from homeassistant.components.application_credentials import ClientCredential
from homeassistant.core import HomeAssistant
from homeassistant.helpers.config_entry_oauth2_flow import (
@@ -7,14 +9,14 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
LocalOAuth2ImplementationWithPkce,
)
from .const import OAUTH2_AUTHORIZE, OAUTH2_TOKEN
from .const import OAUTH2_AUTHORIZE, OAUTH2_SCOPES, OAUTH2_TOKEN
async def async_get_auth_implementation(
hass: HomeAssistant, auth_domain: str, credential: ClientCredential
) -> AbstractOAuth2Implementation:
"""Return auth implementation."""
return LocalOAuth2ImplementationWithPkce(
"""Return custom auth implementation."""
return DropboxOAuth2Implementation(
hass,
auth_domain,
credential.client_id,
@@ -22,3 +24,21 @@ async def async_get_auth_implementation(
OAUTH2_TOKEN,
credential.client_secret,
)
class DropboxOAuth2Implementation(LocalOAuth2ImplementationWithPkce):
"""Custom Dropbox OAuth2 implementation.
Adds the necessary authorize url parameters.
"""
@property
@override
def extra_authorize_data(self) -> dict:
"""Extra data that needs to be appended to the authorize url."""
data: dict = {
"token_access_type": "offline",
"scope": " ".join(OAUTH2_SCOPES),
}
data.update(super().extra_authorize_data)
return data
@@ -12,7 +12,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler
from .auth import DropboxConfigFlowAuth
from .const import DOMAIN, OAUTH2_SCOPES
from .const import DOMAIN
class DropboxConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
@@ -26,15 +26,6 @@ class DropboxConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
"""Return logger."""
return logging.getLogger(__name__)
@property
@override
def extra_authorize_data(self) -> dict:
"""Extra data that needs to be appended to the authorize url."""
return {
"token_access_type": "offline",
"scope": " ".join(OAUTH2_SCOPES),
}
@override
async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult:
"""Create an entry for the flow, or update existing entry."""
@@ -60,9 +51,6 @@ class DropboxConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Perform reauth upon an API authentication error."""
token = entry_data[CONF_TOKEN]
if not set(token.get("scope", "").split()).issuperset(OAUTH2_SCOPES):
return await self.async_step_reauth_permissions()
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
@@ -72,11 +60,3 @@ class DropboxConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
if user_input is None:
return self.async_show_form(step_id="reauth_confirm")
return await self.async_step_user()
async def async_step_reauth_permissions(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Dialog that informs the user that additional permissions are required."""
if user_input is None:
return self.async_show_form(step_id="reauth_permissions")
return await self.async_step_user()
@@ -12,7 +12,6 @@ OAUTH2_SCOPES = [
"account_info.read",
"files.content.read",
"files.content.write",
"files.metadata.read",
]
DATA_BACKUP_AGENT_LISTENERS: HassKey[list[Callable[[], None]]] = HassKey(
@@ -24,20 +24,10 @@
"reauth_confirm": {
"description": "The Dropbox integration needs to re-authenticate your account.",
"title": "[%key:common::config_flow::title::reauth%]"
},
"reauth_permissions": {
"description": "The Dropbox integration requires additional permissions to function correctly.",
"title": "[%key:common::config_flow::title::reauth%]"
}
}
},
"exceptions": {
"missing_refresh_token": {
"message": "[%key:component::dropbox::config::step::reauth_confirm::description%]"
},
"missing_scopes": {
"message": "[%key:component::dropbox::config::step::reauth_permissions::description%]"
},
"oauth2_implementation_unavailable": {
"message": "[%key:common::exceptions::oauth2_implementation_unavailable::message%]"
}
@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["homematicip"],
"requirements": ["homematicip==2.13.2"]
"requirements": ["homematicip==2.13.1"]
}
@@ -1,6 +1,6 @@
"""Config flow to configure StarLine component."""
from typing import override
from typing import TYPE_CHECKING, override
from starline import StarlineAuth
import voluptuous as vol
@@ -192,13 +192,18 @@ class StarlineFlowHandler(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Authenticate application."""
try:
self._app_code = await self.hass.async_add_executor_job(
self._auth.get_app_code, self._app_id, self._app_secret
)
# pylint: disable-next=home-assistant-sequential-executor-jobs
self._app_token = await self.hass.async_add_executor_job(
self._auth.get_app_token, self._app_id, self._app_secret, self._app_code
)
def _get_app_token() -> str:
if TYPE_CHECKING:
assert self._app_id is not None
assert self._app_secret is not None
app_code = self._auth.get_app_code(self._app_id, self._app_secret)
return self._auth.get_app_token(
self._app_id, self._app_secret, app_code
)
self._app_token = await self.hass.async_add_executor_job(_get_app_token)
return self._async_form_auth_user(error)
except Exception as err: # noqa: BLE001
_LOGGER.error("Error auth StarLine: %s", err)
@@ -67,13 +67,12 @@ class Tami4ConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input is not None:
otp = user_input["otp"]
try:
def _submit_otp_and_create_api() -> tuple[str, Tami4EdgeAPI]:
refresh_token = Tami4EdgeAPI.submit_otp(self.phone, otp)
return refresh_token, Tami4EdgeAPI(refresh_token)
refresh_token, api = await self.hass.async_add_executor_job(
_submit_otp_and_create_api
refresh_token = await self.hass.async_add_executor_job(
Tami4EdgeAPI.submit_otp, self.phone, otp
)
# pylint: disable-next=home-assistant-sequential-executor-jobs
api = await self.hass.async_add_executor_job(
Tami4EdgeAPI, refresh_token
)
except exceptions.OTPFailedException:
errors["base"] = "invalid_auth"
+3 -7
View File
@@ -62,14 +62,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: VeraConfigEntry) -> bool
controller = veraApi.VeraController(base_url, subscription_registry)
try:
all_devices = await hass.async_add_executor_job(controller.get_devices)
def _get_devices_and_scenes():
"""Get devices and scenes from the Vera controller."""
return controller.get_devices(), controller.get_scenes()
all_devices, all_scenes = await hass.async_add_executor_job(
_get_devices_and_scenes
)
# pylint: disable-next=home-assistant-sequential-executor-jobs
all_scenes = await hass.async_add_executor_job(controller.get_scenes)
except RequestException as exception:
# There was a network related error connecting to the Vera controller.
_LOGGER.exception("Error communicating with Vera API")
+1 -1
View File
@@ -22,7 +22,7 @@ if TYPE_CHECKING:
APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2026
MINOR_VERSION: Final = 7
PATCH_VERSION: Final = "0b1"
PATCH_VERSION: Final = "0.dev0"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 14, 2)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2026.7.0b1"
version = "2026.7.0.dev0"
license = "Apache-2.0"
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
description = "Open-source home automation platform running on Python 3."
+1 -1
View File
@@ -1284,7 +1284,7 @@ homekit-audio-proxy==1.2.1
homelink-integration-api==0.0.5
# homeassistant.components.homematicip_cloud
homematicip==2.13.2
homematicip==2.13.1
# homeassistant.components.homevolt
homevolt==0.5.0
@@ -128,54 +128,6 @@ async def test_already_configured(
assert result["reason"] == "already_configured"
@pytest.mark.usefixtures("current_request_with_host")
@pytest.mark.parametrize(
("token", "expected_step"),
[
(
{
"access_token": "mock-access-token",
"expires_at": 9_999_999_999,
"scope": " ".join(OAUTH2_SCOPES),
},
"reauth_confirm",
),
(
{
"access_token": "mock-access-token",
"refresh_token": "mock-refresh-token",
"expires_at": 9_999_999_999,
"scope": "account_info.read files.content.read files.content.write",
},
"reauth_permissions",
),
],
ids=["missing_refresh_token", "missing_scope"],
)
async def test_reauth_confirm_step(
hass: HomeAssistant,
mock_config_entry,
token: dict[str, object],
expected_step: str,
) -> None:
"""Test reauth shows the correct confirmation step for the broken token."""
mock_config_entry.add_to_hass(hass)
hass.config_entries.async_update_entry(
mock_config_entry, data={**mock_config_entry.data, "token": token}
)
result = await mock_config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == expected_step
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result["type"] is FlowResultType.EXTERNAL_STEP
assert result["step_id"] == "auth"
@pytest.mark.usefixtures("current_request_with_host")
@pytest.mark.parametrize(
(
+1 -43
View File
@@ -1,13 +1,11 @@
"""Test the Dropbox integration setup."""
from typing import Any
from unittest.mock import AsyncMock, patch
import pytest
from python_dropbox_api import DropboxAuthException, DropboxUnknownException
from homeassistant.components.dropbox.const import DOMAIN, OAUTH2_SCOPES
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.helpers.config_entry_oauth2_flow import (
ImplementationUnavailableError,
@@ -82,46 +80,6 @@ async def test_setup_entry_implementation_unavailable(
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
@pytest.mark.usefixtures("mock_dropbox_client")
@pytest.mark.parametrize(
"token",
[
{
"access_token": "mock-access-token",
"expires_at": 9_999_999_999,
"scope": " ".join(OAUTH2_SCOPES),
},
{
"access_token": "mock-access-token",
"refresh_token": "mock-refresh-token",
"expires_at": 9_999_999_999,
"scope": "account_info.read files.content.read files.content.write",
},
],
ids=["missing_refresh_token", "missing_scope"],
)
async def test_setup_entry_triggers_reauth(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
token: dict[str, Any],
) -> None:
"""Test that a broken token triggers a reauth flow during setup."""
mock_config_entry.add_to_hass(hass)
hass.config_entries.async_update_entry(
mock_config_entry, data={**mock_config_entry.data, "token": token}
)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
flows = hass.config_entries.flow.async_progress_by_handler(DOMAIN)
assert len(flows) == 1
assert flows[0]["context"]["source"] == SOURCE_REAUTH
assert flows[0]["context"]["entry_id"] == mock_config_entry.entry_id
@pytest.mark.usefixtures("mock_dropbox_client")
async def test_unload_entry(
hass: HomeAssistant,