From c4f562ff6afbd8f5f968e3d2cff8abd6a080ab48 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 21 Oct 2023 22:00:40 -0700 Subject: [PATCH] Reduce unnecessary fitbit RPCs on startup (#102504) * Reduce unnecessary fitbit RPCs on startup * Update comment about racing user profile rpcs --- homeassistant/components/fitbit/sensor.py | 19 +++++--- tests/components/fitbit/test_sensor.py | 54 +++++++++++++++++++---- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/fitbit/sensor.py b/homeassistant/components/fitbit/sensor.py index 17bd21544e0..45b8ea21b0e 100644 --- a/homeassistant/components/fitbit/sensor.py +++ b/homeassistant/components/fitbit/sensor.py @@ -1,7 +1,6 @@ """Support for the Fitbit API.""" from __future__ import annotations -import asyncio from collections.abc import Callable from dataclasses import dataclass import datetime @@ -620,10 +619,10 @@ async def async_setup_entry( data: FitbitData = hass.data[DOMAIN][entry.entry_id] api = data.api - # Note: This will only be one rpc since it will cache the user profile - (user_profile, unit_system) = await asyncio.gather( - api.async_get_user_profile(), api.async_get_unit_system() - ) + # These are run serially to reuse the cached user profile, not gathered + # to avoid two racing requests. + user_profile = await api.async_get_user_profile() + unit_system = await api.async_get_unit_system() fitbit_config = config_from_entry_data(entry.data) @@ -654,7 +653,7 @@ async def async_setup_entry( for description in resource_list if is_allowed_resource(description) ] - async_add_entities(entities, True) + async_add_entities(entities) if data.device_coordinator and is_allowed_resource(FITBIT_RESOURCE_BATTERY): async_add_entities( @@ -712,6 +711,14 @@ class FitbitSensor(SensorEntity): self._attr_available = True self._attr_native_value = self.entity_description.value_fn(result) + async def async_added_to_hass(self) -> None: + """When entity is added to hass.""" + await super().async_added_to_hass() + + # We do not ask for an update with async_add_entities() + # because it will update disabled entities. + self.async_schedule_update_ha_state(force_refresh=True) + class FitbitBatterySensor(CoordinatorEntity, SensorEntity): """Implementation of a Fitbit sensor.""" diff --git a/tests/components/fitbit/test_sensor.py b/tests/components/fitbit/test_sensor.py index 926b599dfb5..b54f154d406 100644 --- a/tests/components/fitbit/test_sensor.py +++ b/tests/components/fitbit/test_sensor.py @@ -538,15 +538,8 @@ async def test_settings_scope_config_entry( integration_setup: Callable[[], Awaitable[bool]], register_timeseries: Callable[[str, dict[str, Any]], None], ) -> None: - """Test heartrate sensors are enabled.""" + """Test device sensors are enabled.""" - for api_resource in ("activities/heart",): - register_timeseries( - api_resource, - timeseries_response( - api_resource.replace("/", "-"), {"restingHeartRate": "0"} - ), - ) assert await integration_setup() states = hass.states.async_all() @@ -617,6 +610,51 @@ async def test_sensor_update_failed_requires_reauth( assert flows[0]["step_id"] == "reauth_confirm" +@pytest.mark.parametrize( + ("scopes"), + [(["heartrate"])], +) +async def test_sensor_update_success( + hass: HomeAssistant, + setup_credentials: None, + integration_setup: Callable[[], Awaitable[bool]], + requests_mock: Mocker, +) -> None: + """Test API failure for a battery level sensor for devices.""" + + requests_mock.register_uri( + "GET", + TIMESERIES_API_URL_FORMAT.format(resource="activities/heart"), + [ + { + "status_code": HTTPStatus.OK, + "json": timeseries_response( + "activities-heart", {"restingHeartRate": "60"} + ), + }, + { + "status_code": HTTPStatus.OK, + "json": timeseries_response( + "activities-heart", {"restingHeartRate": "70"} + ), + }, + ], + ) + + assert await integration_setup() + + state = hass.states.get("sensor.resting_heart_rate") + assert state + assert state.state == "60" + + await async_update_entity(hass, "sensor.resting_heart_rate") + await hass.async_block_till_done() + + state = hass.states.get("sensor.resting_heart_rate") + assert state + assert state.state == "70" + + @pytest.mark.parametrize( ("scopes", "mock_devices"), [(["settings"], None)],