Open aaron-schroeder opened 1 year ago
One interesting wrinkle is figuring out whether I'll regain access in ~15 minutes or the next day, and planning retries accordingly. Currently, my retry scheme will definitely fail if the daily rate limit is hit, with a possibility of giving up on even a 15-minute rate limit. I don't necessarily mind giving up, but if it happens, I want it to happen loudly so the user knows their activities weren't added.
I'm a visual person and want to debug that way.
Start off with an endpoint that answers "is this app rate-limited?"
Looking under the hood of stravalib, it seems that:
TLDR: to access the state of the rate limits:
>>> client.protocol.rate_limiter.rules[0].rate_limits
{
'short': {
'usageFieldIndex': 0,
'usage': 1,
'limit': 100,
'time': 900,
'lastExceeded': None
},
'long': {
'usageFieldIndex': 1,
'usage': 581,
'limit': 1000,
'time': 86400,
'lastExceeded': None
}
}
I guess the question now that I can check the rate limit status of the response in multiple ways...what do I want to do w that info? Here are some scenarios that I may encounter during a large backfill of strava activities:
models.StravaImportStorage.iterkeys()
resp = client.get('/athlete/activities', page=page, per_page=200)
if len(activity_list := resp.json()) and isinstance(activity_list, list):
for activity_summary in activity_list:
yield activity_summary['id']
page += 1
num_requests = math.ceil(num_activities / 200)
models.StravaImportStorage.get_data()
iterkeys
:summary_resp = client.get(f'/activities/{key}', include_all_efforts=True)
stream_data = stream_resp.json()
# data['streams'] = json.dumps(stream_data).encode('utf-8')
data['streams_compressed'] = zlib.compress(json.dumps(stream_data).encode('utf-8'))
stream_resp = client.get(
f'/activities/{key}/streams/time,latlng,distance,altitude,'
f'velocity_smooth,heartrate,cadence,watts,temp,moving,grade_smooth')
stream_data = stream_resp.json()
# data['streams'] = json.dumps(stream_data).encode('utf-8')
data['streams_compressed'] = zlib.compress(json.dumps(stream_data).encode('utf-8'))
models.StravaImportStorage.validate_connection()
# TODO GH48: Handle rate limiting here and wherever else it occurs.
# logger.debug(f'Test connection to bucket {self.bucket} with prefix {self.prefix}')
client.get('/athlete')
# client.get_activities(limit=5)
StravaRateLimitMonitor
instance created within StravaApiClient.get()
will raise errors.views.show_strava_status
@strava.route('/status')
def show_strava_status():
# TODO: Find a way to RL display status without burning through
# a dummy request every time. I'm thinking it can be a database
# or redis entry.
if flag_set('ff_rename'):
from distilling_flask.io_storages.strava.util import StravaRateLimitMonitor
s = db.session.scalars(db.select(StravaImportStorage)).first()
response = s.get('/athlete')
monitor = StravaRateLimitMonitor(response)
# TODO: Finish
Currently, this can happen if a user is trying to add more activities at once than the rate limit allows, and generally if they are doing a lot of interacting with the app.
Update 4/7/23: A partial mitigation strategy is now in place on a branch - each api json response is saved in its entirety to the database on the first request.
In dev mode, I like the idea of just displaying dummy data (probably from the mock stravalib).
In production, I suppose I should handle things more gracefully: just messages when data is missing; potentially info about the rate limiting itself; a countdown on the page showing how many more requests are remaining.
Sub task: #55
Strava API request limits:
I'm thinking each activity import hits the strava api twice, meaning 300 imports per batch. Also I am ignoring the initial requests to get summary data in big batches.
Stravalib seems to keep track of some rate limits (re
stravalib.exc.RateLimitExceeded
). I want verify for myself that they work accurately and reflect the rate limits on my account.I want to see if there is specific, actionable information about rate limits in Stravalib's exceptions that I can use to make my retry scheme smarter. I just need to wait to tinker until I'm rate-limited.
I handle this in the very specific case of the batch-add-activity task. But it exists in lots of places where I haven't dealt with it.
Stravalib points of contact
Notes
Successful response example
Rate-limited response example
Rate-limited json response
b'{"message":"Rate Limit Exceeded","errors":[{"resource":"Application","field":"overall rate limit","code":"exceeded"}]}'