Update fitbit client to use asyncio (#100933)

This commit is contained in:
Allen Porter 2023-09-26 07:50:42 -07:00 committed by GitHub
parent bd40cbcb21
commit 822251a642
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 36 additions and 16 deletions

View File

@ -1,7 +1,7 @@
"""API for fitbit bound to Home Assistant OAuth.""" """API for fitbit bound to Home Assistant OAuth."""
import logging import logging
from typing import Any from typing import Any, cast
from fitbit import Fitbit from fitbit import Fitbit
@ -34,10 +34,12 @@ class FitbitApi:
"""Property to expose the underlying client library.""" """Property to expose the underlying client library."""
return self._client return self._client
def get_user_profile(self) -> FitbitProfile: async def async_get_user_profile(self) -> FitbitProfile:
"""Return the user profile from the API.""" """Return the user profile from the API."""
if self._profile is None: if self._profile is None:
response: dict[str, Any] = self._client.user_profile_get() response: dict[str, Any] = await self._hass.async_add_executor_job(
self._client.user_profile_get
)
_LOGGER.debug("user_profile_get=%s", response) _LOGGER.debug("user_profile_get=%s", response)
profile = response["user"] profile = response["user"]
self._profile = FitbitProfile( self._profile = FitbitProfile(
@ -47,7 +49,7 @@ class FitbitApi:
) )
return self._profile return self._profile
def get_unit_system(self) -> FitbitUnitSystem: async def async_get_unit_system(self) -> FitbitUnitSystem:
"""Get the unit system to use when fetching timeseries. """Get the unit system to use when fetching timeseries.
This is used in a couple ways. The first is to determine the request This is used in a couple ways. The first is to determine the request
@ -62,16 +64,18 @@ class FitbitApi:
return self._unit_system return self._unit_system
# Use units consistent with the account user profile or fallback to the # Use units consistent with the account user profile or fallback to the
# home assistant unit settings. # home assistant unit settings.
profile = self.get_user_profile() profile = await self.async_get_user_profile()
if profile.locale == FitbitUnitSystem.EN_GB: if profile.locale == FitbitUnitSystem.EN_GB:
return FitbitUnitSystem.EN_GB return FitbitUnitSystem.EN_GB
if self._hass.config.units is METRIC_SYSTEM: if self._hass.config.units is METRIC_SYSTEM:
return FitbitUnitSystem.METRIC return FitbitUnitSystem.METRIC
return FitbitUnitSystem.EN_US return FitbitUnitSystem.EN_US
def get_devices(self) -> list[FitbitDevice]: async def async_get_devices(self) -> list[FitbitDevice]:
"""Return available devices.""" """Return available devices."""
devices: list[dict[str, str]] = self._client.get_devices() devices: list[dict[str, str]] = await self._hass.async_add_executor_job(
self._client.get_devices
)
_LOGGER.debug("get_devices=%s", devices) _LOGGER.debug("get_devices=%s", devices)
return [ return [
FitbitDevice( FitbitDevice(
@ -84,13 +88,18 @@ class FitbitApi:
for device in devices for device in devices
] ]
def get_latest_time_series(self, resource_type: str) -> dict[str, Any]: async def async_get_latest_time_series(self, resource_type: str) -> dict[str, Any]:
"""Return the most recent value from the time series for the specified resource type.""" """Return the most recent value from the time series for the specified resource type."""
# Set request header based on the configured unit system # Set request header based on the configured unit system
self._client.system = self.get_unit_system() self._client.system = await self.async_get_unit_system()
response: dict[str, Any] = self._client.time_series(resource_type, period="7d") def _time_series() -> dict[str, Any]:
return cast(
dict[str, Any], self._client.time_series(resource_type, period="7d")
)
response: dict[str, Any] = await self._hass.async_add_executor_job(_time_series)
_LOGGER.debug("time_series(%s)=%s", resource_type, response) _LOGGER.debug("time_series(%s)=%s", resource_type, response)
key = resource_type.replace("/", "-") key = resource_type.replace("/", "-")
dated_results: list[dict[str, Any]] = response[key] dated_results: list[dict[str, Any]] = response[key]

View File

@ -1,6 +1,7 @@
"""Support for the Fitbit API.""" """Support for the Fitbit API."""
from __future__ import annotations from __future__ import annotations
import asyncio
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
import datetime import datetime
@ -518,8 +519,12 @@ def setup_platform(
authd_client.client.refresh_token() authd_client.client.refresh_token()
api = FitbitApi(hass, authd_client, config[CONF_UNIT_SYSTEM]) api = FitbitApi(hass, authd_client, config[CONF_UNIT_SYSTEM])
user_profile = api.get_user_profile() user_profile = asyncio.run_coroutine_threadsafe(
unit_system = api.get_unit_system() api.async_get_user_profile(), hass.loop
).result()
unit_system = asyncio.run_coroutine_threadsafe(
api.async_get_unit_system(), hass.loop
).result()
clock_format = config[CONF_CLOCK_FORMAT] clock_format = config[CONF_CLOCK_FORMAT]
monitored_resources = config[CONF_MONITORED_RESOURCES] monitored_resources = config[CONF_MONITORED_RESOURCES]
@ -539,7 +544,10 @@ def setup_platform(
if description.key in monitored_resources if description.key in monitored_resources
] ]
if "devices/battery" in monitored_resources: if "devices/battery" in monitored_resources:
devices = api.get_devices() devices = asyncio.run_coroutine_threadsafe(
api.async_get_devices(),
hass.loop,
).result()
entities.extend( entities.extend(
[ [
FitbitSensor( FitbitSensor(
@ -708,21 +716,24 @@ class FitbitSensor(SensorEntity):
return attrs return attrs
def update(self) -> None: async def async_update(self) -> None:
"""Get the latest data from the Fitbit API and update the states.""" """Get the latest data from the Fitbit API and update the states."""
resource_type = self.entity_description.key resource_type = self.entity_description.key
if resource_type == "devices/battery" and self.device is not None: if resource_type == "devices/battery" and self.device is not None:
device_id = self.device.id device_id = self.device.id
registered_devs: list[FitbitDevice] = self.api.get_devices() registered_devs: list[FitbitDevice] = await self.api.async_get_devices()
self.device = next( self.device = next(
device for device in registered_devs if device.id == device_id device for device in registered_devs if device.id == device_id
) )
self._attr_native_value = self.device.battery self._attr_native_value = self.device.battery
else: else:
result = self.api.get_latest_time_series(resource_type) result = await self.api.async_get_latest_time_series(resource_type)
self._attr_native_value = self.entity_description.value_fn(result) self._attr_native_value = self.entity_description.value_fn(result)
self.hass.async_add_executor_job(self._update_token)
def _update_token(self) -> None:
token = self.api.client.client.session.token token = self.api.client.client.session.token
config_contents = { config_contents = {
ATTR_ACCESS_TOKEN: token.get("access_token"), ATTR_ACCESS_TOKEN: token.get("access_token"),