From 2fa3f436aa057e628a5e87eafabe0ecf080e11e8 Mon Sep 17 00:00:00 2001 From: Jess Johnson Date: Thu, 28 Apr 2016 16:47:34 -0700 Subject: [PATCH 1/6] Only allow one thread at a time to access use/update a token. --- fitapp/tasks.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/fitapp/tasks.py b/fitapp/tasks.py index 46ad9d3..7431a1b 100644 --- a/fitapp/tasks.py +++ b/fitapp/tasks.py @@ -5,6 +5,7 @@ from celery.exceptions import Ignore, Reject from dateutil import parser from django.core.cache import cache +from django.db import transaction from fitbit.exceptions import HTTPBadRequest, HTTPTooManyRequests from . import utils @@ -46,7 +47,6 @@ def unsubscribe(*args, **kwargs): raise Reject(exc, requeue=False) - @shared_task def get_time_series_data(fitbit_user, cat, resource, date=None): """ Get the user's time series data """ @@ -66,22 +66,27 @@ def get_time_series_data(fitbit_user, cat, resource, date=None): _type, fitbit_user, sdat)) raise Ignore() - fbusers = UserFitbit.objects.filter(fitbit_user=fitbit_user) - dates = {'base_date': 'today', 'period': 'max'} - if date: - dates = {'base_date': date, 'end_date': date} try: - for fbuser in fbusers: - data = utils.get_fitbit_data(fbuser, _type, **dates) - for datum in data: - # Create new record or update existing record - date = parser.parse(datum['dateTime']) - tsd, created = TimeSeriesData.objects.get_or_create( - user=fbuser.user, resource_type=_type, date=date) - tsd.value = datum['value'] - tsd.save() - # Release the lock - cache.delete(lock_id) + with transaction.atomic(): + # Block until we have exclusive update access to this UserFitbit, so + # that another process cannot step on us when we update tokens + fbusers = UserFitbit.objects.select_for_update().filter( + fitbit_user=fitbit_user) + dates = {'base_date': 'today', 'period': 'max'} + if date: + dates = {'base_date': date, 'end_date': date} + + for fbuser in fbusers: + data = utils.get_fitbit_data(fbuser, _type, **dates) + for datum in data: + # Create new record or update existing record + date = parser.parse(datum['dateTime']) + tsd, created = TimeSeriesData.objects.get_or_create( + user=fbuser.user, resource_type=_type, date=date) + tsd.value = datum['value'] + tsd.save() + # 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 From 49f1a992b0ce2658af481d8c3af7ffe3544e4ba5 Mon Sep 17 00:00:00 2001 From: Jess Johnson Date: Thu, 28 Apr 2016 17:19:53 -0700 Subject: [PATCH 2/6] Drop support for Django 1.4 --- tox.ini | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/tox.ini b/tox.ini index 55b55bd..da94218 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,9 @@ [tox] -envlist = pypy-1.9.X,pypy-1.8.X,pypy-1.7.X,pypy-1.4.X, +envlist = pypy-1.9.X,pypy-1.8.X,pypy-1.7.X, py35-trunk,py35-1.9.X, py34-trunk,py34-1.9.X,py34-1.8.X,py34-1.7.X, py33-1.8.X,py33-1.7.X, - py27-1.9.X,py27-1.8.X,py27-1.7.X,py27-1.4.X, - py26-1.4.X + py27-1.9.X,py27-1.8.X,py27-1.7.X [testenv] commands = {envpython} run_tests.py @@ -26,10 +25,6 @@ deps = django>=1.8,<1.9 deps = django>=1.7,<1.8 {[testenv]deps} -[django14] -deps = django>=1.4,<1.5 - {[testenv]deps} - [testenv:pypy-trunk] basepython = pypy deps = {[djangotrunk]deps} @@ -46,10 +41,6 @@ deps = {[django18]deps} basepython = pypy deps = {[django17]deps} -[testenv:pypy-1.4.X] -basepython = pypy -deps = {[django14]deps} - [testenv:py35-trunk] basepython = python3.5 deps = {[djangotrunk]deps} @@ -105,11 +96,3 @@ deps = {[django18]deps} [testenv:py27-1.7.X] basepython = python2.7 deps = {[django17]deps} - -[testenv:py27-1.4.X] -basepython = python2.7 -deps = {[django14]deps} - -[testenv:py26-1.4.X] -basepython = python2.6 -deps = {[django14]deps} From d3c9c40a8f2c2aa3dee6f9256edd1ad26ebd8a39 Mon Sep 17 00:00:00 2001 From: Jess Johnson Date: Thu, 28 Apr 2016 17:24:44 -0700 Subject: [PATCH 3/6] Travis doesn't try to test Django 1.4 --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4863a0d..57dd03a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ env: #- TOX_ENV=pypy-1.9.X #- TOX_ENV=pypy-1.8.X #- TOX_ENV=pypy-1.7.X - #- TOX_ENV=pypy-1.4.X - TOX_ENV=py35-trunk - TOX_ENV=py35-1.9.X - TOX_ENV=py34-trunk @@ -18,8 +17,6 @@ env: - TOX_ENV=py27-1.9.X - TOX_ENV=py27-1.8.X - TOX_ENV=py27-1.7.X - - TOX_ENV=py27-1.4.X - - TOX_ENV=py26-1.4.X install: - pip install coveralls - pip install tox From eccf9900c7778506d1691b18fb31e223413646fb Mon Sep 17 00:00:00 2001 From: Jess Johnson Date: Mon, 2 May 2016 16:34:43 -0700 Subject: [PATCH 4/6] Docs; bump version. --- AUTHORS | 1 + CHANGELOG.md | 5 +++++ docs/starting.rst | 4 ++++ fitapp/__init__.py | 2 +- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index a9dd39d..f17dcef 100644 --- a/AUTHORS +++ b/AUTHORS @@ -2,3 +2,4 @@ Rebecca Lovewell Dan Poirier Percy Perez Brad Pitcher +Jess Johnson \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bc8301..ac77060 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.2.3 (2016-05-02) +================== + +- More refresh token bugfixes + 0.2.2 (2016-03-30) ================== diff --git a/docs/starting.rst b/docs/starting.rst index 0dd6502..27ed437 100644 --- a/docs/starting.rst +++ b/docs/starting.rst @@ -44,3 +44,7 @@ Getting started 8. To get step data for a user from a web page, use the AJAX :py:func:`fitapp.views.get_steps` view. + +9. If you are using sqlite, you will want to create a celery configuration that + prevents the fitapp celery tasks from being executed concurrently. If you + are using any other database type, you can skip this step. diff --git a/fitapp/__init__.py b/fitapp/__init__.py index 7f88a6a..9a7da7a 100644 --- a/fitapp/__init__.py +++ b/fitapp/__init__.py @@ -1,2 +1,2 @@ "Django integration for python-fitbit" -__version__ = "0.2.2" +__version__ = "0.2.3" From 7af5a445131aab90c1066fe360359069ba7f4324 Mon Sep 17 00:00:00 2001 From: Jess Johnson Date: Tue, 3 May 2016 13:50:43 -0700 Subject: [PATCH 5/6] Don't test for coverage on docs. --- .coveragerc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index d36a464..84551e2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,2 @@ [run] -omit = fitapp/tests*,fitapp/migrations/*,fitapp/south_migrations/*,run_tests.py,*/site-packages/*,*/python?.?/*,*/pypy/*,.tox/*,/opt/python/* +omit = fitapp/tests*,fitapp/migrations/*,fitapp/south_migrations/*,run_tests.py,*/site-packages/*,*/python?.?/*,*/pypy/*,.tox/*,/opt/python/*,docs/*,*md,*txt,AUTHORS From 2b61557c2a6ccfc2260e34b4c00749da84b5c43e Mon Sep 17 00:00:00 2001 From: Jess Johnson Date: Tue, 3 May 2016 16:30:10 -0700 Subject: [PATCH 6/6] Run django.setup() after coverage is started. --- run_tests.py | 70 +++++------------------------------------------- test_settings.py | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 64 deletions(-) create mode 100644 test_settings.py diff --git a/run_tests.py b/run_tests.py index 2d5d046..cdf9c9e 100755 --- a/run_tests.py +++ b/run_tests.py @@ -1,66 +1,16 @@ #!/usr/bin/env python import coverage +import django import optparse import os import sys from django.conf import settings - +from django.test.utils import get_runner if not settings.configured: - settings.configure( - DATABASES={ - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': 'django_fitapp', - } - }, - INSTALLED_APPS=[ - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.messages', - 'django.contrib.sessions', - 'fitapp', - ], - SECRET_KEY='something-secret', - ROOT_URLCONF='fitapp.urls', - - FITAPP_CONSUMER_KEY='', - FITAPP_CONSUMER_SECRET='', - FITAPP_SUBSCRIBE=True, - FITAPP_SUBSCRIBER_ID=1, - - LOGGING = { - 'version': 1, - 'handlers': { - 'null': { - 'level': 'DEBUG', - 'class': '%s.NullHandler' % ( - 'logging' if sys.version_info[0:2] > (2,6) - else 'django.utils.log'), - }, - }, - 'loggers': { - 'fitapp.tasks': {'handlers': ['null'], 'level': 'DEBUG'}, - }, - }, - - TEMPLATES=[ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'APP_DIRS': True - }, - ], - - MIDDLEWARE_CLASSES = ( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - ) - ) - - -from django.test.utils import get_runner + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_settings") def run_tests(): @@ -73,15 +23,13 @@ def run_tests(): covlevel = int(options.coverage) if covlevel: - current_dir = os.path.dirname(os.path.abspath(__file__)) - if covlevel == 2: - branch = True - else: - branch = False + branch = covlevel == 2 cov = coverage.coverage(branch=branch, config_file='.coveragerc') cov.load() cov.start() + django.setup() + TestRunner = get_runner(settings) test_runner = TestRunner(verbosity=1, interactive=True, failfast=False) exit_val = test_runner.run_tests(tests) @@ -94,11 +42,5 @@ def run_tests(): sys.exit(exit_val) -import django -# In Django 1.7, we need to run setup first -if hasattr(django, 'setup'): - django.setup() - - if __name__ == '__main__': run_tests() diff --git a/test_settings.py b/test_settings.py new file mode 100644 index 0000000..45f4130 --- /dev/null +++ b/test_settings.py @@ -0,0 +1,50 @@ +import sys + + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': 'django_fitapp', + } +} +INSTALLED_APPS = [ + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.messages', + 'django.contrib.sessions', + 'fitapp', +] +SECRET_KEY = 'something-secret' +ROOT_URLCONF = 'fitapp.urls' + +FITAPP_CONSUMER_KEY = '' +FITAPP_CONSUMER_SECRET = '' +FITAPP_SUBSCRIBE = True +FITAPP_SUBSCRIBER_ID = 1 + +LOGGING = { + 'version': 1, + 'handlers': { + 'null': { + 'level': 'DEBUG', + 'class': '%s.NullHandler' % ( + 'logging' if sys.version_info[0:2] > (2,6) + else 'django.utils.log'), + }, + }, + 'loggers': { + 'fitapp.tasks': {'handlers': ['null'], 'level': 'DEBUG'}, + }, +} + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'APP_DIRS': True + }, +] + +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', +)