Initial commit.

master
josiah 2 years ago
parent f8da988313
commit 598aa6caf2

@ -6,14 +6,11 @@ repos:
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/pycqa/pylint
rev: v2.12.2
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.3
hooks:
- id: pylint
args:
- --max-line-length=80
- --ignore-imports=yes
- -d duplicate-code
- id: flake8
args: ["--max-line-length=140"]
- repo: https://github.com/psf/black
rev: 22.1.0
hooks:
@ -22,7 +19,6 @@ repos:
rev: v0.931
hooks:
- id: mypy
additional_dependencies: [pydantic] # add if use pydantic
- repo: https://github.com/PyCQA/isort
rev: 5.10.1
hooks:

@ -7,13 +7,14 @@ name = "pypi"
garminconnect = "*"
pyyaml = "*"
pandas = "*"
types-pyyaml = "*"
[dev-packages]
isort = "*"
mypy = "*"
pylint = "*"
pre-commit = "*"
black = "*"
flake8 = "*"
[requires]
python_version = "3.9"

160
Pipfile.lock generated

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "93665e9b5a20a7f8c12e9632f6bc260ae4734a090bbedd432fb532b7dba4854f"
"sha256": "5c76c118f2dac92a61783724dc656c0ab3175d8ea5bc77a8352f91274dc43f04"
},
"pipfile-spec": 6,
"requires": {
@ -25,11 +25,11 @@
},
"charset-normalizer": {
"hashes": [
"sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd",
"sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455"
"sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45",
"sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"
],
"markers": "python_version >= '3'",
"version": "==2.0.10"
"version": "==2.0.11"
},
"cloudscraper": {
"hashes": [
@ -193,6 +193,14 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"types-pyyaml": {
"hashes": [
"sha256:6ea4eefa8579e0ce022f785a62de2bcd647fad4a81df5cf946fd67e4b059920b",
"sha256:8b50294b55a9db89498cdc5a65b1b4545112b6cd1cf4465bd693d828b0282a17"
],
"index": "pypi",
"version": "==6.0.3"
},
"urllib3": {
"hashes": [
"sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed",
@ -203,14 +211,6 @@
}
},
"develop": {
"astroid": {
"hashes": [
"sha256:1efdf4e867d4d8ba4a9f6cf9ce07cd182c4c41de77f23814feb27ca93ca9d877",
"sha256:506daabe5edffb7e696ad82483ad0228245a9742ed7d2d8c9cdb31537decf9f6"
],
"markers": "python_full_version >= '3.6.2'",
"version": "==2.9.3"
},
"black": {
"hashes": [
"sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2",
@ -271,6 +271,14 @@
"markers": "python_version >= '3.7'",
"version": "==3.4.2"
},
"flake8": {
"hashes": [
"sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d",
"sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"
],
"index": "pypi",
"version": "==4.0.1"
},
"identify": {
"hashes": [
"sha256:233679e3f61a02015d4293dbccf16aa0e4996f868bd114688b8c124f18826706",
@ -287,49 +295,6 @@
"index": "pypi",
"version": "==5.10.1"
},
"lazy-object-proxy": {
"hashes": [
"sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7",
"sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a",
"sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c",
"sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc",
"sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f",
"sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09",
"sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442",
"sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e",
"sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029",
"sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61",
"sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb",
"sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0",
"sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35",
"sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42",
"sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1",
"sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad",
"sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443",
"sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd",
"sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9",
"sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148",
"sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38",
"sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55",
"sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36",
"sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a",
"sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b",
"sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44",
"sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6",
"sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69",
"sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4",
"sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84",
"sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de",
"sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28",
"sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c",
"sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1",
"sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8",
"sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b",
"sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"
],
"markers": "python_version >= '3.6'",
"version": "==1.7.1"
},
"mccabe": {
"hashes": [
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
@ -400,13 +365,21 @@
"index": "pypi",
"version": "==2.17.0"
},
"pylint": {
"pycodestyle": {
"hashes": [
"sha256:9d945a73640e1fec07ee34b42f5669b770c759acd536ec7b16d7e4b87a9c9ff9",
"sha256:daabda3f7ed9d1c60f52d563b1b854632fd90035bcf01443e234d3dc794e3b74"
"sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20",
"sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"
],
"index": "pypi",
"version": "==2.12.2"
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.8.0"
},
"pyflakes": {
"hashes": [
"sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c",
"sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.0"
},
"pyyaml": {
"hashes": [
@ -447,14 +420,6 @@
"index": "pypi",
"version": "==6.0"
},
"setuptools": {
"hashes": [
"sha256:2404879cda71495fc4d5cbc445ed52fdaddf352b36e40be8dcc63147cb4edabe",
"sha256:68eb94073fc486091447fcb0501efd6560a0e5a1839ba249e5ff3c4c93f05f90"
],
"markers": "python_version >= '3.7'",
"version": "==60.5.0"
},
"six": {
"hashes": [
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
@ -484,7 +449,7 @@
"sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e",
"sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"
],
"markers": "python_version < '3.10'",
"markers": "python_version >= '3.6'",
"version": "==4.0.1"
},
"virtualenv": {
@ -494,63 +459,6 @@
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==20.13.0"
},
"wrapt": {
"hashes": [
"sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179",
"sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096",
"sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374",
"sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df",
"sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185",
"sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785",
"sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7",
"sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909",
"sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918",
"sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33",
"sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068",
"sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829",
"sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af",
"sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79",
"sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce",
"sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc",
"sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36",
"sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade",
"sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca",
"sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32",
"sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125",
"sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e",
"sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709",
"sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f",
"sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b",
"sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb",
"sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb",
"sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489",
"sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640",
"sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb",
"sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851",
"sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d",
"sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44",
"sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13",
"sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2",
"sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb",
"sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b",
"sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9",
"sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755",
"sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c",
"sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a",
"sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf",
"sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3",
"sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229",
"sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e",
"sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de",
"sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554",
"sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10",
"sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80",
"sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056",
"sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==1.13.3"
}
}
}

@ -0,0 +1,198 @@
#!/usr/bin/env python3
"""export data I care about from garmin connect."""
import argparse
import datetime
import logging
import os
import sys
import typing
import pandas
import yaml # type: ignore
from garminconnect import (
Garmin,
GarminConnectAuthenticationError,
GarminConnectConnectionError,
GarminConnectTooManyRequestsError,
)
# Configure debug logging
logging.basicConfig(level=logging.WARN)
logger = logging.getLogger(__name__)
# configure credentials
credentials = yaml.safe_load(open("../credentials.yml"))
email = credentials["garmin"]["email"]
password = credentials["garmin"]["password"]
def resolvepath(path: str) -> str:
"""Resolves a path as absolutely as possible"""
return os.path.realpath(os.path.normpath(os.path.expanduser(path)))
@typing.no_type_check
def idb_excepthook(type, value, tb):
"""Call an interactive debugger in post-mortem mode
If you do "sys.excepthook = idb_excepthook", then an interactive debugger
will be spawned at an unhandled exception
"""
if hasattr(sys, "ps1") or not sys.stderr.isatty():
sys.__excepthook__(type, value, tb)
else:
import pdb
import traceback
traceback.print_exception(type, value, tb)
print
pdb.pm()
return None
def get_yesterday_user_statistics(path: str = "./") -> None:
"""pulls yesterday's user metrics from garmin. accepts optional path argument"""
today = datetime.date.today()
yesterday = today - datetime.timedelta(days=1)
yesterdays_year = yesterday.strftime("%Y")
yesterdays_month = yesterday.strftime("%m")
folder = f"{path}/{yesterdays_year}/{yesterdays_month}/"
try:
# setup the connection
api = Garmin(email, password)
api.login()
stats = api.get_stats_and_body(yesterday.isoformat())
spo_data = api.get_spo2_data(yesterday.isoformat())
max_data = api.get_max_metrics(yesterday.isoformat())
except (
GarminConnectConnectionError,
GarminConnectAuthenticationError,
GarminConnectTooManyRequestsError,
) as err:
logger.error("Error occurred during Garmin Connect communication: %s", err)
if not os.path.exists(folder):
os.makedirs(folder)
try:
stats = pandas.DataFrame.from_dict(stats)
spo_data = pandas.json_normalize(
spo_data
) # json normalize needed due to variable list length in dict.
max_data = pandas.DataFrame.from_dict(max_data)
stats.to_csv(f"{folder}{yesterday.strftime('%Y-%m-%d')}_summary_stats.csv")
spo_data.to_csv(f"{folder}{yesterday.strftime('%Y-%m-%d')}_summary_spo.csv")
max_data.to_csv(f"{folder}{yesterday.strftime('%Y-%m-%d')}_summary_max.csv")
except OSError:
logger.error("Unable to find folder for data: %s")
exit(
"Unable to create folder for data. Please review permissions and recite the incantation."
)
return None
def get_activity_info(path: str = ".") -> None:
"""Return activities since purchase time. If you want, you can be more specific, but I just default to "all".
I think I've captured all possible activity types.
A bunch of state happens here."""
today = datetime.date.today()
purchasetime = "2021-12-25"
try:
# setup the connection
api = Garmin(email, password)
api.login()
activity_types = [
"cycling",
"running",
"swimming",
"multi_sport",
"fitness_equipment",
"hiking",
"walking",
"other",
]
for workout in activity_types:
# Get activities data from startdate 'YYYY-MM-DD' to enddate 'YYYY-MM-DD'
activities = api.get_activities_by_date(purchasetime, today, workout)
# Download an Activity
for activity in activities:
activity_id = activity["activityId"]
start_date = datetime.datetime.strptime(
activity["startTimeLocal"], "%Y-%m-%d %H:%M:%S"
).strftime("%Y-%m-%d")
activity_year = datetime.datetime.strptime(
activity["startTimeLocal"], "%Y-%m-%d %H:%M:%S"
).strftime("%Y")
activity_month = datetime.datetime.strptime(
activity["startTimeLocal"], "%Y-%m-%d %H:%M:%S"
).strftime("%m")
folder = f"{path}/{activity_year}/{activity_month}/"
activity_type = activity["activityType"]["typeKey"]
file_name = f"{folder}{start_date}_{activity_type}_{activity_id}"
logger.info("api.download_activities(%s)", activity_id)
gpx_data = api.download_activity(activity_id, dl_fmt=api.ActivityDownloadFormat.GPX)
output_file = f"{str(file_name)}.gpx"
with open(output_file, "wb") as fb:
fb.write(gpx_data)
# TCX data is used for garmin shit only.
tcx_data = api.download_activity(activity_id, dl_fmt=api.ActivityDownloadFormat.TCX)
output_file = f"{str(file_name)}.tcx"
with open(output_file, "wb") as fb:
fb.write(tcx_data)
# FIT is another garmin only. what the fuck. This ZIP contains the .fit file, I assume due to some complicated edge case.
zip_data = api.download_activity(
activity_id, dl_fmt=api.ActivityDownloadFormat.ORIGINAL
)
output_file = f"{str(file_name)}.zip"
with open(output_file, "wb") as fb:
fb.write(zip_data)
# contains some nice data about the activity that's NOT in the GPX file
csv_data = api.download_activity(activity_id, dl_fmt=api.ActivityDownloadFormat.CSV)
output_file = f"{str(file_name)}.csv"
with open(output_file, "wb") as fb:
fb.write(csv_data)
except (
GarminConnectConnectionError,
GarminConnectAuthenticationError,
GarminConnectTooManyRequestsError,
) as err:
logger.error("Error occurred during Garmin Connect communication: %s", err)
return None
@typing.no_type_check
def main(*args, **kwargs):
parser = argparse.ArgumentParser(description="")
parser.add_argument("--debug", "-d", action="store_true", help="Include debugging output")
parsed = parser.parse_args()
if parsed.debug:
sys.excepthook = idb_excepthook
# where do you want things to go
data_path = resolvepath("/home/josiah/dhd/quantified_life/fitness")
if not os.path.exists(data_path):
exit("Unable to resolve path. Make sure your path exists and try again.")
get_yesterday_user_statistics(data_path)
get_activity_info(data_path)
return None
if __name__ == "__main__":
sys.exit(main(*sys.argv))

@ -10,6 +10,7 @@ Make sure this is run inside a git repo.
#+begin_src shell
pipenv shell
pipenv install -d isort mypy pylint pre-commit
#+end_src
#+NAME:Configure pre-commit
#+begin_src shell

Loading…
Cancel
Save