Bump here_transit/here_routing and Implement backoff mechanism for here_travel_time (#83976)

* Add failing test

* Add backoff mechanism for too many requests

* Increase async_fire_time_changed

* Minimize try/except block
This commit is contained in:
Kevin Stillhammer 2022-12-21 16:00:15 +01:00 committed by GitHub
parent b85e175812
commit 588211223b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 178 additions and 22 deletions

View file

@ -6,13 +6,21 @@ import logging
from typing import Any
import here_routing
from here_routing import HERERoutingApi, Return, RoutingMode, Spans, TransportMode
from here_routing import (
HERERoutingApi,
HERERoutingTooManyRequestsError,
Return,
RoutingMode,
Spans,
TransportMode,
)
import here_transit
from here_transit import (
HERETransitApi,
HERETransitConnectionError,
HERETransitDepartureArrivalTooCloseError,
HERETransitNoRouteFoundError,
HERETransitTooManyRequestsError,
)
import voluptuous as vol
@ -27,6 +35,8 @@ from homeassistant.util.unit_conversion import DistanceConverter
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, ROUTE_MODE_FASTEST
from .model import HERETravelTimeConfig, HERETravelTimeData
BACKOFF_MULTIPLIER = 1.1
_LOGGER = logging.getLogger(__name__)
@ -71,19 +81,35 @@ class HERERoutingDataUpdateCoordinator(DataUpdateCoordinator):
departure,
)
response = await self._api.route(
transport_mode=TransportMode(self.config.travel_mode),
origin=here_routing.Place(origin[0], origin[1]),
destination=here_routing.Place(destination[0], destination[1]),
routing_mode=route_mode,
arrival_time=arrival,
departure_time=departure,
return_values=[Return.POLYINE, Return.SUMMARY],
spans=[Spans.NAMES],
)
try:
response = await self._api.route(
transport_mode=TransportMode(self.config.travel_mode),
origin=here_routing.Place(origin[0], origin[1]),
destination=here_routing.Place(destination[0], destination[1]),
routing_mode=route_mode,
arrival_time=arrival,
departure_time=departure,
return_values=[Return.POLYINE, Return.SUMMARY],
spans=[Spans.NAMES],
)
except HERERoutingTooManyRequestsError as error:
assert self.update_interval is not None
_LOGGER.debug(
"Rate limit has been reached. Increasing update interval to %s",
self.update_interval.total_seconds() * BACKOFF_MULTIPLIER,
)
self.update_interval = timedelta(
seconds=self.update_interval.total_seconds() * BACKOFF_MULTIPLIER
)
raise UpdateFailed("Rate limit has been reached") from error
_LOGGER.debug("Raw response is: %s", response)
if self.update_interval != timedelta(seconds=DEFAULT_SCAN_INTERVAL):
_LOGGER.debug(
"Resetting update interval to %s",
DEFAULT_SCAN_INTERVAL,
)
self.update_interval = timedelta(seconds=DEFAULT_SCAN_INTERVAL)
return self._parse_routing_response(response)
def _parse_routing_response(self, response: dict[str, Any]) -> HERETravelTimeData:
@ -160,16 +186,31 @@ class HERETransitDataUpdateCoordinator(DataUpdateCoordinator):
here_transit.Return.TRAVEL_SUMMARY,
],
)
_LOGGER.debug("Raw response is: %s", response)
return self._parse_transit_response(response)
except HERETransitTooManyRequestsError as error:
assert self.update_interval is not None
_LOGGER.debug(
"Rate limit has been reached. Increasing update interval to %s",
self.update_interval.total_seconds() * BACKOFF_MULTIPLIER,
)
self.update_interval = timedelta(
seconds=self.update_interval.total_seconds() * BACKOFF_MULTIPLIER
)
raise UpdateFailed("Rate limit has been reached") from error
except HERETransitDepartureArrivalTooCloseError:
_LOGGER.debug("Ignoring HERETransitDepartureArrivalTooCloseError")
return None
except (HERETransitConnectionError, HERETransitNoRouteFoundError) as error:
raise UpdateFailed from error
_LOGGER.debug("Raw response is: %s", response)
if self.update_interval != timedelta(seconds=DEFAULT_SCAN_INTERVAL):
_LOGGER.debug(
"Resetting update interval to %s",
DEFAULT_SCAN_INTERVAL,
)
self.update_interval = timedelta(seconds=DEFAULT_SCAN_INTERVAL)
return self._parse_transit_response(response)
def _parse_transit_response(self, response: dict[str, Any]) -> HERETravelTimeData:
"""Parse the transit response dict to a HERETravelTimeData."""
sections: list[dict[str, Any]] = response["routes"][0]["sections"]

View file

@ -3,7 +3,7 @@
"name": "HERE Travel Time",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/here_travel_time",
"requirements": ["here_routing==0.1.1", "here_transit==1.1.1"],
"requirements": ["here_routing==0.2.0", "here_transit==1.2.0"],
"codeowners": ["@eifinger"],
"iot_class": "cloud_polling",
"loggers": ["here_routing", "here_transit", "homeassistant.helpers.location"]

View file

@ -867,10 +867,10 @@ hdate==0.10.4
heatmiserV3==1.1.18
# homeassistant.components.here_travel_time
here_routing==0.1.1
here_routing==0.2.0
# homeassistant.components.here_travel_time
here_transit==1.1.1
here_transit==1.2.0
# homeassistant.components.hikvisioncam
hikvision==0.4

View file

