From cf9d68d0ac31f25c13e1a89135c2d8b2179978c6 Mon Sep 17 00:00:00 2001 From: Giles Malet Date: Sat, 30 Jan 2021 21:27:20 -0500 Subject: [PATCH 1/8] Save everything to a file for use in uploader.py --- strava_local_client.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/strava_local_client.py b/strava_local_client.py index 8864abb..fd73d49 100755 --- a/strava_local_client.py +++ b/strava_local_client.py @@ -16,6 +16,7 @@ import stravalib from flask import Flask, request +import os app = Flask(__name__) @@ -25,6 +26,8 @@ CLIENT_ID = None CLIENT_SECRET = None +TOKEN_FILE = "tokens.txt" + @app.route("/auth") def auth_callback(): code = request.args.get('code') @@ -32,9 +35,19 @@ def auth_callback(): client_id=CLIENT_ID, client_secret=CLIENT_SECRET, code=code - ) - return access_token['access_token'] + ) + + if os.path.isfile(TOKEN_FILE): + os.rename(TOKEN_FILE, TOKEN_FILE + '.bak') + with open(TOKEN_FILE, 'w') as output: + output.write('client' + '\t' + CLIENT_ID + '\n') + output.write('secret' + '\t' + CLIENT_SECRET + '\n') + output.write('refresh' + '\t' + access_token['refresh_token'] + '\n') + output.write('access' + '\t' + access_token['access_token'] + '\n') + output.write('expiry' + '\t' + str(access_token['expires_at']) + '\n') + + return access_token['access_token'] if __name__ == '__main__': import docopt From c3493929844e5d5eebd4bf5eb97608d93cbf8711 Mon Sep 17 00:00:00 2001 From: Giles Malet Date: Sat, 30 Jan 2021 22:15:58 -0500 Subject: [PATCH 2/8] Upload docs to mention auto-refresh of tokens. --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 71173bf..0c375f1 100644 --- a/README.md +++ b/README.md @@ -9,19 +9,18 @@ Borrows liberally from @anthonywu's [Strava API Experiment](https://github.com/a 1. **Get data from Runkeeper**
Next, you need to **get your data from Runkeeper.** Go to the Settings page, and look for "Export Data" near the bottom. Define your time range, wait a minute or two, and then click download. Unzip the file - the directory should have .gpx files for all of your GPS-tracked runs, and two spreadsheets - "measurements.csv" and "cardio_activities.csv". 1. **Navigate to folder**
Open a shell (accessed by using the "Terminal" application on MacOS) and `cd` to the data directory (the folder you just downloaded - should be something like "runkeeper-data-export-1234567"). 1. **Install requirements**
Install the requirements - from any shell run `pip install -r requirements.txt` -1. **Get authorization from Strava**
Next, we need to **get an Authorization Token from Strava** for your Athlete account. Run the command `python strava_local_client.py get_write_token ` where you replace `` and `` with the codes you pulled from the [Strava API Management Page](https://www.strava.com/settings/api). It should open a browser and ask you to log in to Strava. You should then be shown a code - copy this, and either: - 1. (Preferably) Set the environment variable `STRAVA_UPLOADER_TOKEN` before running `uploader.py`, e.g. `STRAVA_UPLOADER_TOKEN=my_token ./uploader.py` - 1. Or paste it in the `uploader.py` file as the `access_token` variable, replacing `None`. Don't forget to quote the variable, e.g. `access_token = "my_token"` -1. **Upload to Strava**
Now we're ready to upload. Run `STRAVA_UPLOADER_TOKEN=my_token ./uploader.py` and let it run! +1. **Get authorization from Strava**
Next, we need to **get an Authorization Token from Strava** for your Athlete account. Run the command `python strava_local_client.py get_write_token ` where you replace `` and `` with the codes you pulled from the [Strava API Management Page](https://www.strava.com/settings/api). It should open a browser and ask you to log in to Strava. You should then be shown a code, which is automatically saved with other data in a file called 'tokens.txt' in the current directory. This is used in the next step. +1. **Upload to Strava**
Now we're ready to upload. Run `./uploader.py` and let it run! Make sure 'tokens.txt' is in the current directory. **A few notes on how this works:** - The script will crawl through the cardio activities csv file line for line, uploading each event. - Right now it handles runs, rides, walks, swims, hikes and elliptical exercises. You can add more - be sure to grab the RunKeeper definition and the Strava definition and add to the `activity_translator` function. - If there is a GPX file listed in the last column, it will look for that file in the directory. If there is no GPX file, it will manually upload using the distance and duration data listed in the spreadsheet. -- Strava's API [rate-limits you to 600 requests every 15 minutes](https://www.strava.com/settings/api). The `uploader.py` script will automatically wait for 15 minutes when the upload count hits 599. This is probably too conservative - feel free to adjust. ***From May 2020 onwards**, newly created applications have a limit of 100 requests every 15 minutes.* +- Strava's API [rate-limits you to 600 requests every 15 minutes](https://www.strava.com/settings/api). The `uploader.py` script will automatically wait for 15 minutes when the upload count hits 599. This is probably too conservative - feel free to adjust. ***From May 2020 onwards**, newly created applications have a limit of 100 requests every 15 minutes. - It will move successfully uploaded GPX files to a sub-folder called archive. - It will try to catch various errors, and ignore duplicate files. - It will log everything in a file `strava-uploader.log`. +- If the program quits because it hit the daily upload limit, you can simply run `./uploader.py` again the next day (perhaps as a cron job) to continue. Needed tokens are refreshed and stored in the 'tokens.txt' file. ## Misc other notes: - Do NOT modify or even save (without modification) the CSV from Excel. Even if you just open it and save it with no modification, Excel changes the date formatting which will break this script. If you do need to modify the CSV for some reason (e.g., mine had a run with a missing distance, not clear why), do it in Sublime or another text editor. From 077fe35815f1381abcfc089217e7e1c6a91dfabb Mon Sep 17 00:00:00 2001 From: Giles Malet Date: Sat, 30 Jan 2021 22:28:50 -0500 Subject: [PATCH 3/8] Store and automatically update required tokens in tokens.txt file. --- uploader.py | 110 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 79 insertions(+), 31 deletions(-) diff --git a/uploader.py b/uploader.py index 5555daf..ef80c4c 100755 --- a/uploader.py +++ b/uploader.py @@ -17,15 +17,10 @@ # Access Token # # You need to run the strava_local_client.py script, with your application's ID and secret, -# to generate the access token. -# -# When you have the access token, you can -# (a) set an environment variable `STRAVA_UPLOADER_TOKEN` or; -# (b) replace `None` below with the token in quote marks, e.g. access_token = 'token' +# to generate the access and other tokens in the following token_file. ##################################### -access_token = None - cardio_file = 'cardioActivities.csv' +token_file = 'tokens.txt' archive_dir = 'archive' skip_dir = 'skipped' @@ -63,35 +58,85 @@ def get_cardio_file(): logger.error(cardio_file + ' file cannot be found') exit(1) -def get_strava_access_token(): - global access_token +def write_strava_access_tokens(tokens): + if os.path.isfile(token_file): + os.rename(token_file, token_file + '.bak') - if access_token is not None: - logger.info('Found access token') - return access_token + with open(token_file, 'w') as output: + for key, value in tokens.items(): + output.write(key + '\t' + str(value) + '\n') - access_token = os.environ.get('STRAVA_UPLOADER_TOKEN') - if access_token is not None: - logger.info('Found access token') - return access_token - logger.error('Access token not found. Please set the env variable STRAVA_UPLOADER_TOKEN') - exit(1) +def refresh_strava_access_tokens(client): + logger.info('Refreshing Strava access tokens') + + tokens = client.tokens + + refresh_response = client.refresh_access_token( + client_id=tokens['client'], + client_secret=tokens['secret'], + refresh_token=tokens['refresh']) + + tokens['access'] = refresh_response['access_token'] # this is a short-lived access token + tokens['refresh'] = refresh_response['refresh_token'] + tokens['expiry'] = refresh_response['expires_at'] + + client.access_token = tokens['access'] + client.token_expires_at = int(tokens['expiry']) - 20*60 # update 20 mins before actual expiry + + write_strava_access_tokens(tokens) + + logger.info('Refreshed Strava access token expires at local time ' + time.asctime(time.localtime(tokens['expiry']))) + + client.tokens = tokens + return client + + +def get_strava_access_token(client): + + tokens = dict() + + if os.path.isfile(token_file): + with open(token_file, "r") as token_input: + for linein in token_input: + line = linein.split() + token = line[0].casefold() + tokens[token] = line[1] + + if 'client' not in tokens or 'secret' not in tokens or 'refresh' not in tokens: + logger.info('Need client, secret and refresh tokens in the ' + token_file + ' file') + exit(1) + + if 'access' in tokens: + client.access_token = tokens['access'] + if 'expiry' in tokens: + client.token_expires_at = int(tokens['expiry']) - 20*60 # update 20 mins before actual expiry + else: + client.token_expires_at = 0 # force refrech + + client.tokens = tokens + + if time.time() > client.token_expires_at: + client = refresh_strava_access_tokens(client) + + return client def get_strava_client(): - token = get_strava_access_token() + rate_limiter = RateLimiter() rate_limiter.rules.append(XRateLimitRule( {'short': {'usageFieldIndex': 0, 'usage': 0, # 60s * 15 = 15 min - 'limit': 100, 'time': (60*15), - 'lastExceeded': None,}, + 'limit': 100, 'time': (60 * 15), + 'lastExceeded': None, }, 'long': {'usageFieldIndex': 1, 'usage': 0, # 60s * 60m * 24 = 1 day - 'limit': 1000, 'time': (60*60*24), + 'limit': 1000, 'time': (60 * 60 * 24), 'lastExceeded': None}})) + client = Client(rate_limiter=rate_limiter) - client.access_token = token + client = get_strava_access_token(client) + return client def archive_file(file): @@ -163,8 +208,7 @@ def upload_gpx(client, gpxfile, strava_activity_type, notes): if i > 0: logger.error("Daily Rate limit exceeded - exiting program") exit(1) - logger.warning("Rate limit exceeded in uploading - pausing uploads for 15 minutes to avoid rate-limit") - time.sleep(900) + wait_for_timeout(client) continue except ConnectionError as err: logger.error("No Internet connection: {}".format(err)) @@ -182,9 +226,7 @@ def upload_gpx(client, gpxfile, strava_activity_type, notes): if i > 0: logger.error("Daily Rate limit exceeded - exiting program") exit(1) - logger.warning( - "Rate limit exceeded in processing upload - pausing uploads for 15 minutes to avoid rate-limit") - time.sleep(900) + wait_for_timeout(client) continue except exc.ActivityUploadFailed as err: errStr = str(err) @@ -286,6 +328,13 @@ def miles_to_meters(miles): def km_to_meters(km): return float(km) * 1000 +def wait_for_timeout(client): + logger.warning("Rate limit exceeded - pausing transactions for 15 minutes to avoid rate-limit") + time.sleep(900) + if time.time() > client.token_expires_at: + client = refresh_strava_access_tokens(client) + return client + def main(): set_up_logger() @@ -301,8 +350,7 @@ def main(): if i > 0: logger.error("Daily Rate limit exceeded - exiting program") exit(1) - logger.warning("Rate limit exceeded in connecting - Retrying strava connection in 15 minutes") - time.sleep(900) + wait_for_timeout(client) continue break @@ -328,7 +376,7 @@ def main(): # if there is a gpx file listed, find it and upload it if ".gpx" in row['GPX File']: gpxfile = row['GPX File'] - strava_activity_type = activity_translator(str(row['Type'])) + strava_activity_type = row['Type'] #activity_translator(str(row['Type'])) if strava_activity_type is not None: if upload_gpx(client, gpxfile, strava_activity_type, row['Notes']): From 584499239c0c96260bbac9b32a462bf153442d9c Mon Sep 17 00:00:00 2001 From: Giles Malet Date: Mon, 1 Feb 2021 18:36:49 -0500 Subject: [PATCH 4/8] Sleep only the remainder of a 15 minute timeout. --- uploader.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/uploader.py b/uploader.py index ef80c4c..6185256 100755 --- a/uploader.py +++ b/uploader.py @@ -329,11 +329,21 @@ def km_to_meters(km): return float(km) * 1000 def wait_for_timeout(client): - logger.warning("Rate limit exceeded - pausing transactions for 15 minutes to avoid rate-limit") - time.sleep(900) + if wait_for_timeout.last_timeout == 0: # defined below. + sleeptime = 900 # first time through, else we sleep only the remainder of 15 minutes + else: + sleeptime = time.time() - wait_for_timeout.last_timeout + + logger.warning("Rate limit exceeded - pausing transactions for " + str(sleeptime) + " seconds to avoid rate-limit") + time.sleep(sleeptime) + + wait_for_timeout.last_timeout = time.time() + if time.time() > client.token_expires_at: client = refresh_strava_access_tokens(client) + return client +wait_for_timeout.last_timeout = 0 # ugh... track calls above def main(): set_up_logger() From 56f2bf8b8f1659bb2be1565a331d2b49c0f9e1cf Mon Sep 17 00:00:00 2001 From: Giles Malet Date: Mon, 1 Feb 2021 19:07:01 -0500 Subject: [PATCH 5/8] Minor cleanup in docs while waiting for test run.... --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0c375f1..7f35f51 100644 --- a/README.md +++ b/README.md @@ -9,22 +9,23 @@ Borrows liberally from @anthonywu's [Strava API Experiment](https://github.com/a 1. **Get data from Runkeeper**
Next, you need to **get your data from Runkeeper.** Go to the Settings page, and look for "Export Data" near the bottom. Define your time range, wait a minute or two, and then click download. Unzip the file - the directory should have .gpx files for all of your GPS-tracked runs, and two spreadsheets - "measurements.csv" and "cardio_activities.csv". 1. **Navigate to folder**
Open a shell (accessed by using the "Terminal" application on MacOS) and `cd` to the data directory (the folder you just downloaded - should be something like "runkeeper-data-export-1234567"). 1. **Install requirements**
Install the requirements - from any shell run `pip install -r requirements.txt` -1. **Get authorization from Strava**
Next, we need to **get an Authorization Token from Strava** for your Athlete account. Run the command `python strava_local_client.py get_write_token ` where you replace `` and `` with the codes you pulled from the [Strava API Management Page](https://www.strava.com/settings/api). It should open a browser and ask you to log in to Strava. You should then be shown a code, which is automatically saved with other data in a file called 'tokens.txt' in the current directory. This is used in the next step. -1. **Upload to Strava**
Now we're ready to upload. Run `./uploader.py` and let it run! Make sure 'tokens.txt' is in the current directory. +1. **Get authorization from Strava**
Next, we need to **get an Authorization Token from Strava** for your Athlete account. Run the command `python strava_local_client.py get_write_token ` where you replace `` and `` with the codes you pulled from the [Strava API Management Page](https://www.strava.com/settings/api). It should open a browser and ask you to log in to Strava. You should then be shown a code, which is automatically saved with other data in a file called `tokens.txt` in the current directory. This is used in the next step. +1. **Upload to Strava**
Now we're ready to upload. Run `./uploader.py` and let it run! Make sure `tokens.txt` is in the current directory. **A few notes on how this works:** - The script will crawl through the cardio activities csv file line for line, uploading each event. - Right now it handles runs, rides, walks, swims, hikes and elliptical exercises. You can add more - be sure to grab the RunKeeper definition and the Strava definition and add to the `activity_translator` function. - If there is a GPX file listed in the last column, it will look for that file in the directory. If there is no GPX file, it will manually upload using the distance and duration data listed in the spreadsheet. -- Strava's API [rate-limits you to 600 requests every 15 minutes](https://www.strava.com/settings/api). The `uploader.py` script will automatically wait for 15 minutes when the upload count hits 599. This is probably too conservative - feel free to adjust. ***From May 2020 onwards**, newly created applications have a limit of 100 requests every 15 minutes. +- Strava's API [rate-limits you to 600 requests every 15 minutes](https://www.strava.com/settings/api). The `uploader.py` script will automatically wait for 15 minutes when the upload count hits 599. This is probably too conservative - feel free to adjust. ***From May 2020 onwards**, newly created applications have a limit of 100 requests every 15 minutes.* - It will move successfully uploaded GPX files to a sub-folder called archive. - It will try to catch various errors, and ignore duplicate files. - It will log everything in a file `strava-uploader.log`. -- If the program quits because it hit the daily upload limit, you can simply run `./uploader.py` again the next day (perhaps as a cron job) to continue. Needed tokens are refreshed and stored in the 'tokens.txt' file. +- If the program quits because it hit the daily upload limit, you can simply run `./uploader.py` again the next day (perhaps as a cron job) to continue. Needed tokens are refreshed and stored in the `tokens.txt` file. ## Misc other notes: - Do NOT modify or even save (without modification) the CSV from Excel. Even if you just open it and save it with no modification, Excel changes the date formatting which will break this script. If you do need to modify the CSV for some reason (e.g., mine had a run with a missing distance, not clear why), do it in Sublime or another text editor. - I personally ran into a few errors of "malformed GPX files". You can try opening the file in a text editor and looking for issues - look for missing closure tags (e.g., ``) - that was the issue with one of my files. You could also try to use other solutions - some ideas that solved other issues [here](https://support.strava.com/hc/en-us/articles/216942247-How-to-Fix-GPX-File-Errors). + ## Updates specific to this branch You can use this script to upload a non-Runkeeper file in CSV format. The current Runkeeper CSV file format includes the following columns: Activity Id, Date,Type, Route Name, Distance (mi), Duration, Average Pace, Average Speed (mph), Calories Burned, Climb (ft), Average Heart Rate (bpm), Friend's Tagged, Notes, GPX File. If you wish to upload a non-Runkeeper file you have to create a cardioActivities.csv in this folder containing at least the following columns: Activity Id, Date, Type, Distance (mi), Duration. The non-Runkeeper file must have matching column names to the Runkeeper original! The GPX file if included should be a filename located in the same folder. From 361d3182dcea126496d4ac4bc03cc61faceab522 Mon Sep 17 00:00:00 2001 From: Giles Malet Date: Mon, 1 Feb 2021 18:42:06 -0500 Subject: [PATCH 6/8] Undo local change that should remain in prod. Cleanup docs. --- README.md | 9 +++++---- uploader.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0c375f1..7f35f51 100644 --- a/README.md +++ b/README.md @@ -9,22 +9,23 @@ Borrows liberally from @anthonywu's [Strava API Experiment](https://github.com/a 1. **Get data from Runkeeper**
Next, you need to **get your data from Runkeeper.** Go to the Settings page, and look for "Export Data" near the bottom. Define your time range, wait a minute or two, and then click download. Unzip the file - the directory should have .gpx files for all of your GPS-tracked runs, and two spreadsheets - "measurements.csv" and "cardio_activities.csv". 1. **Navigate to folder**
Open a shell (accessed by using the "Terminal" application on MacOS) and `cd` to the data directory (the folder you just downloaded - should be something like "runkeeper-data-export-1234567"). 1. **Install requirements**
Install the requirements - from any shell run `pip install -r requirements.txt` -1. **Get authorization from Strava**
Next, we need to **get an Authorization Token from Strava** for your Athlete account. Run the command `python strava_local_client.py get_write_token ` where you replace `` and `` with the codes you pulled from the [Strava API Management Page](https://www.strava.com/settings/api). It should open a browser and ask you to log in to Strava. You should then be shown a code, which is automatically saved with other data in a file called 'tokens.txt' in the current directory. This is used in the next step. -1. **Upload to Strava**
Now we're ready to upload. Run `./uploader.py` and let it run! Make sure 'tokens.txt' is in the current directory. +1. **Get authorization from Strava**
Next, we need to **get an Authorization Token from Strava** for your Athlete account. Run the command `python strava_local_client.py get_write_token ` where you replace `` and `` with the codes you pulled from the [Strava API Management Page](https://www.strava.com/settings/api). It should open a browser and ask you to log in to Strava. You should then be shown a code, which is automatically saved with other data in a file called `tokens.txt` in the current directory. This is used in the next step. +1. **Upload to Strava**
Now we're ready to upload. Run `./uploader.py` and let it run! Make sure `tokens.txt` is in the current directory. **A few notes on how this works:** - The script will crawl through the cardio activities csv file line for line, uploading each event. - Right now it handles runs, rides, walks, swims, hikes and elliptical exercises. You can add more - be sure to grab the RunKeeper definition and the Strava definition and add to the `activity_translator` function. - If there is a GPX file listed in the last column, it will look for that file in the directory. If there is no GPX file, it will manually upload using the distance and duration data listed in the spreadsheet. -- Strava's API [rate-limits you to 600 requests every 15 minutes](https://www.strava.com/settings/api). The `uploader.py` script will automatically wait for 15 minutes when the upload count hits 599. This is probably too conservative - feel free to adjust. ***From May 2020 onwards**, newly created applications have a limit of 100 requests every 15 minutes. +- Strava's API [rate-limits you to 600 requests every 15 minutes](https://www.strava.com/settings/api). The `uploader.py` script will automatically wait for 15 minutes when the upload count hits 599. This is probably too conservative - feel free to adjust. ***From May 2020 onwards**, newly created applications have a limit of 100 requests every 15 minutes.* - It will move successfully uploaded GPX files to a sub-folder called archive. - It will try to catch various errors, and ignore duplicate files. - It will log everything in a file `strava-uploader.log`. -- If the program quits because it hit the daily upload limit, you can simply run `./uploader.py` again the next day (perhaps as a cron job) to continue. Needed tokens are refreshed and stored in the 'tokens.txt' file. +- If the program quits because it hit the daily upload limit, you can simply run `./uploader.py` again the next day (perhaps as a cron job) to continue. Needed tokens are refreshed and stored in the `tokens.txt` file. ## Misc other notes: - Do NOT modify or even save (without modification) the CSV from Excel. Even if you just open it and save it with no modification, Excel changes the date formatting which will break this script. If you do need to modify the CSV for some reason (e.g., mine had a run with a missing distance, not clear why), do it in Sublime or another text editor. - I personally ran into a few errors of "malformed GPX files". You can try opening the file in a text editor and looking for issues - look for missing closure tags (e.g., ``) - that was the issue with one of my files. You could also try to use other solutions - some ideas that solved other issues [here](https://support.strava.com/hc/en-us/articles/216942247-How-to-Fix-GPX-File-Errors). + ## Updates specific to this branch You can use this script to upload a non-Runkeeper file in CSV format. The current Runkeeper CSV file format includes the following columns: Activity Id, Date,Type, Route Name, Distance (mi), Duration, Average Pace, Average Speed (mph), Calories Burned, Climb (ft), Average Heart Rate (bpm), Friend's Tagged, Notes, GPX File. If you wish to upload a non-Runkeeper file you have to create a cardioActivities.csv in this folder containing at least the following columns: Activity Id, Date, Type, Distance (mi), Duration. The non-Runkeeper file must have matching column names to the Runkeeper original! The GPX file if included should be a filename located in the same folder. diff --git a/uploader.py b/uploader.py index 6185256..7b952ee 100755 --- a/uploader.py +++ b/uploader.py @@ -386,7 +386,7 @@ def main(): # if there is a gpx file listed, find it and upload it if ".gpx" in row['GPX File']: gpxfile = row['GPX File'] - strava_activity_type = row['Type'] #activity_translator(str(row['Type'])) + strava_activity_type = activity_translator(str(row['Type'])) if strava_activity_type is not None: if upload_gpx(client, gpxfile, strava_activity_type, row['Notes']): From df2f0bbbb028b1bb3939a267916ca12bd071394e Mon Sep 17 00:00:00 2001 From: Giles Malet Date: Mon, 1 Feb 2021 20:00:29 -0500 Subject: [PATCH 7/8] Fix bug with shortened sleep times. --- uploader.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/uploader.py b/uploader.py index 6185256..0e61ab9 100755 --- a/uploader.py +++ b/uploader.py @@ -329,21 +329,20 @@ def km_to_meters(km): return float(km) * 1000 def wait_for_timeout(client): - if wait_for_timeout.last_timeout == 0: # defined below. - sleeptime = 900 # first time through, else we sleep only the remainder of 15 minutes - else: - sleeptime = time.time() - wait_for_timeout.last_timeout + sleeptime = 900 - (int(time.time()) - wait_for_timeout.last_timeout) + if sleeptime < 60: + sleeptime = 60 logger.warning("Rate limit exceeded - pausing transactions for " + str(sleeptime) + " seconds to avoid rate-limit") time.sleep(sleeptime) - wait_for_timeout.last_timeout = time.time() + wait_for_timeout.last_timeout = int(time.time()) if time.time() > client.token_expires_at: client = refresh_strava_access_tokens(client) return client -wait_for_timeout.last_timeout = 0 # ugh... track calls above +wait_for_timeout.last_timeout = int(time.time()) # ugh... track calls above def main(): set_up_logger() From dae082e2c32eeb67fab349e35a4407c2d960bce1 Mon Sep 17 00:00:00 2001 From: Giles Malet Date: Mon, 1 Feb 2021 21:29:09 -0500 Subject: [PATCH 8/8] Don't be clever with sleep times. Seems Strava means "15 minutes from the last activiy", rather than dividing an hour into 15 minute pieces, and limiting each. So just do the 15 minute wait each time a timeout is required. --- uploader.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/uploader.py b/uploader.py index 44983bb..430cad5 100755 --- a/uploader.py +++ b/uploader.py @@ -116,7 +116,7 @@ def get_strava_access_token(client): client.tokens = tokens - if time.time() > client.token_expires_at: + if int(time.time()) > client.token_expires_at: client = refresh_strava_access_tokens(client) return client @@ -329,20 +329,15 @@ def km_to_meters(km): return float(km) * 1000 def wait_for_timeout(client): - sleeptime = 900 - (int(time.time()) - wait_for_timeout.last_timeout) - if sleeptime < 60: - sleeptime = 60 + sleeptime = 15 # minutes - logger.warning("Rate limit exceeded - pausing transactions for " + str(sleeptime) + " seconds to avoid rate-limit") - time.sleep(sleeptime) + logger.warning("Rate limit exceeded - pausing transactions for " + str(sleeptime) + " minutes to avoid rate-limit") + time.sleep(sleeptime*60) - wait_for_timeout.last_timeout = int(time.time()) - - if time.time() > client.token_expires_at: + if int(time.time()) > client.token_expires_at: client = refresh_strava_access_tokens(client) return client -wait_for_timeout.last_timeout = int(time.time()) # ugh... track calls above def main(): set_up_logger()