mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
BMW Connected Drive: Handle HTTP 429 issues better (#73675)
Co-authored-by: rikroe <rikroe@users.noreply.github.com>
This commit is contained in:
parent
754fe86dd9
commit
54591b8ca1
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from bimmer_connected.account import MyBMWAccount
|
from bimmer_connected.api.authentication import MyBMWAuthentication
|
||||||
from bimmer_connected.api.regions import get_region_from_name
|
from bimmer_connected.api.regions import get_region_from_name
|
||||||
from httpx import HTTPError
|
from httpx import HTTPError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -14,7 +14,7 @@ from homeassistant.core import callback
|
|||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
|
||||||
from . import DOMAIN
|
from . import DOMAIN
|
||||||
from .const import CONF_ALLOWED_REGIONS, CONF_READ_ONLY
|
from .const import CONF_ALLOWED_REGIONS, CONF_READ_ONLY, CONF_REFRESH_TOKEN
|
||||||
|
|
||||||
DATA_SCHEMA = vol.Schema(
|
DATA_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
@ -32,19 +32,22 @@ async def validate_input(
|
|||||||
|
|
||||||
Data has the keys from DATA_SCHEMA with values provided by the user.
|
Data has the keys from DATA_SCHEMA with values provided by the user.
|
||||||
"""
|
"""
|
||||||
account = MyBMWAccount(
|
auth = MyBMWAuthentication(
|
||||||
data[CONF_USERNAME],
|
data[CONF_USERNAME],
|
||||||
data[CONF_PASSWORD],
|
data[CONF_PASSWORD],
|
||||||
get_region_from_name(data[CONF_REGION]),
|
get_region_from_name(data[CONF_REGION]),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await account.get_vehicles()
|
await auth.login()
|
||||||
except HTTPError as ex:
|
except HTTPError as ex:
|
||||||
raise CannotConnect from ex
|
raise CannotConnect from ex
|
||||||
|
|
||||||
# Return info that you want to store in the config entry.
|
# Return info that you want to store in the config entry.
|
||||||
return {"title": f"{data[CONF_USERNAME]}{data.get(CONF_SOURCE, '')}"}
|
retval = {"title": f"{data[CONF_USERNAME]}{data.get(CONF_SOURCE, '')}"}
|
||||||
|
if auth.refresh_token:
|
||||||
|
retval[CONF_REFRESH_TOKEN] = auth.refresh_token
|
||||||
|
return retval
|
||||||
|
|
||||||
|
|
||||||
class BMWConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
class BMWConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
@ -70,7 +73,13 @@ class BMWConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
|
|
||||||
if info:
|
if info:
|
||||||
return self.async_create_entry(title=info["title"], data=user_input)
|
return self.async_create_entry(
|
||||||
|
title=info["title"],
|
||||||
|
data={
|
||||||
|
**user_input,
|
||||||
|
CONF_REFRESH_TOKEN: info.get(CONF_REFRESH_TOKEN),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
||||||
|
@ -7,7 +7,7 @@ import logging
|
|||||||
from bimmer_connected.account import MyBMWAccount
|
from bimmer_connected.account import MyBMWAccount
|
||||||
from bimmer_connected.api.regions import get_region_from_name
|
from bimmer_connected.api.regions import get_region_from_name
|
||||||
from bimmer_connected.models import GPSPosition
|
from bimmer_connected.models import GPSPosition
|
||||||
from httpx import HTTPError, TimeoutException
|
from httpx import HTTPError, HTTPStatusError, TimeoutException
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME
|
||||||
@ -16,7 +16,8 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
|
|||||||
|
|
||||||
from .const import CONF_READ_ONLY, CONF_REFRESH_TOKEN, DOMAIN
|
from .const import CONF_READ_ONLY, CONF_REFRESH_TOKEN, DOMAIN
|
||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=300)
|
DEFAULT_SCAN_INTERVAL_SECONDS = 300
|
||||||
|
SCAN_INTERVAL = timedelta(seconds=DEFAULT_SCAN_INTERVAL_SECONDS)
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -53,8 +54,18 @@ class BMWDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await self.account.get_vehicles()
|
await self.account.get_vehicles()
|
||||||
except (HTTPError, TimeoutException) as err:
|
except (HTTPError, HTTPStatusError, TimeoutException) as err:
|
||||||
self._update_config_entry_refresh_token(None)
|
if isinstance(err, HTTPStatusError) and err.response.status_code == 429:
|
||||||
|
# Increase scan interval to not jump to not bring up the issue next time
|
||||||
|
self.update_interval = timedelta(
|
||||||
|
seconds=DEFAULT_SCAN_INTERVAL_SECONDS * 3
|
||||||
|
)
|
||||||
|
if isinstance(err, HTTPStatusError) and err.response.status_code in (
|
||||||
|
401,
|
||||||
|
403,
|
||||||
|
):
|
||||||
|
# Clear refresh token only on issues with authorization
|
||||||
|
self._update_config_entry_refresh_token(None)
|
||||||
raise UpdateFailed(f"Error communicating with BMW API: {err}") from err
|
raise UpdateFailed(f"Error communicating with BMW API: {err}") from err
|
||||||
|
|
||||||
if self.account.refresh_token != old_refresh_token:
|
if self.account.refresh_token != old_refresh_token:
|
||||||
@ -65,6 +76,9 @@ class BMWDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
self.account.refresh_token,
|
self.account.refresh_token,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Reset scan interval after successful update
|
||||||
|
self.update_interval = timedelta(seconds=DEFAULT_SCAN_INTERVAL_SECONDS)
|
||||||
|
|
||||||
def _update_config_entry_refresh_token(self, refresh_token: str | None) -> None:
|
def _update_config_entry_refresh_token(self, refresh_token: str | None) -> None:
|
||||||
"""Update or delete the refresh_token in the Config Entry."""
|
"""Update or delete the refresh_token in the Config Entry."""
|
||||||
data = {
|
data = {
|
||||||
|
@ -1,19 +1,32 @@
|
|||||||
"""Test the for the BMW Connected Drive config flow."""
|
"""Test the for the BMW Connected Drive config flow."""
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from bimmer_connected.api.authentication import MyBMWAuthentication
|
||||||
from httpx import HTTPError
|
from httpx import HTTPError
|
||||||
|
|
||||||
from homeassistant import config_entries, data_entry_flow
|
from homeassistant import config_entries, data_entry_flow
|
||||||
from homeassistant.components.bmw_connected_drive.config_flow import DOMAIN
|
from homeassistant.components.bmw_connected_drive.config_flow import DOMAIN
|
||||||
from homeassistant.components.bmw_connected_drive.const import CONF_READ_ONLY
|
from homeassistant.components.bmw_connected_drive.const import (
|
||||||
|
CONF_READ_ONLY,
|
||||||
|
CONF_REFRESH_TOKEN,
|
||||||
|
)
|
||||||
from homeassistant.const import CONF_USERNAME
|
from homeassistant.const import CONF_USERNAME
|
||||||
|
|
||||||
from . import FIXTURE_CONFIG_ENTRY, FIXTURE_USER_INPUT
|
from . import FIXTURE_CONFIG_ENTRY, FIXTURE_USER_INPUT
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
FIXTURE_COMPLETE_ENTRY = FIXTURE_USER_INPUT.copy()
|
FIXTURE_REFRESH_TOKEN = "SOME_REFRESH_TOKEN"
|
||||||
FIXTURE_IMPORT_ENTRY = FIXTURE_USER_INPUT.copy()
|
FIXTURE_COMPLETE_ENTRY = {
|
||||||
|
**FIXTURE_USER_INPUT,
|
||||||
|
CONF_REFRESH_TOKEN: FIXTURE_REFRESH_TOKEN,
|
||||||
|
}
|
||||||
|
FIXTURE_IMPORT_ENTRY = {**FIXTURE_USER_INPUT, CONF_REFRESH_TOKEN: None}
|
||||||
|
|
||||||
|
|
||||||
|
def login_sideeffect(self: MyBMWAuthentication):
|
||||||
|
"""Mock logging in and setting a refresh token."""
|
||||||
|
self.refresh_token = FIXTURE_REFRESH_TOKEN
|
||||||
|
|
||||||
|
|
||||||
async def test_show_form(hass):
|
async def test_show_form(hass):
|
||||||
@ -50,8 +63,9 @@ async def test_connection_error(hass):
|
|||||||
async def test_full_user_flow_implementation(hass):
|
async def test_full_user_flow_implementation(hass):
|
||||||
"""Test registering an integration and finishing flow works."""
|
"""Test registering an integration and finishing flow works."""
|
||||||
with patch(
|
with patch(
|
||||||
"bimmer_connected.account.MyBMWAccount.get_vehicles",
|
"bimmer_connected.api.authentication.MyBMWAuthentication.login",
|
||||||
return_value=[],
|
side_effect=login_sideeffect,
|
||||||
|
autospec=True,
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.bmw_connected_drive.async_setup_entry",
|
"homeassistant.components.bmw_connected_drive.async_setup_entry",
|
||||||
return_value=True,
|
return_value=True,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user