[med-svn] [python-fitbit] 01/04: Imported Upstream version 0.1.0

Iain Learmonth irl-guest at moszumanska.debian.org
Tue Jun 3 19:45:18 UTC 2014


This is an automated email from the git hooks/post-receive script.

irl-guest pushed a commit to branch master
in repository python-fitbit.

commit 43d778899c5d45decec2c304e9bee76ef7a61cdc
Author: Iain R. Learmonth <irl at fsfe.org>
Date:   Tue Jun 3 18:32:01 2014 +0100

    Imported Upstream version 0.1.0
---
 .gitignore                                      |   6 +
 .travis.yml                                     |  17 +-
 AUTHORS                                         |   1 +
 MANIFEST.in                                     |   2 +-
 README.md                                       |  15 +
 README.rst                                      |  14 -
 docs/conf.py                                    |  12 +-
 docs/index.rst                                  |   4 +-
 fitbit/__init__.py                              |   4 +-
 fitbit/api.py                                   | 438 ++++++++++++++++++------
 fitbit/exceptions.py                            |   7 +-
 fitbit_tests/__init__.py                        |   6 +-
 fitbit_tests/base.py                            |  18 -
 fitbit_tests/test_api.py                        | 124 ++++++-
 fitbit_tests/test_auth.py                       | 102 ++----
 fitbit_tests/test_exceptions.py                 |  15 +-
 fitbit/gather_keys_cli.py => gather_keys_cli.py |  80 ++---
 requirements.txt                                |   4 -
 requirements/base.txt                           |   2 +
 requirements/dev.txt                            |   5 +
 requirements/test.txt                           |   2 +
 requirements_dev.txt                            |   4 -
 requirements_test.txt                           |   1 -
 setup.py                                        |  13 +-
 tox.ini                                         |  24 ++
 25 files changed, 625 insertions(+), 295 deletions(-)

diff --git a/.gitignore b/.gitignore
index 3649edf..b629af3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,14 @@
 *.pyc
 *.DS_Store
+.coverage
+.tox
 *~
 docs/_build
 *.egg-info
 *.egg
 dist
 build
+env
+
+# Editors
+.idea
diff --git a/.travis.yml b/.travis.yml
index e8cd8a0..f1c6347 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,12 @@
 language: python
-
-python:
-  - 2.6
-  - 2.7
-
-script: python setup.py test
+python: 3.3
+env:
+  - TOX_ENV=pypy
+  - TOX_ENV=py33
+  - TOX_ENV=py32
+  - TOX_ENV=py27
+  - TOX_ENV=py26
+install:
+  - pip install coveralls tox
+script: tox -e  $TOX_ENV
+after_success: coveralls
diff --git a/AUTHORS b/AUTHORS
index e833c69..835f236 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -4,3 +4,4 @@ Rebecca Lovewell (Caktus Consulting Group)
 Dan Poirier (Caktus Consulting Group)
 Brad Pitcher (ORCAS)
 Silvio Tomatis
