171 lines
5.2 KiB
Python
171 lines
5.2 KiB
Python
"""Api for Withings."""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from collections.abc import Awaitable, Callable, Iterable
|
|
from typing import Any
|
|
|
|
import arrow
|
|
import requests
|
|
from withings_api import AbstractWithingsApi, DateType
|
|
from withings_api.common import (
|
|
GetSleepSummaryField,
|
|
MeasureGetMeasGroupCategory,
|
|
MeasureGetMeasResponse,
|
|
MeasureType,
|
|
NotifyAppli,
|
|
NotifyListResponse,
|
|
SleepGetSummaryResponse,
|
|
)
|
|
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.config_entry_oauth2_flow import (
|
|
AbstractOAuth2Implementation,
|
|
OAuth2Session,
|
|
)
|
|
|
|
from .const import LOGGER
|
|
|
|
_RETRY_COEFFICIENT = 0.5
|
|
|
|
|
|
class ConfigEntryWithingsApi(AbstractWithingsApi):
|
|
"""Withing API that uses HA resources."""
|
|
|
|
def __init__(
|
|
self,
|
|
hass: HomeAssistant,
|
|
config_entry: ConfigEntry,
|
|
implementation: AbstractOAuth2Implementation,
|
|
) -> None:
|
|
"""Initialize object."""
|
|
self._hass = hass
|
|
self.config_entry = config_entry
|
|
self._implementation = implementation
|
|
self.session = OAuth2Session(hass, config_entry, implementation)
|
|
|
|
def _request(
|
|
self, path: str, params: dict[str, Any], method: str = "GET"
|
|
) -> dict[str, Any]:
|
|
"""Perform an async request."""
|
|
asyncio.run_coroutine_threadsafe(
|
|
self.session.async_ensure_token_valid(), self._hass.loop
|
|
).result()
|
|
|
|
access_token = self.config_entry.data["token"]["access_token"]
|
|
response = requests.request(
|
|
method,
|
|
f"{self.URL}/{path}",
|
|
params=params,
|
|
headers={"Authorization": f"Bearer {access_token}"},
|
|
timeout=10,
|
|
)
|
|
return response.json()
|
|
|
|
async def _do_retry(self, func: Callable[[], Awaitable[Any]], attempts=3) -> Any:
|
|
"""Retry a function call.
|
|
|
|
Withings' API occasionally and incorrectly throws errors.
|
|
Retrying the call tends to work.
|
|
"""
|
|
exception = None
|
|
for attempt in range(1, attempts + 1):
|
|
LOGGER.debug("Attempt %s of %s", attempt, attempts)
|
|
try:
|
|
return await func()
|
|
except Exception as exception1: # pylint: disable=broad-except
|
|
LOGGER.debug(
|
|
"Failed attempt %s of %s (%s)", attempt, attempts, exception1
|
|
)
|
|
# Make each backoff pause a little bit longer
|
|
await asyncio.sleep(_RETRY_COEFFICIENT * attempt)
|
|
exception = exception1
|
|
continue
|
|
|
|
if exception:
|
|
raise exception
|
|
|
|
async def async_measure_get_meas(
|
|
self,
|
|
meastype: MeasureType | None = None,
|
|
category: MeasureGetMeasGroupCategory | None = None,
|
|
startdate: DateType | None = arrow.utcnow(),
|
|
enddate: DateType | None = arrow.utcnow(),
|
|
offset: int | None = None,
|
|
lastupdate: DateType | None = arrow.utcnow(),
|
|
) -> MeasureGetMeasResponse:
|
|
"""Get measurements."""
|
|
|
|
async def call_super() -> MeasureGetMeasResponse:
|
|
return await self._hass.async_add_executor_job(
|
|
self.measure_get_meas,
|
|
meastype,
|
|
category,
|
|
startdate,
|
|
enddate,
|
|
offset,
|
|
lastupdate,
|
|
)
|
|
|
|
return await self._do_retry(call_super)
|
|
|
|
async def async_sleep_get_summary(
|
|
self,
|
|
data_fields: Iterable[GetSleepSummaryField],
|
|
startdateymd: DateType | None = arrow.utcnow(),
|
|
enddateymd: DateType | None = arrow.utcnow(),
|
|
offset: int | None = None,
|
|
lastupdate: DateType | None = arrow.utcnow(),
|
|
) -> SleepGetSummaryResponse:
|
|
"""Get sleep data."""
|
|
|
|
async def call_super() -> SleepGetSummaryResponse:
|
|
return await self._hass.async_add_executor_job(
|
|
self.sleep_get_summary,
|
|
data_fields,
|
|
startdateymd,
|
|
enddateymd,
|
|
offset,
|
|
lastupdate,
|
|
)
|
|
|
|
return await self._do_retry(call_super)
|
|
|
|
async def async_notify_list(
|
|
self, appli: NotifyAppli | None = None
|
|
) -> NotifyListResponse:
|
|
"""List webhooks."""
|
|
|
|
async def call_super() -> NotifyListResponse:
|
|
return await self._hass.async_add_executor_job(self.notify_list, appli)
|
|
|
|
return await self._do_retry(call_super)
|
|
|
|
async def async_notify_subscribe(
|
|
self,
|
|
callbackurl: str,
|
|
appli: NotifyAppli | None = None,
|
|
comment: str | None = None,
|
|
) -> None:
|
|
"""Subscribe to webhook."""
|
|
|
|
async def call_super() -> None:
|
|
await self._hass.async_add_executor_job(
|
|
self.notify_subscribe, callbackurl, appli, comment
|
|
)
|
|
|
|
await self._do_retry(call_super)
|
|
|
|
async def async_notify_revoke(
|
|
self, callbackurl: str | None = None, appli: NotifyAppli | None = None
|
|
) -> None:
|
|
"""Revoke webhook."""
|
|
|
|
async def call_super() -> None:
|
|
await self._hass.async_add_executor_job(
|
|
self.notify_revoke, callbackurl, appli
|
|
)
|
|
|
|
await self._do_retry(call_super)
|