@ -653,10 +653,10 @@ hatasmota==0.6.1
hdate==0.10.4
# homeassistant.components.here_travel_time
here_routing==0.1.1
here_routing==0.2.0
# homeassistant.components.here_travel_time
here_transit==1.1.1
here_transit==1.2.0
# homeassistant.components.hlk_sw16
hlk-sw16==0.0.9

View file

@ -1,8 +1,10 @@
"""The test for the HERE Travel Time sensor platform."""
from datetime import timedelta
from unittest.mock import MagicMock, patch
from here_routing import (
HERERoutingError,
HERERoutingTooManyRequestsError,
Place,
Return,
RoutingMode,
@ -13,6 +15,7 @@ from here_transit import (
HERETransitDepartureArrivalTooCloseError,
HERETransitNoRouteFoundError,
HERETransitNoTransitRouteFoundError,
HERETransitTooManyRequestsError,
)
import pytest
@ -27,6 +30,7 @@ from homeassistant.components.here_travel_time.const import (
CONF_ORIGIN_LATITUDE,
CONF_ORIGIN_LONGITUDE,
CONF_ROUTE_MODE,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
ICON_BICYCLE,
ICON_CAR,
@ -39,6 +43,7 @@ from homeassistant.components.here_travel_time.const import (
TRAVEL_MODE_PUBLIC,
TRAVEL_MODE_TRUCK,
)
from homeassistant.components.here_travel_time.coordinator import BACKOFF_MULTIPLIER
from homeassistant.components.sensor import (
ATTR_LAST_RESET,
ATTR_STATE_CLASS,
@ -59,7 +64,9 @@ from homeassistant.const import (
)
from homeassistant.core import CoreState, HomeAssistant, State
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow
from .conftest import RESPONSE, TRANSIT_RESPONSE
from .const import (
API_KEY,
DEFAULT_CONFIG,
@ -69,7 +76,11 @@ from .const import (
ORIGIN_LONGITUDE,
)
from tests.common import MockConfigEntry, mock_restore_cache_with_extra_data
from tests.common import (
MockConfigEntry,
async_fire_time_changed,
mock_restore_cache_with_extra_data,
)
@pytest.mark.parametrize(
@ -634,3 +645,107 @@ async def test_transit_errors(hass: HomeAssistant, caplog, exception, expected_m
await hass.async_block_till_done()
assert expected_message in caplog.text
async def test_routing_rate_limit(hass: HomeAssistant, caplog):
"""Test that rate limiting is applied when encountering HTTP 429."""
with patch(
"here_routing.HERERoutingApi.route",
return_value=RESPONSE,
):
entry = MockConfigEntry(
domain=DOMAIN,
unique_id="0123456789",
data=DEFAULT_CONFIG,
options=DEFAULT_OPTIONS,
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
assert hass.states.get("sensor.test_distance").state == "13.682"
with patch(
"here_routing.HERERoutingApi.route",
side_effect=HERERoutingTooManyRequestsError(
"Rate limit for this service has been reached"
),
):
async_fire_time_changed(
hass, utcnow() + timedelta(seconds=DEFAULT_SCAN_INTERVAL + 1)
)
await hass.async_block_till_done()
assert hass.states.get("sensor.test_distance").state == "unavailable"
assert "Increasing update interval to" in caplog.text
with patch(
"here_routing.HERERoutingApi.route",
return_value=RESPONSE,
):
async_fire_time_changed(
hass,
utcnow()
+ timedelta(seconds=DEFAULT_SCAN_INTERVAL * BACKOFF_MULTIPLIER + 1),
)
await hass.async_block_till_done()
assert hass.states.get("sensor.test_distance").state == "13.682"
assert "Resetting update interval to" in caplog.text
async def test_transit_rate_limit(hass: HomeAssistant, caplog):
"""Test that rate limiting is applied when encountering HTTP 429."""
with patch(
"here_transit.HERETransitApi.route",
return_value=TRANSIT_RESPONSE,
):
entry = MockConfigEntry(
domain=DOMAIN,
unique_id="0123456789",
data={
CONF_ORIGIN_LATITUDE: float(ORIGIN_LATITUDE),
CONF_ORIGIN_LONGITUDE: float(ORIGIN_LONGITUDE),
CONF_DESTINATION_LATITUDE: float(DESTINATION_LATITUDE),
CONF_DESTINATION_LONGITUDE: float(DESTINATION_LONGITUDE),
CONF_API_KEY: API_KEY,
CONF_MODE: TRAVEL_MODE_PUBLIC,
CONF_NAME: "test",
},
options=DEFAULT_OPTIONS,
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
await hass.async_block_till_done()
assert hass.states.get("sensor.test_distance").state == "1.883"
with patch(
"here_transit.HERETransitApi.route",
side_effect=HERETransitTooManyRequestsError(
"Rate limit for this service has been reached"
),
):
async_fire_time_changed(
hass, utcnow() + timedelta(seconds=DEFAULT_SCAN_INTERVAL + 1)
)
await hass.async_block_till_done()
assert hass.states.get("sensor.test_distance").state == "unavailable"
assert "Increasing update interval to" in caplog.text
with patch(
"here_transit.HERETransitApi.route",
return_value=TRANSIT_RESPONSE,
):
async_fire_time_changed(
hass,
utcnow()
+ timedelta(seconds=DEFAULT_SCAN_INTERVAL * BACKOFF_MULTIPLIER + 1),
)
await hass.async_block_till_done()
assert hass.states.get("sensor.test_distance").state == "1.883"
assert "Resetting update interval to" in caplog.text