+Steven Skoczen
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
index 44a59eb..8bd82f5 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1 +1 @@
-include LICENSE AUTHORS README.rst requirements* docs/*
+include LICENSE AUTHORS README.md requirements/* docs/*
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d88db85
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+python-fitbit
+=============
+
+[![Build Status](https://travis-ci.org/orcasgit/python-fitbit.png?branch=master)](https://travis-ci.org/orcasgit/python-fitbit)
+[![Coverage Status](https://coveralls.io/repos/orcasgit/python-fitbit/badge.png?branch=master)](https://coveralls.io/r/orcasgit/python-fitbit?branch=master)
+[![Requirements Status](https://requires.io/github/orcasgit/python-fitbit/requirements.png?branch=master)](https://requires.io/github/orcasgit/python-fitbit/requirements/?branch=master)
+
+Fitbit API Python Client Implementation
+
+For documentation: [http://python-fitbit.readthedocs.org/](http://python-fitbit.readthedocs.org/)
+
+Requirements
+============
+
+* Python 2.6+  
diff --git a/README.rst b/README.rst
deleted file mode 100644
index e2914b9..0000000
--- a/README.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-=============
-python-fitbit
-=============
-
-.. image:: https://travis-ci.org/orcasgit/python-fitbit.png?branch=master   :target: https://travis-ci.org/orcasgit/python-fitbit
-
-Fitbit API Python Client Implementation
-
-For documentation: `http://python-fitbit.readthedocs.org/ <http://python-fitbit.readthedocs.org/>`_
-
-Requirements
-============
-
-* Python 2.6+  
diff --git a/docs/conf.py b/docs/conf.py
index 4e73820..cd95ecc 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -41,16 +41,16 @@ master_doc = 'index'
 
 # General information about the project.
 project = u'Python-Fitbit'
-copyright = u'Copyright 2012 ORCAS'
+copyright = u'Copyright 2014 ORCAS'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 #
 # The short X.Y version.
-version = '0.0'
+version = '0.1'
 # The full version, including alpha/beta/rc tags.
-release = '0.0.2'
+release = '0.1.0'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -184,7 +184,7 @@ latex_elements = {
 # (source start file, target name, title, author, documentclass [howto/manual]).
 latex_documents = [
   ('index', 'Python-Fitbit.tex', u'Python-Fitbit Documentation',
-   u'Issac Kelly, Percy Perez', 'manual'),
+   u'Issac Kelly, Percy Perez, Brad Pitcher', 'manual'),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
@@ -214,7 +214,7 @@ latex_documents = [
 # (source start file, name, description, authors, manual section).
 man_pages = [
     ('index', 'python-fitbit', u'Python-Fitbit Documentation',
-     [u'Issac Kelly, Percy Perez'], 1)
+     [u'Issac Kelly, Percy Perez, Brad Pitcher'], 1)
 ]
 
 # If true, show URL addresses after external links.
@@ -228,7 +228,7 @@ man_pages = [
 #  dir menu entry, description, category)
 texinfo_documents = [
   ('index', 'Python-Fitbit', u'Python-Fitbit Documentation',
-   u'Issac Kelly, Percy Perez', 'Python-Fitbit', 'One line description of project.',
+   u'Issac Kelly, Percy Perez, Brad Pitcher', 'Python-Fitbit', 'Fitbit API Python Client Implementation',
    'Miscellaneous'),
 ]
 
diff --git a/docs/index.rst b/docs/index.rst
index ff6e3ea..a237953 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -8,7 +8,7 @@ Overview
 
 This is a complete python implementation of the Fitbit API.
 
-It uses oAuath for authentication, it supports both us and si
+It uses oAuth for authentication, it supports both us and si
 measurements
 
 Quickstart
@@ -22,7 +22,7 @@ Here is some example usage::
     unauth_client.activities()
 
     # You'll have to gather the user keys on your own, or try ./fitbit/gather_keys_cli.py <con_key> <con_sec> for development
-    authd_client = fitbit.Fitbit('<consumer_key>', '<consumer_secret>', user_key='<user_key>', user_secret='<user_secret>')
+    authd_client = fitbit.Fitbit('<consumer_key>', '<consumer_secret>', resource_owner_key='<user_key>', resource_owner_secret='<user_secret>')
     authd_client.sleep()
 
 Fitbit API
diff --git a/fitbit/__init__.py b/fitbit/__init__.py
index 25787bf..e4957cb 100644
--- a/fitbit/__init__.py
+++ b/fitbit/__init__.py
@@ -7,7 +7,7 @@ Fitbit API Library
 :license: BSD, see LICENSE for more details.
 """
 
-from .api import Fitbit, FitbitConsumer, FitbitOauthClient
+from .api import Fitbit, FitbitOauthClient
 
 # Meta.
 
@@ -17,7 +17,7 @@ __author_email__ = 'bpitcher at orcasinc.com'
 __copyright__ = 'Copyright 2012 ORCAS'
 __license__ = 'Apache 2.0'
 
-__version__ = '0.0.3'
+__version__ = '0.1.0'
 
 # Module namespace.
 
diff --git a/fitbit/api.py b/fitbit/api.py
index 7516476..4ada88a 100644
--- a/fitbit/api.py
+++ b/fitbit/api.py
@@ -1,43 +1,60 @@
 # -*- coding: utf-8 -*-
-import oauth2 as oauth
 import requests
 import json
 import datetime
-import urllib
 
-from requests_oauthlib import OAuth1Session
+try:
+    from urllib.parse import urlencode
+except ImportError:
+    # Python 2.x
+    from urllib import urlencode
+
+from requests_oauthlib import OAuth1, OAuth1Session
+
 from fitbit.exceptions import (BadResponse, DeleteError, HTTPBadRequest,
                                HTTPUnauthorized, HTTPForbidden,
                                HTTPServerError, HTTPConflict, HTTPNotFound)
 from fitbit.utils import curry
 
 
-class FitbitConsumer(oauth.Consumer):
-    pass
-
-
-# example client using httplib with headers
-class FitbitOauthClient(oauth.Client):
+class FitbitOauthClient(object):
     API_ENDPOINT = "https://api.fitbit.com"
     AUTHORIZE_ENDPOINT = "https://www.fitbit.com"
     API_VERSION = 1
-    _signature_method = oauth.SignatureMethod_HMAC_SHA1()
 
     request_token_url = "%s/oauth/request_token" % API_ENDPOINT
     access_token_url = "%s/oauth/access_token" % API_ENDPOINT
     authorization_url = "%s/oauth/authorize" % AUTHORIZE_ENDPOINT
 
-    def __init__(self, consumer_key, consumer_secret, user_key=None,
-                 user_secret=None, user_id=None, *args, **kwargs):
-        if user_key and user_secret:
-            self._token = oauth.Token(user_key, user_secret)
-        else:
-            # This allows public calls to be made
-            self._token = None
+    def __init__(self, client_key, client_secret, resource_owner_key=None,
+                 resource_owner_secret=None, user_id=None, callback_uri=None,
+                 *args, **kwargs):
+        """
+        Create a FitbitOauthClient object. Specify the first 5 parameters if
+        you have them to access user data. Specify just the first 2 parameters
+        to access anonymous data and start the set up for user authorization.
+        
+        Set callback_uri to a URL and when the user has granted us access at
+        the fitbit site, fitbit will redirect them to the URL you passed.  This
+        is how we get back the magic verifier string from fitbit if we're a web
+        app. If we don't pass it, then fitbit will just display the verifier
+        string for the user to copy and we'll have to ask them to paste it for
+        us and read it that way.
+        """
+
+        self.client_key = client_key
+        self.client_secret = client_secret
+        self.resource_owner_key = resource_owner_key
+        self.resource_owner_secret = resource_owner_secret
         if user_id:
             self.user_id = user_id
-        self._consumer = FitbitConsumer(consumer_key, consumer_secret)
-        super(FitbitOauthClient, self).__init__(self._consumer, *args, **kwargs)
+        params = {'client_secret': client_secret}
+        if callback_uri:
+            params['callback_uri'] = callback_uri
+        if self.resource_owner_key and self.resource_owner_secret:
+            params['resource_owner_key'] = self.resource_owner_key
+            params['resource_owner_secret'] = self.resource_owner_secret
+        self.oauth = OAuth1Session(client_key, **params)
 
     def _request(self, method, url, **kwargs):
         """
@@ -47,17 +64,16 @@ class FitbitOauthClient(oauth.Client):
 
     def make_request(self, url, data={}, method=None, **kwargs):
         """
-        Builds and makes the Oauth Request, catches errors
+        Builds and makes the OAuth Request, catches errors
 
         https://wiki.fitbit.com/display/API/API+Response+Format+And+Errors
         """
         if not method:
             method = 'POST' if data else 'GET'
-        request = oauth.Request.from_consumer_and_token(self._consumer, self._token, http_method=method, http_url=url, parameters=data)
-        request.sign_request(self._signature_method, self._consumer,
-                             self._token)
-        response = self._request(method, url, data=data,
-                                 headers=request.to_header())
+        auth = OAuth1(
+            self.client_key, self.client_secret, self.resource_owner_key,
+            self.resource_owner_secret, signature_type='auth_header')
+        response = self._request(method, url, data=data, auth=auth, **kwargs)
 
         if response.status_code == 401:
             raise HTTPUnauthorized(response)
@@ -73,79 +89,49 @@ class FitbitOauthClient(oauth.Client):
             raise HTTPBadRequest(response)
         return response
 
-    def fetch_request_token(self, parameters=None):
+    def fetch_request_token(self):
         """
         Step 1 of getting authorized to access a user's data at fitbit: this
-        makes a signed request to fitbit to get a token to use in the next
-        step.  Returns that token.
-
-        Set parameters['oauth_callback'] to a URL and when the user has
-        granted us access at the fitbit site, fitbit will redirect them to the URL
-        you passed.  This is how we get back the magic verifier string from fitbit
-        if we're a web app. If we don't pass it, then fitbit will just display
-        the verifier string for the user to copy and we'll have to ask them to
-        paste it for us and read it that way.
-        """
-
-        """
-        via headers
-        -> OAuthToken
-
-        Providing 'oauth_callback' parameter in the Authorization header of
-        request_token_url request, will have priority over the dev.fitbit.com
-        settings, ie. parameters = {'oauth_callback': 'callback_url'}
-        """
-
-        request = oauth.Request.from_consumer_and_token(
-            self._consumer,
-            http_url=self.request_token_url,
-            parameters=parameters
-        )
-        request.sign_request(self._signature_method, self._consumer, None)
-        response = self._request(request.method, self.request_token_url,
-                                 headers=request.to_header())
-        return oauth.Token.from_string(response.content)
-
-    def authorize_token_url(self, token):
-        """Step 2: Given the token returned by fetch_request_token(), return
-        the URL the user needs to go to in order to grant us authorization
-        to look at their data.  Then redirect the user to that URL, open their
-        browser to it, or tell them to copy the URL into their browser.
-        """
-        request = oauth.Request.from_token_and_callback(
-            token=token,
-            http_url=self.authorization_url
-        )
-        return request.to_url()
-
-    #def authorize_token(self, token):
-    #    # via url
-    #    # -> typically just some okay response
-    #    request = oauth.Request.from_token_and_callback(token=token,
-    #                                         http_url=self.authorization_url)
-    #    response = self._request(request.method, request.to_url(),
-    #                                             headers=request.to_header())
-    #    return response.content
-
-    def fetch_access_token(self, token, verifier):
-        """Step 4: Given the token from step 1, and the verifier from step 3 (see step 2),
-        calls fitbit again and returns an access token object.  Extract .key and .secret
-        from that and save them, then pass them as user_key and user_secret in future
-        API calls to fitbit to get this user's data.
-        """
-        client = OAuth1Session(
-            self._consumer.key,
-            client_secret=self._consumer.secret,
-            resource_owner_key=token.key,
-            resource_owner_secret=token.secret,
+        makes a signed request to fitbit to get a token to use in step 3.
+        Returns that token.}
+        """
+
+        token = self.oauth.fetch_request_token(self.request_token_url)
+        self.resource_owner_key = token.get('oauth_token')
+        self.resource_owner_secret = token.get('oauth_token_secret')
+        return token
+
+    def authorize_token_url(self):
+        """Step 2: Return the URL the user needs to go to in order to grant us
+        authorization to look at their data.  Then redirect the user to that
+        URL, open their browser to it, or tell them to copy the URL into their
+        browser.
+        """
+        
+        return self.oauth.authorization_url(self.authorization_url)
+
+    def fetch_access_token(self, verifier, token=None):
+        """Step 3: Given the verifier from fitbit, and optionally a token from
+        step 1 (not necessary if using the same FitbitOAuthClient object) calls
+        fitbit again and returns an access token object. Extract the needed
+        information from that and save it to use in future API calls.
+        """
+        if token:
+            self.resource_owner_key = token.get('oauth_token')
+            self.resource_owner_secret = token.get('oauth_token_secret')
+            
+        self.oauth = OAuth1Session(
+            self.client_key,
+            client_secret=self.client_secret,
+            resource_owner_key=self.resource_owner_key,
+            resource_owner_secret=self.resource_owner_secret,
             verifier=verifier)
-        response = client.fetch_access_token(self.access_token_url)
+        response = self.oauth.fetch_access_token(self.access_token_url)
 
-        self.user_id = response['encoded_user_id']
-        self._token = oauth.Token(
-            key=response['oauth_token'],
-            secret=response['oauth_token_secret'])
-        return self._token
+        self.user_id = response.get('encoded_user_id')
+        self.resource_owner_key = response.get('oauth_token')
+        self.resource_owner_secret = response.get('oauth_token_secret')
+        return response
 
 
 class Fitbit(object):
@@ -154,6 +140,7 @@ class Fitbit(object):
 
     API_ENDPOINT = "https://api.fitbit.com"
     API_VERSION = 1
+    WEEK_DAYS = ['SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY']
 
     _resource_list = [
         'body',
@@ -172,8 +159,8 @@ class Fitbit(object):
         'frequent',
     ]
 
-    def __init__(self, consumer_key, consumer_secret, system=US, **kwargs):
-        self.client = FitbitOauthClient(consumer_key, consumer_secret, **kwargs)
+    def __init__(self, client_key, client_secret, system=US, **kwargs):
+        self.client = FitbitOauthClient(client_key, client_secret, **kwargs)
         self.SYSTEM = system
 
         # All of these use the same patterns, define the method for accessing
@@ -210,7 +197,7 @@ class Fitbit(object):
             else:
                 raise DeleteError(response)
         try:
-            rep = json.loads(response.content)
+            rep = json.loads(response.content.decode('utf8'))
         except ValueError:
             raise BadResponse
 
@@ -277,7 +264,7 @@ class Fitbit(object):
             date = datetime.date.today()
         if not user_id:
             user_id = '-'
-        if not isinstance(date, basestring):
+        if not isinstance(date, str):
             date = date.strftime('%Y-%m-%d')
 
         if not data:
@@ -345,7 +332,7 @@ class Fitbit(object):
             raise TypeError("Either end_date or period can be specified, not both")
 
         if end_date:
-            if not isinstance(end_date, basestring):
+            if not isinstance(end_date, str):
                 end = end_date.strftime('%Y-%m-%d')
             else:
                 end = end_date
@@ -354,7 +341,7 @@ class Fitbit(object):
                 raise ValueError("Period must be one of '1d', '7d', '30d', '1w', '1m', '3m', '6m', '1y', 'max'")
             end = period
 
-        if not isinstance(base_date, basestring):
+        if not isinstance(base_date, str):
             base_date = base_date.strftime('%Y-%m-%d')
 
         url = "%s/%s/user/%s/%s/date/%s/%s.json" % (
@@ -434,6 +421,15 @@ class Fitbit(object):
         )
         return self.make_request(url, method='POST')
 
+    def log_activity(self, data):
+        """
+        https://wiki.fitbit.com/display/API/API-Log-Activity
+        """
+        url = "%s/%s/user/-/activities.json" % (
+            self.API_ENDPOINT,
+            self.API_VERSION)
+        return self.make_request(url, data = data)
+
     def delete_favorite_activity(self, activity_id):
         """
         https://wiki.fitbit.com/display/API/API-Delete-Favorite-Activity
@@ -497,6 +493,130 @@ class Fitbit(object):
         )
         return self.make_request(url)
 
+    def get_alarms(self, device_id):
+        """
+        https://wiki.fitbit.com/display/API/API-Devices-Get-Alarms
+        """
+        url = "%s/%s/user/-/devices/tracker/%s/alarms.json" % (
+            self.API_ENDPOINT,
+            self.API_VERSION,
+            device_id
+        )
+        return self.make_request(url)
+
+    def add_alarm(self, device_id, alarm_time, week_days, recurring=False, enabled=True, label=None,
+                     snooze_length=None, snooze_count=None, vibe='DEFAULT'):
+        """
+        https://wiki.fitbit.com/display/API/API-Devices-Add-Alarm
+        alarm_time should be a timezone aware datetime object.
+        """
+        url = "%s/%s/user/-/devices/tracker/%s/alarms.json" % (
+            self.API_ENDPOINT,
+            self.API_VERSION,
+            device_id
+        )
+        alarm_time = alarm_time.strftime("%H:%M%z")
+        # Check week_days list
+        if not isinstance(week_days, list):
+            raise ValueError("Week days needs to be a list")
+        for day in week_days:
+            if day not in self.WEEK_DAYS:
+                raise ValueError("Incorrect week day %s. see WEEK_DAY_LIST." % day)
+        data = {
+            'time': alarm_time,
+            'weekDays': week_days,
+            'recurring': recurring,
+            'enabled': enabled,
+            'vibe': vibe
+        }
+        if label:
+            data['label'] = label
+        if snooze_length:
+            data['snoozeLength'] = snooze_length
+        if snooze_count:
+            data['snoozeCount'] = snooze_count
+        return self.make_request(url, data=data, method="POST")
+        # return
+
+    def update_alarm(self, device_id, alarm_id, alarm_time, week_days, recurring=False, enabled=True, label=None,
+                     snooze_length=None, snooze_count=None, vibe='DEFAULT'):
+        """
+        https://wiki.fitbit.com/display/API/API-Devices-Update-Alarm
+        alarm_time should be a timezone aware datetime object.
+        """
+        # TODO Refactor with create_alarm. Tons of overlap.
+        # Check week_days list
+        if not isinstance(week_days, list):
+            raise ValueError("Week days needs to be a list")
+        for day in week_days:
+            if day not in self.WEEK_DAYS:
+                raise ValueError("Incorrect week day %s. see WEEK_DAY_LIST." % day)
+        url = "%s/%s/user/-/devices/tracker/%s/alarms/%s.json" % (
+            self.API_ENDPOINT,
+            self.API_VERSION,
+            device_id,
+            alarm_id
+        )
+        alarm_time = alarm_time.strftime("%H:%M%z")
+
+        data = {
+            'time': alarm_time,
+            'weekDays': week_days,
+            'recurring': recurring,
+            'enabled': enabled,
+            'vibe': vibe
+        }
+        if label:
+            data['label'] = label
+        if snooze_length:
+            data['snoozeLength'] = snooze_length
+        if snooze_count:
+            data['snoozeCount'] = snooze_count
+        return self.make_request(url, data=data, method="POST")
+        # return
+
+    def delete_alarm(self, device_id, alarm_id):
+        """
+        https://wiki.fitbit.com/display/API/API-Devices-Delete-Alarm
+        """
+        url = "%s/%s/user/-/devices/tracker/%s/alarms/%s.json" % (
+            self.API_ENDPOINT,
+            self.API_VERSION,
+            device_id,
+            alarm_id
+        )
+        return self.make_request(url, method="DELETE")
+
+    def get_sleep(self, date):
+        """
+        https://wiki.fitbit.com/display/API/API-Get-Sleep
+        date should be a datetime.date object.
+        """
+        url = "%s/%s/user/-/sleep/date/%s-%s-%s.json" % (
+            self.API_ENDPOINT,
+            self.API_VERSION,
+            date.year,
+            date.month,
+            date.day
+        )
+        return self.make_request(url)
+
+    def log_sleep(self, start_time, duration):
+        """
+        https://wiki.fitbit.com/display/API/API-Log-Sleep
+        start time should be a datetime object. We will be using the year, month, day, hour, and minute.
+        """
+        data = {
+            'startTime': start_time.strftime("%H:%M"),
+            'duration': duration,
+            'date': start_time.strftime("%Y-%m-%d"),
+        }
+        url = "%s/%s/user/-/sleep" % (
+            self.API_ENDPOINT,
+            self.API_VERSION,
+        )
+        return self.make_request(url, data=data, method="POST")
+
     def activities_list(self):
         """
         https://wiki.fitbit.com/display/API/API-Browse-Activities
@@ -525,7 +645,7 @@ class Fitbit(object):
         url = "%s/%s/foods/search.json?%s" % (
             self.API_ENDPOINT,
             self.API_VERSION,
-            urllib.urlencode({'query': query})
+            urlencode({'query': query})
         )
         return self.make_request(url)
 
@@ -550,6 +670,118 @@ class Fitbit(object):
         )
         return self.make_request(url)
 
+    def get_bodyweight(self, base_date=None, user_id=None, period=None, end_date=None):
+        """
+        https://wiki.fitbit.com/display/API/API-Get-Body-Weight
+        base_date should be a datetime.date object (defaults to today),
+        period can be '1d', '7d', '30d', '1w', '1m', '3m', '6m', '1y', 'max' or None
+        end_date should be a datetime.date object, or None.
+
+        You can specify period or end_date, or neither, but not both.
+        """
+        if not base_date:
+            base_date = datetime.date.today()
+
+        if not user_id:
+            user_id = '-'
+
+        if period and end_date:
+            raise TypeError("Either end_date or period can be specified, not both")
+
+        if not isinstance(base_date, str):
+            base_date_string = base_date.strftime('%Y-%m-%d')
+        else:
+            base_date_string = base_date
+
+        if period:
+            if not period in ['1d', '7d', '30d', '1w', '1m', '3m', '6m', '1y', 'max']:
+                raise ValueError("Period must be one of '1d', '7d', '30d', '1w', '1m', '3m', '6m', '1y', 'max'")
+
+            url = "%s/%s/user/%s/body/log/weight/date/%s/%s.json" % (
+                self.API_ENDPOINT,
+                self.API_VERSION,
+                user_id,
+                base_date_string,
+                period
+            )
+        elif end_date:
+            if not isinstance(end_date, str):
+                end_string = end_date.strftime('%Y-%m-%d')
+            else:
+                end_string = end_date
+
+            url = "%s/%s/user/%s/body/log/weight/date/%s/%s.json" % (
+                self.API_ENDPOINT,
+                self.API_VERSION,
+                user_id,
+                base_date_string,
+                end_string
+            )
+        else:
+            url = "%s/%s/user/%s/body/log/weight/date/%s.json" % (
+                self.API_ENDPOINT,
+                self.API_VERSION,
+                user_id,
+                base_date_string,
+            )
+        return self.make_request(url)
+
+    def get_bodyfat(self, base_date=None, user_id=None, period=None, end_date=None):
+        """
+        https://wiki.fitbit.com/display/API/API-Get-Body-fat
+        base_date should be a datetime.date object (defaults to today),
+        period can be '1d', '7d', '30d', '1w', '1m', '3m', '6m', '1y', 'max' or None
+        end_date should be a datetime.date object, or None.
+
+        You can specify period or end_date, or neither, but not both.
+        """
+        if not base_date:
+            base_date = datetime.date.today()
+
+        if not user_id:
+            user_id = '-'
+
+        if period and end_date:
+            raise TypeError("Either end_date or period can be specified, not both")
+
+        if not isinstance(base_date, str):
+            base_date_string = base_date.strftime('%Y-%m-%d')
+        else:
+            base_date_string = base_date
+
+        if period:
+            if not period in ['1d', '7d', '30d', '1w', '1m', '3m', '6m', '1y', 'max']:
+                raise ValueError("Period must be one of '1d', '7d', '30d', '1w', '1m', '3m', '6m', '1y', 'max'")
+
+            url = "%s/%s/user/%s/body/log/fat/date/%s/%s.json" % (
+                self.API_ENDPOINT,
+                self.API_VERSION,
+                user_id,
+                base_date_string,
+                period
+            )
+        elif end_date:
+            if not isinstance(end_date, str):
+                end_string = end_date.strftime('%Y-%m-%d')
+            else:
+                end_string = end_date
+
+            url = "%s/%s/user/%s/body/log/fat/date/%s/%s.json" % (
+                self.API_ENDPOINT,
+                self.API_VERSION,
+                user_id,
+                base_date_string,
+                end_string
+            )
+        else:
+            url = "%s/%s/user/%s/body/log/fat/date/%s.json" % (
+                self.API_ENDPOINT,
+                self.API_VERSION,
+                user_id,
+                base_date_string,
+            )
+        return self.make_request(url)
+
     def get_friends(self, user_id=None):
         """
         https://wiki.fitbit.com/display/API/API-Get-Friends
@@ -676,8 +908,8 @@ class Fitbit(object):
         return self.make_request(url)
 
     @classmethod
-    def from_oauth_keys(self, consumer_key, consumer_secret, user_key=None,
+    def from_oauth_keys(self, client_key, client_secret, user_key=None,
                         user_secret=None, user_id=None, system=US):
-        client = FitbitOauthClient(consumer_key, consumer_secret, user_key,
+        client = FitbitOauthClient(client_key, client_secret, user_key,
                                    user_secret, user_id)
         return self(client, system)
diff --git a/fitbit/exceptions.py b/fitbit/exceptions.py
index 33ccc20..b594b02 100644
--- a/fitbit/exceptions.py
+++ b/fitbit/exceptions.py
@@ -15,10 +15,13 @@ class DeleteError(Exception):
 class HTTPException(Exception):
     def __init__(self, response, *args, **kwargs):
         try:
-            errors = json.loads(response.content)['errors']
+            errors = json.loads(response.content.decode('utf8'))['errors']
             message = '\n'.join([error['message'] for error in errors])
         except Exception:
-            message = response
+            if response.status_code == 401:
+                message = response.content.decode('utf8')
+            else:
+                message = response
         super(HTTPException, self).__init__(message, *args, **kwargs)
 
 class HTTPBadRequest(HTTPException):
diff --git a/fitbit_tests/__init__.py b/fitbit_tests/__init__.py
index 06457ff..e3e0700 100644
--- a/fitbit_tests/__init__.py
+++ b/fitbit_tests/__init__.py
@@ -1,7 +1,7 @@
 import unittest
-from test_exceptions import ExceptionTest
-from test_auth import AuthTest
-from fitbit_tests.test_api import APITest, CollectionResourceTest, DeleteCollectionResourceTest, MiscTest
+from .test_exceptions import ExceptionTest
+from .test_auth import AuthTest
+from .test_api import APITest, CollectionResourceTest, DeleteCollectionResourceTest, MiscTest
 
 
 def all_tests(consumer_key="", consumer_secret="", user_key=None, user_secret=None):
diff --git a/fitbit_tests/base.py b/fitbit_tests/base.py
deleted file mode 100644
index 9bb7866..0000000
--- a/fitbit_tests/base.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import unittest
-
-class APITestCase(unittest.TestCase):
-
-
-    def __init__(self, consumer_key="", consumer_secret="", client_key=None, client_secret=None, *args, **kwargs):
-        self.consumer_key = consumer_key
-        self.consumer_secret = consumer_secret
-        self.client_key = client_key
-        self.client_secret = client_secret
-
-        self.client_kwargs = {
-            "consumer_key": consumer_key,
-            "consumer_secret": consumer_secret,
-            "client_key": client_key,
-            "client_secret": client_secret,
-        }
-        super(APITestCase, self).__init__(*args, **kwargs)
diff --git a/fitbit_tests/test_api.py b/fitbit_tests/test_api.py
index 0a0c696..0eb3f3e 100644
--- a/fitbit_tests/test_api.py
+++ b/fitbit_tests/test_api.py
@@ -9,7 +9,7 @@ URLBASE = "%s/%s/user" % (Fitbit.API_ENDPOINT, Fitbit.API_VERSION)
 
 class TestBase(TestCase):
     def setUp(self):
-        self.fb = Fitbit(consumer_key='x', consumer_secret='y')
+        self.fb = Fitbit('x', 'y')
 
     def common_api_test(self, funcname, args, kwargs, expected_args, expected_kwargs):
         # Create a fitbit object, call the named function on it with the given
@@ -33,7 +33,7 @@ class APITest(TestBase):
         KWARGS = { 'a': 3, 'b': 4, 'headers': {'Accept-Language': self.fb.SYSTEM}}
         mock_response = mock.Mock()
         mock_response.status_code = 200
-        mock_response.content = "1"
+        mock_response.content = b"1"
         with mock.patch.object(self.fb.client, 'make_request') as client_make_request:
             client_make_request.return_value = mock_response
             retval = self.fb.make_request(*ARGS, **KWARGS)
@@ -155,7 +155,7 @@ class CollectionResourceTest(TestBase):
         # since the __init__ is going to set up references to it
         with mock.patch('fitbit.api.Fitbit._COLLECTION_RESOURCE') as coll_resource:
             coll_resource.return_value = 999
-            fb = Fitbit(consumer_key='x', consumer_secret='y')
+            fb = Fitbit('x', 'y')
             retval = fb.body(date=1, user_id=2, data=3)
         args, kwargs = coll_resource.call_args
         self.assertEqual(('body',), args)
@@ -181,7 +181,7 @@ class DeleteCollectionResourceTest(TestBase):
         # since the __init__ is going to set up references to it
         with mock.patch('fitbit.api.Fitbit._DELETE_COLLECTION_RESOURCE') as delete_resource:
             delete_resource.return_value = 999
-            fb = Fitbit(consumer_key='x', consumer_secret='y')
+            fb = Fitbit('x', 'y')
             retval = fb.delete_water(log_id=log_id)
         args, kwargs = delete_resource.call_args
         self.assertEqual(('water',), args)
@@ -193,7 +193,7 @@ class MiscTest(TestBase):
     def test_recent_activities(self):
         user_id = "LukeSkywalker"
         with mock.patch('fitbit.api.Fitbit.activity_stats') as act_stats:
-            fb = Fitbit(consumer_key='x', consumer_secret='y')
+            fb = Fitbit('x', 'y')
             retval = fb.recent_activities(user_id=user_id)
         args, kwargs = act_stats.call_args
         self.assertEqual((), args)
@@ -287,9 +287,55 @@ class MiscTest(TestBase):
     def test_activities(self):
         url = "%s/%s/activities.json" % (Fitbit.API_ENDPOINT, Fitbit.API_VERSION)
         self.common_api_test('activities_list', (), {}, (url,), {})
+        url = "%s/%s/user/-/activities.json" % (Fitbit.API_ENDPOINT, Fitbit.API_VERSION)
+        self.common_api_test('log_activity', (), {'data' : 'FOO'}, (url,), {'data' : 'FOO'} )
         url = "%s/%s/activities/FOOBAR.json" % (Fitbit.API_ENDPOINT, Fitbit.API_VERSION)
         self.common_api_test('activity_detail', ("FOOBAR",), {}, (url,), {})
 
+    def test_bodyweight(self):
+        def test_get_bodyweight(fb, base_date=None, user_id=None, period=None, end_date=None, expected_url=None):
+            with mock.patch.object(fb, 'make_request') as make_request:
+                fb.get_bodyweight(base_date, user_id=user_id, period=period, end_date=end_date)
+            args, kwargs = make_request.call_args
+            self.assertEqual((expected_url,), args)
+
+        user_id = 'BAR'
+
+        # No end_date or period
+        test_get_bodyweight(self.fb, base_date=datetime.date(1992, 5, 12), user_id=None, period=None, end_date=None,
+            expected_url=URLBASE + "/-/body/log/weight/date/1992-05-12.json")
+        # With end_date
+        test_get_bodyweight(self.fb, base_date=datetime.date(1992, 5, 12), user_id=user_id, period=None, end_date=datetime.date(1998, 12, 31),
+            expected_url=URLBASE + "/BAR/body/log/weight/date/1992-05-12/1998-12-31.json")
+        # With period
+        test_get_bodyweight(self.fb, base_date=datetime.date(1992, 5, 12), user_id=user_id, period="1d", end_date=None,
+            expected_url=URLBASE + "/BAR/body/log/weight/date/1992-05-12/1d.json")
+        # Date defaults to today
+        test_get_bodyweight(self.fb, base_date=None, user_id=None, period=None, end_date=None,
+            expected_url=URLBASE + "/-/body/log/weight/date/%s.json" % datetime.date.today().strftime('%Y-%m-%d'))
+
+    def test_bodyfat(self):
+        def test_get_bodyfat(fb, base_date=None, user_id=None, period=None, end_date=None, expected_url=None):
+            with mock.patch.object(fb, 'make_request') as make_request:
+                fb.get_bodyfat(base_date, user_id=user_id, period=period, end_date=end_date)
+            args, kwargs = make_request.call_args
+            self.assertEqual((expected_url,), args)
+
+        user_id = 'BAR'
+
+        # No end_date or period
+        test_get_bodyfat(self.fb, base_date=datetime.date(1992, 5, 12), user_id=None, period=None, end_date=None,
+            expected_url=URLBASE + "/-/body/log/fat/date/1992-05-12.json")
+        # With end_date
+        test_get_bodyfat(self.fb, base_date=datetime.date(1992, 5, 12), user_id=user_id, period=None, end_date=datetime.date(1998, 12, 31),
+            expected_url=URLBASE + "/BAR/body/log/fat/date/1992-05-12/1998-12-31.json")
+        # With period
+        test_get_bodyfat(self.fb, base_date=datetime.date(1992, 5, 12), user_id=user_id, period="1d", end_date=None,
+            expected_url=URLBASE + "/BAR/body/log/fat/date/1992-05-12/1d.json")
+        # Date defaults to today
+        test_get_bodyfat(self.fb, base_date=None, user_id=None, period=None, end_date=None,
+            expected_url=URLBASE + "/-/body/log/fat/date/%s.json" % datetime.date.today().strftime('%Y-%m-%d'))
+
     def test_friends(self):
         url = URLBASE + "/-/friends.json"
         self.common_api_test('get_friends', (), {}, (url,), {})
@@ -326,3 +372,71 @@ class MiscTest(TestBase):
         url = URLBASE + "/-/COLLECTION/apiSubscriptions/SUBSCRIPTION_ID-COLLECTION.json"
         self.common_api_test('subscription', ("SUBSCRIPTION_ID", "SUBSCRIBER_ID"), {'method': 'THROW', 'collection': "COLLECTION"},
             (url,), {'method': 'THROW', 'headers': {'X-Fitbit-Subscriber-id': "SUBSCRIBER_ID"}})
+
+    def test_alarms(self):
+        url = "%s/-/devices/tracker/%s/alarms.json" % (URLBASE, 'FOO')
+        self.common_api_test('get_alarms', (), {'device_id': 'FOO'}, (url,), {})
+        url = "%s/-/devices/tracker/%s/alarms/%s.json" % (URLBASE, 'FOO', 'BAR')
+        self.common_api_test('delete_alarm', (), {'device_id': 'FOO', 'alarm_id': 'BAR'}, (url,), {'method': 'DELETE'})
+        url = "%s/-/devices/tracker/%s/alarms.json" % (URLBASE, 'FOO')
+        self.common_api_test('add_alarm',
+            (),
+            {'device_id': 'FOO',
+             'alarm_time': datetime.datetime(year=2013, month=11, day=13, hour=8, minute=16),
+             'week_days': ['MONDAY']
+            },
+            (url,),
+            {'data':
+                 {'enabled': True,
+                    'recurring': False,
+                    'time': datetime.datetime(year=2013, month=11, day=13, hour=8, minute=16).strftime("%H:%M%z"),
+                    'vibe': 'DEFAULT',
+                    'weekDays': ['MONDAY'],
+                },
+            'method': 'POST'
+            }
+        )
+        self.common_api_test('add_alarm',
+            (),
+            {'device_id': 'FOO',
+             'alarm_time': datetime.datetime(year=2013, month=11, day=13, hour=8, minute=16),
+             'week_days': ['MONDAY'], 'recurring': True, 'enabled': False, 'label': 'ugh',
+             'snooze_length': 5,
+             'snooze_count': 5
+            },
+            (url,),
+            {'data':
+                 {'enabled': False,
+                  'recurring': True,
+                  'label': 'ugh',
+                  'snoozeLength': 5,
+                  'snoozeCount': 5,
+                  'time': datetime.datetime(year=2013, month=11, day=13, hour=8, minute=16).strftime("%H:%M%z"),
+                  'vibe': 'DEFAULT',
+                  'weekDays': ['MONDAY'],
+                },
+            'method': 'POST'}
+        )
+        url = "%s/-/devices/tracker/%s/alarms/%s.json" % (URLBASE, 'FOO', 'BAR')
+        self.common_api_test('update_alarm',
+            (),
+            {'device_id': 'FOO',
+             'alarm_id': 'BAR',
+             'alarm_time': datetime.datetime(year=2013, month=11, day=13, hour=8, minute=16),
+             'week_days': ['MONDAY'], 'recurring': True, 'enabled': False, 'label': 'ugh',
+             'snooze_length': 5,
+             'snooze_count': 5
+            },
+            (url,),
+            {'data':
+                 {'enabled': False,
+                  'recurring': True,
+                  'label': 'ugh',
+                  'snoozeLength': 5,
+                  'snoozeCount': 5,
+                  'time': datetime.datetime(year=2013, month=11, day=13, hour=8, minute=16).strftime("%H:%M%z"),
+                  'vibe': 'DEFAULT',
+                  'weekDays': ['MONDAY'],
+                },
+            'method': 'POST'}
+        )
diff --git a/fitbit_tests/test_auth.py b/fitbit_tests/test_auth.py
index ea8a0ea..5bc2a2b 100644
--- a/fitbit_tests/test_auth.py
+++ b/fitbit_tests/test_auth.py
@@ -1,7 +1,7 @@
 from unittest import TestCase
 from fitbit import Fitbit
 import mock
-import oauth2 as oauth
+from requests_oauthlib import OAuth1Session
 
 class AuthTest(TestCase):
     """Add tests for auth part of API
@@ -9,80 +9,52 @@ class AuthTest(TestCase):
     make sure we call the right oauth calls, respond correctly based on the responses
     """
     client_kwargs = {
-        "consumer_key": "",
-        "consumer_secret": "",
-        "user_key": None,
-        "user_secret": None,
-        }
+        'client_key': '',
+        'client_secret': '',
+        'user_key': None,
+        'user_secret': None,
+        'callback_uri': 'CALLBACK_URL'
+    }
 
     def test_fetch_request_token(self):
         # fetch_request_token needs to make a request and then build a token from the response
 
         fb = Fitbit(**self.client_kwargs)
-        callback_url = "CALLBACK_URL"
-        parameters = {'oauth_callback': callback_url}
-        with mock.patch.object(oauth.Request, 'from_consumer_and_token') as from_consumer_and_token:
-            mock_request = mock.Mock()
-            mock_request.to_header.return_value = "MOCKHEADERS"
-            mock_request.method = 'GET'
-            from_consumer_and_token.return_value = mock_request
-            with mock.patch('fitbit.api.FitbitOauthClient._request') as _request:
-                fake_response = mock.Mock()
-                fake_response.content = "FAKECONTENT"
-                fake_response.status_code = 200
-                _request.return_value = fake_response
-                with mock.patch.object(oauth.Token, 'from_string') as from_string:
-                    from_string.return_value = "FAKERETURNVALUE"
-
-                    retval = fb.client.fetch_request_token(parameters)
-        # Got the right return value
-        self.assertEqual("FAKERETURNVALUE", retval)
-        # The right parms were passed along the way to getting there
-        self.assertEqual(1, from_consumer_and_token.call_count)
-        self.assertEqual((fb.client._consumer,), from_consumer_and_token.call_args[0])
-        self.assertEqual({'http_url': fb.client.request_token_url, 'parameters': parameters}, from_consumer_and_token.call_args[1])
-        self.assertEqual(1, mock_request.sign_request.call_count)
-        self.assertEqual((fb.client._signature_method, fb.client._consumer, None), mock_request.sign_request.call_args[0])
-        self.assertEqual(1, _request.call_count)
-        self.assertEqual((mock_request.method,fb.client.request_token_url), _request.call_args[0])
-        self.assertEqual({'headers': "MOCKHEADERS"}, _request.call_args[1])
-        self.assertEqual(1, from_string.call_count)
-        self.assertEqual(("FAKECONTENT",), from_string.call_args[0])
+        with mock.patch.object(OAuth1Session, 'fetch_request_token') as frt:
+            frt.return_value = {
+                'oauth_callback_confirmed': 'true',
+                'oauth_token': 'FAKE_OAUTH_TOKEN',
+                'oauth_token_secret': 'FAKE_OAUTH_TOKEN_SECRET'}
+            retval = fb.client.fetch_request_token()
+            self.assertEqual(1, frt.call_count)
+            # Got the right return value
+            self.assertEqual('true', retval.get('oauth_callback_confirmed'))
+            self.assertEqual('FAKE_OAUTH_TOKEN', retval.get('oauth_token'))
+            self.assertEqual('FAKE_OAUTH_TOKEN_SECRET',
+                             retval.get('oauth_token_secret'))
 
     def test_authorize_token_url(self):
         # authorize_token_url calls oauth and returns a URL
         fb = Fitbit(**self.client_kwargs)
-        fake_token = "FAKETOKEN"
-        with mock.patch.object(oauth.Request, "from_token_and_callback") as from_token_and_callback:
-            mock_request = mock.Mock()
-            mock_request.to_url.return_value = "FAKEURL"
-            from_token_and_callback.return_value = mock_request
-            retval = fb.client.authorize_token_url(fake_token)
-        self.assertEqual("FAKEURL", retval)
-        self.assertEqual(1, from_token_and_callback.call_count)
-        kwargs = from_token_and_callback.call_args_list[0][1]
-        self.assertEqual({'token': fake_token, 'http_url': fb.client.authorization_url}, kwargs)
+        with mock.patch.object(OAuth1Session, 'authorization_url') as au:
+            au.return_value = 'FAKEURL'
+            retval = fb.client.authorize_token_url()
+            self.assertEqual(1, au.call_count)
+            self.assertEqual("FAKEURL", retval)
 
     def test_fetch_access_token(self):
-        fb = Fitbit(**self.client_kwargs)
-        fake_token = mock.Mock(key="FAKEKEY", secret="FAKESECRET")
+        kwargs = self.client_kwargs
+        kwargs['resource_owner_key'] = ''
+        kwargs['resource_owner_secret'] = ''
+        fb = Fitbit(**kwargs)
         fake_verifier = "FAKEVERIFIER"
-        with mock.patch('requests_oauthlib.OAuth1Session.fetch_access_token') as fetch_access_token:
-            fetch_access_token.return_value = {
-            'encoded_user_id': 'FAKEUSERID',
-            'oauth_token': 'FAKERETURNEDKEY',
-            'oauth_token_secret': 'FAKERETURNEDSECRET'
+        with mock.patch.object(OAuth1Session, 'fetch_access_token') as fat:
+            fat.return_value = {
+                'encoded_user_id': 'FAKE_USER_ID',
+                'oauth_token': 'FAKE_RETURNED_KEY',
+                'oauth_token_secret': 'FAKE_RETURNED_SECRET'
             }
-            retval = fb.client.fetch_access_token(fake_token, fake_verifier)
-        self.assertEqual("FAKERETURNEDKEY", retval.key)
-        self.assertEqual("FAKERETURNEDSECRET", retval.secret)
-        self.assertEqual('FAKEUSERID', fb.client.user_id)
-
-    def test_fetch_access_token_error(self):
-        fb = Fitbit(**self.client_kwargs)
-        with mock.patch('requests.sessions.Session.post') as post:
-            post.return_value = mock.Mock(text="not a url encoded string")
-            fake_token = mock.Mock(key="FAKEKEY", secret="FAKESECRET")
-            self.assertRaises(ValueError,
-                fb.client.fetch_access_token,
-                fake_token, "fake_verifier")
+            retval = fb.client.fetch_access_token(fake_verifier)
+        self.assertEqual("FAKE_RETURNED_KEY", retval['oauth_token'])
+        self.assertEqual("FAKE_RETURNED_SECRET", retval['oauth_token_secret'])
+        self.assertEqual('FAKE_USER_ID', fb.client.user_id)
diff --git a/fitbit_tests/test_exceptions.py b/fitbit_tests/test_exceptions.py
index 98eba79..5c174bb 100644
--- a/fitbit_tests/test_exceptions.py
+++ b/fitbit_tests/test_exceptions.py
@@ -9,13 +9,12 @@ class ExceptionTest(unittest.TestCase):
     Tests that certain response codes raise certain exceptions
     """
     client_kwargs = {
-        "consumer_key": "",
-        "consumer_secret": "",
+        "client_key": "",
+        "client_secret": "",
         "user_key": None,
         "user_secret": None,
     }
 
-
     def test_response_ok(self):
         """
         This mocks a pretty normal resource, that the request was authenticated,
@@ -24,7 +23,7 @@ class ExceptionTest(unittest.TestCase):
         """
         r = mock.Mock(spec=requests.Response)
         r.status_code = 200
-        r.content = '{"normal": "resource"}'
+        r.content = b'{"normal": "resource"}'
 
         f = Fitbit(**self.client_kwargs)
         f.client._request = lambda *args, **kwargs: r
@@ -44,7 +43,7 @@ class ExceptionTest(unittest.TestCase):
         """
         r = mock.Mock(spec=requests.Response)
         r.status_code = 401
-        r.content = "{'normal': 'resource'}"
+        r.content = b"{'normal': 'resource'}"
 
         f = Fitbit(**self.client_kwargs)
         f.client._request = lambda *args, **kwargs: r
@@ -60,7 +59,7 @@ class ExceptionTest(unittest.TestCase):
         Tests other HTTP errors
         """
         r = mock.Mock(spec=requests.Response)
-        r.content = "{'normal': 'resource'}"
+        r.content = b"{'normal': 'resource'}"
 
         f = Fitbit(**self.client_kwargs)
         f.client._request = lambda *args, **kwargs: r
@@ -84,7 +83,7 @@ class ExceptionTest(unittest.TestCase):
         """
         r = mock.Mock(spec=requests.Response)
         r.status_code = 200
-        r.content = "iyam not jason"
+        r.content = b"iyam not jason"
 
         f = Fitbit(**self.client_kwargs)
         f.client._request = lambda *args, **kwargs: r
@@ -96,7 +95,7 @@ class ExceptionTest(unittest.TestCase):
         """
         r = mock.Mock(spec=requests.Response)
         r.status_code = 201
-        r.content = '{"it\'s all": "ok"}'
+        r.content = b'{"it\'s all": "ok"}'
 
         f = Fitbit(**self.client_kwargs)
         f.client._request = lambda *args, **kwargs: r
diff --git a/fitbit/gather_keys_cli.py b/gather_keys_cli.py
similarity index 55%
rename from fitbit/gather_keys_cli.py
rename to gather_keys_cli.py
index a11c2fb..c7b4523 100755
--- a/fitbit/gather_keys_cli.py
+++ b/gather_keys_cli.py
@@ -31,67 +31,53 @@ Instead, you'll want to create your own subclass of OAuthClient
 or find one that works with your web framework.
 """
 
-from api import FitbitOauthClient
-import time
-import oauth2 as oauth
-import urlparse
-import platform
-import subprocess
+import os
+import pprint
+import sys
+import webbrowser
 
+from fitbit.api import FitbitOauthClient
 
 
 def gather_keys():
     # setup
-    print '** OAuth Python Library Example **'
-    client = FitbitOauthClient(CONSUMER_KEY, CONSUMER_SECRET)
-
-    print ''
+    pp = pprint.PrettyPrinter(indent=4)
+    print('** OAuth Python Library Example **\n')
+    client = FitbitOauthClient(CLIENT_KEY, CLIENT_SECRET)
 
     # get request token
-    print '* Obtain a request token ...'
-    print ''
+    print('* Obtain a request token ...\n')
     token = client.fetch_request_token()
-    print 'FROM RESPONSE'
-    print 'key: %s' % str(token.key)
-    print 'secret: %s' % str(token.secret)
-    print 'callback confirmed? %s' % str(token.callback_confirmed)
-    print ''
-
-    print '* Authorize the request token in your browser'
-    print ''
-    if platform.mac_ver():
-        subprocess.Popen(['open', client.authorize_token_url(token)])
-    else:
-        print 'open: %s' % client.authorize_token_url(token)
-    print ''
-    verifier = raw_input('Verifier: ')
-    print verifier
-    print ''
+    print('RESPONSE')
+    pp.pprint(token)
+    print('')
+
+    print('* Authorize the request token in your browser\n')
+    stderr = os.dup(2)
+    os.close(2)
+    os.open(os.devnull, os.O_RDWR)
+    webbrowser.open(client.authorize_token_url())
+    os.dup2(stderr, 2)
+    try:
+        verifier = raw_input('Verifier: ')
+    except NameError:
+        # Python 3.x
+        verifier = input('Verifier: ')
 
     # get access token
-    print '* Obtain an access token ...'
-    print ''
-    print 'REQUEST (via headers)'
-    print ''
-    token = client.fetch_access_token(token, verifier)
-    print 'FROM RESPONSE'
-    print 'key: %s' % str(token.key)
-    print 'secret: %s' % str(token.secret)
-    print ''
-
+    print('\n* Obtain an access token ...\n')
+    token = client.fetch_access_token(verifier)
+    print('RESPONSE')
+    pp.pprint(token)
+    print('')
 
-def pause():
-    print ''
-    time.sleep(1)
 
 if __name__ == '__main__':
-    import sys
-
     if not (len(sys.argv) == 3):
-        print "Arguments 'client key', 'client secret' are required"
+        print("Arguments 'client key', 'client secret' are required")
         sys.exit(1)
-    CONSUMER_KEY = sys.argv[1]
-    CONSUMER_SECRET = sys.argv[2]
+    CLIENT_KEY = sys.argv[1]
+    CLIENT_SECRET = sys.argv[2]
 
     gather_keys()
-    print 'Done.'
+    print('Done.')
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index e26d6b8..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-oauth2>=1.5.211
-requests>=0.14.0
-python-dateutil>=1.5
-requests-oauthlib
\ No newline at end of file
diff --git a/requirements/base.txt b/requirements/base.txt
new file mode 100644
index 0000000..f5d86ee
--- /dev/null
+++ b/requirements/base.txt
@@ -0,0 +1,2 @@
+python-dateutil>=1.5
+requests-oauthlib>=0.4.0
diff --git a/requirements/dev.txt b/requirements/dev.txt
new file mode 100644
index 0000000..5ad7c4b
--- /dev/null
+++ b/requirements/dev.txt
@@ -0,0 +1,5 @@
+-r base.txt
+-r test.txt
+
+Sphinx==1.2.2
+tox==1.7.1
diff --git a/requirements/test.txt b/requirements/test.txt
new file mode 100644
index 0000000..969f7a2
--- /dev/null
+++ b/requirements/test.txt
@@ -0,0 +1,2 @@
+mock==1.0.1
+coverage==3.7.1
diff --git a/requirements_dev.txt b/requirements_dev.txt
deleted file mode 100644
index 5442982..0000000
--- a/requirements_dev.txt
+++ /dev/null
@@ -1,4 +0,0 @@
--r requirements.txt
--r requirements_test.txt
-Sphinx==1.1.3
-Mock
diff --git a/requirements_test.txt b/requirements_test.txt
deleted file mode 100644
index 9d11ae4..0000000
--- a/requirements_test.txt
+++ /dev/null
@@ -1 +0,0 @@
-mock==0.8.0
diff --git a/setup.py b/setup.py
index a9063b2..c0fbf8e 100644
--- a/setup.py
+++ b/setup.py
@@ -5,8 +5,8 @@ import re
 
 from setuptools import setup
 
-required = [line for line in open('requirements.txt').read().split("\n")]
-required_dev = [line for line in open('requirements_dev.txt').read().split("\n") if not line.startswith("-r")]
+required = [line for line in open('requirements/base.txt').read().split("\n")]
+required_test = [line for line in open('requirements/test.txt').read().split("\n") if not line.startswith("-r")]
 
 fbinit = open('fitbit/__init__.py').read()
 author = re.search("__author__ = '([^']+)'", fbinit).group(1)
@@ -17,7 +17,7 @@ setup(
     name='fitbit',
     version=version,
     description='Fitbit API Wrapper.',
-    long_description=open('README.rst').read(),
+    long_description=open('README.md').read(),
     author=author,
     author_email=author_email,
     url='https://github.com/orcasgit/python-fitbit',
@@ -27,7 +27,7 @@ setup(
     install_requires=["distribute"] + required,
     license='Apache 2.0',
     test_suite='fitbit_tests.all_tests',
-    tests_require=required_dev,
+    tests_require=required_test,
     classifiers=(
         'Intended Audience :: Developers',
         'Natural Language :: English',
@@ -35,5 +35,10 @@ setup(
         'Programming Language :: Python',
         'Programming Language :: Python :: 2.6',
         'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.2',
+        'Programming Language :: Python :: 3.3',
+        'Programming Language :: Python :: 3.4',
+        'Programming Language :: Python :: Implementation :: PyPy'
     ),
 )
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..e2f8462
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,24 @@
+[tox]
+envlist = pypy,py34,py33,py32,py27,py26
+
+[testenv]
+commands = coverage run --source=fitbit setup.py test
+deps = -r{toxinidir}/requirements/test.txt
+
+[testenv:pypy]
+basepython = pypy
+
+[testenv:py34]
+basepython = python3.4
+
+[testenv:py33]
+basepython = python3.3
+
+[testenv:py32]
+basepython = python3.2
+
+[testenv:py27]
+basepython = python2.7
+
+[testenv:py26]
+basepython = python2.6

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/python-fitbit.git



More information about the debian-med-commit mailing list