-
Notifications
You must be signed in to change notification settings - Fork 29
Safe refresh #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Safe refresh #32
Changes from 7 commits
f7dc1c3
7e9f9ba
efe782a
3e23eb2
4c5e220
97f5b8e
802d7a0
89df0b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import unicode_literals | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ('fitapp', '0005_upgrade_oauth1_tokens_to_oauth2'), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name='userfitbit', | ||
| name='expires_at', | ||
| field=models.FloatField(default=0), | ||
| preserve_default=False, | ||
| ), | ||
| migrations.AddField( | ||
| model_name='userfitbit', | ||
| name='timezone', | ||
| field=models.CharField(default='UTC', max_length=128), | ||
| preserve_default=False, | ||
| ), | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,9 @@ class UserFitbit(models.Model): | |
| access_token = models.TextField() | ||
| auth_secret = models.TextField() | ||
| refresh_token = models.TextField() | ||
| # We will store the timestamp float number as it comes from Fitbit here | ||
| expires_at = models.FloatField() | ||
| timezone = models.CharField(max_length=128) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @brad I see that the migration file for the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @percyperez When I ran
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @brad Ah Cool 👍 |
||
|
|
||
| def __str__(self): | ||
| return self.user.__str__() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,22 +12,42 @@ | |
|
|
||
|
|
||
| logger = logging.getLogger(__name__) | ||
| LOCK_EXPIRE = 60 * 5 # Lock expires in 5 minutes | ||
| LOCK_EXPIRE = 60 * 5 # Lock expires in 5 minutes | ||
|
|
||
|
|
||
| def _hit_rate_limit(exc, task): | ||
| # We have hit the rate limit for the user, retry when it's reset, | ||
| # according to the reply from the failing API call | ||
| logger.debug('Rate limit reached, will try again in %s seconds' % | ||
| exc.retry_after_secs) | ||
| raise task.retry(exc=exc, countdown=exc.retry_after_secs) | ||
|
|
||
|
|
||
| def _generic_task_exception(exc, task_name): | ||
| logger.exception("Exception running task %s: %s" % (task_name, exc)) | ||
| raise Reject(exc, requeue=False) | ||
|
|
||
|
|
||
| @shared_task | ||
| def subscribe(fitbit_user, subscriber_id): | ||
| """ Subscribe to the user's fitbit data """ | ||
|
|
||
| fbusers = UserFitbit.objects.filter(fitbit_user=fitbit_user) | ||
| for fbuser in fbusers: | ||
| """ Subscribe the user and retrieve historical data for it """ | ||
| update_user_timezone.apply_async((fitbit_user,), countdown=1) | ||
| for fbuser in UserFitbit.objects.filter(fitbit_user=fitbit_user): | ||
| fb = utils.create_fitbit(**fbuser.get_user_data()) | ||
| try: | ||
| fb.subscription(fbuser.user.id, subscriber_id) | ||
| except: | ||
| exc = sys.exc_info()[1] | ||
| logger.exception("Error subscribing user: %s" % exc) | ||
| raise Reject(exc, requeue=False) | ||
| except HTTPTooManyRequests: | ||
| _hit_rate_limit(sys.exc_info()[1], subscribe) | ||
| except Exception: | ||
| _generic_task_exception(sys.exc_info()[1], 'subscribe') | ||
|
|
||
| # Create tasks for all data in all data types | ||
| for i, _type in enumerate(TimeSeriesDataType.objects.all()): | ||
| # Delay execution for a few seconds to speed up response Offset each | ||
| # call by 5 seconds so they don't bog down the server | ||
| get_time_series_data.apply_async( | ||
| (fitbit_user, _type.category, _type.resource,), | ||
| countdown=10 + (i * 5)) | ||
|
|
||
|
|
||
| @shared_task | ||
|
|
@@ -40,11 +60,10 @@ def unsubscribe(*args, **kwargs): | |
| if sub['ownerId'] == kwargs['user_id']: | ||
| fb.subscription(sub['subscriptionId'], sub['subscriberId'], | ||
| method="DELETE") | ||
| except: | ||
| exc = sys.exc_info()[1] | ||
| logger.exception("Error unsubscribing user: %s" % exc) | ||
| raise Reject(exc, requeue=False) | ||
|
|
||
| except HTTPTooManyRequests: | ||
| _hit_rate_limit(sys.exc_info()[1], unsubscribe) | ||
| except Exception: | ||
| _generic_task_exception(sys.exc_info()[1], 'unsubscribe') | ||
|
|
||
|
|
||
| @shared_task | ||
|
|
@@ -83,12 +102,7 @@ def get_time_series_data(fitbit_user, cat, resource, date=None): | |
| # Release the lock | ||
| cache.delete(lock_id) | ||
| except HTTPTooManyRequests: | ||
| # We have hit the rate limit for the user, retry when it's reset, | ||
| # according to the reply from the failing API call | ||
| e = sys.exc_info()[1] | ||
| logger.debug('Rate limit reached, will try again in %s seconds' % | ||
| e.retry_after_secs) | ||
| raise get_time_series_data.retry(exc=e, countdown=e.retry_after_secs) | ||
| _hit_rate_limit(sys.exc_info()[1], get_time_series_data) | ||
| except HTTPBadRequest: | ||
| # If the resource is elevation or floors, we are just getting this | ||
| # error because the data doesn't exist for this user, so we can ignore | ||
|
|
@@ -98,6 +112,22 @@ def get_time_series_data(fitbit_user, cat, resource, date=None): | |
| logger.exception("Exception updating data: %s" % exc) | ||
| raise Reject(exc, requeue=False) | ||
| except Exception: | ||
| exc = sys.exc_info()[1] | ||
| logger.exception("Exception updating data: %s" % exc) | ||
| raise Reject(exc, requeue=False) | ||
| _generic_task_exception(sys.exc_info()[1], 'get_time_series_data') | ||
|
|
||
|
|
||
| @shared_task | ||
| def update_user_timezone(fitbit_user): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
| """ Get the user's profile and update the timezone we have on file """ | ||
|
|
||
| fbusers = UserFitbit.objects.filter(fitbit_user=fitbit_user) | ||
| try: | ||
| for fbuser in fbusers: | ||
| fb = utils.create_fitbit(**fbuser.get_user_data()) | ||
| profile = fb.user_profile_get() | ||
| fbuser.timezone = profile['user']['timezone'] | ||
| fbuser.save() | ||
| utils.check_for_new_token(fbuser, fb.client.token) | ||
| except HTTPTooManyRequests: | ||
| _hit_rate_limit(sys.exc_info()[1], update_user_timezone) | ||
| except Exception: | ||
| _generic_task_exception(sys.exc_info()[1], 'update_user_timezone') | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brad What do you think about renaming this file to
0006_add_expires_timezone_fields.py?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@percyperez Oh yeah, of course!