Added Withings API
This commit is contained in:
parent
d23ee1ce18
commit
33337cd1a2
102
personal_data/fetchers/withings.py
Normal file
102
personal_data/fetchers/withings.py
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
"""Withings API fetcher.
|
||||||
|
|
||||||
|
Supports downloading activity summary from the [Withings
|
||||||
|
API](https://developer.withings.com/api-reference/) using the [non-official
|
||||||
|
Withings API Python Client](https://pypi.org/project/withings-api/).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import withings_api
|
||||||
|
from withings_api.common import get_measure_value, MeasureType, CredentialsType
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
from collections.abc import Iterator
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import bs4
|
||||||
|
import requests_util
|
||||||
|
|
||||||
|
import personal_data.html_util
|
||||||
|
from personal_data import secrets
|
||||||
|
from personal_data.data import DeduplicateMode, Scraper
|
||||||
|
|
||||||
|
from .. import parse_util
|
||||||
|
import pickle
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CREDENTIALS_FILE = Path('secrets/withings_oath_creds')
|
||||||
|
|
||||||
|
def save_credentials(credentials: CredentialsType) -> None:
|
||||||
|
"""Save credentials to a file."""
|
||||||
|
logger.info("Saving credentials in: %s", CREDENTIALS_FILE)
|
||||||
|
with open(CREDENTIALS_FILE, "wb") as file_handle:
|
||||||
|
pickle.dump(credentials, file_handle)
|
||||||
|
|
||||||
|
|
||||||
|
def load_credentials() -> CredentialsType:
|
||||||
|
"""Load credentials from a file."""
|
||||||
|
logger.info("Using credentials saved in: %s", CREDENTIALS_FILE)
|
||||||
|
try:
|
||||||
|
with open(CREDENTIALS_FILE, "rb") as file_handle:
|
||||||
|
return pickle.load(file_handle)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class WithingsActivityScraper(Scraper):
|
||||||
|
dataset_name = 'withings_activity'
|
||||||
|
deduplicate_mode = DeduplicateMode.BY_ALL_COLUMNS
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def requires_cfscrape() -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def oauth_flow(self) -> CredentialsType:
|
||||||
|
if creds := load_credentials():
|
||||||
|
return creds
|
||||||
|
|
||||||
|
auth = withings_api.WithingsAuth(
|
||||||
|
client_id=secrets.WITHINGS_CLIENTID,
|
||||||
|
consumer_secret=secrets.WITHINGS_SECRET,
|
||||||
|
callback_uri=secrets.WITHINGS_CALLBACK_URI,
|
||||||
|
scope=(
|
||||||
|
withings_api.AuthScope.USER_ACTIVITY,
|
||||||
|
withings_api.AuthScope.USER_METRICS,
|
||||||
|
withings_api.AuthScope.USER_INFO,
|
||||||
|
withings_api.AuthScope.USER_SLEEP_EVENTS,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
authorize_url = auth.get_authorize_url()
|
||||||
|
|
||||||
|
subprocess.run(['firefox', '--new-tab', authorize_url])
|
||||||
|
credentials_code = input('Please insert your code here: ').strip()
|
||||||
|
|
||||||
|
creds = auth.get_credentials(credentials_code)
|
||||||
|
save_credentials(creds)
|
||||||
|
return creds
|
||||||
|
|
||||||
|
def scrape(self):
|
||||||
|
credentials = self.oauth_flow()
|
||||||
|
|
||||||
|
# Now you are ready to make calls for data.
|
||||||
|
api = withings_api.WithingsApi(credentials)
|
||||||
|
|
||||||
|
start = datetime.date.today() - datetime.timedelta(days = 200)
|
||||||
|
end = datetime.date.today()
|
||||||
|
|
||||||
|
activity_result = api.measure_get_activity(
|
||||||
|
startdateymd=start,
|
||||||
|
enddateymd=end,
|
||||||
|
)
|
||||||
|
for activity in activity_result.activities:
|
||||||
|
sample = dict(activity)
|
||||||
|
sample['date'] = activity.date.date()
|
||||||
|
del sample['timezone'], sample['is_tracker']
|
||||||
|
yield sample
|
||||||
|
del activity, sample
|
||||||
|
|
|
@ -40,3 +40,8 @@ MAILGUN_RECIPIENT = load_secret('MAILGUN_RECIPIENT')
|
||||||
JELLYFIN_URL = load_secret('JELLYFIN_URL')
|
JELLYFIN_URL = load_secret('JELLYFIN_URL')
|
||||||
JELLYFIN_USERNAME = load_secret('JELLYFIN_USERNAME')
|
JELLYFIN_USERNAME = load_secret('JELLYFIN_USERNAME')
|
||||||
JELLYFIN_PASSWORD = load_secret('JELLYFIN_PASSWORD')
|
JELLYFIN_PASSWORD = load_secret('JELLYFIN_PASSWORD')
|
||||||
|
|
||||||
|
# Withings
|
||||||
|
WITHINGS_CLIENTID = load_secret('WITHINGS_CLIENTID')
|
||||||
|
WITHINGS_SECRET = load_secret('WITHINGS_SECRET')
|
||||||
|
WITHINGS_CALLBACK_URI = load_secret('WITHINGS_CALLBACK_URI')
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import csv
|
import csv
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
|
import _csv
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
import typing
|
import typing
|
||||||
|
@ -180,7 +181,7 @@ def extend_csv_file(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dicts = load_csv_file(csv_file)
|
dicts = load_csv_file(csv_file)
|
||||||
except FileNotFoundError as e:
|
except (FileNotFoundError, _csv.Error) as e:
|
||||||
logger.info('Creating file: %s', csv_file)
|
logger.info('Creating file: %s', csv_file)
|
||||||
dicts = []
|
dicts = []
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user