[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