From 598aa6caf23d4015477a10bd69a17b07ea04b889 Mon Sep 17 00:00:00 2001 From: josiah Date: Sun, 30 Jan 2022 13:01:57 -0600 Subject: [PATCH] Initial commit. --- .pre-commit-config.yaml | 12 +-- Pipfile | 3 +- Pipfile.lock | 160 +++++++----------------------- fitness/get_garmin_data.py | 198 +++++++++++++++++++++++++++++++++++++ readme.org | 1 + 5 files changed, 239 insertions(+), 135 deletions(-) create mode 100644 fitness/get_garmin_data.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a6fc57b..1697c4c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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: diff --git a/Pipfile b/Pipfile index fe30bb9..74d4de2 100644 --- a/Pipfile +++ b/Pipfile @@ -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" diff --git a/Pipfile.lock b/Pipfile.lock index d6f77c9..5ca4a32 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -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" } } } diff --git a/fitness/get_garmin_data.py b/fitness/get_garmin_data.py new file mode 100644 index 0000000..bda83b7 --- /dev/null +++ b/fitness/get_garmin_data.py @@ -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)) diff --git a/readme.org b/readme.org index 6033038..e66a508 100644 --- a/readme.org +++ b/readme.org @@ -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