diff --git a/fitbit/api.py b/fitbit/api.py index 1984135..7e41509 100644 --- a/fitbit/api.py +++ b/fitbit/api.py @@ -14,7 +14,7 @@ from fitbit.exceptions import (BadResponse, DeleteError, HTTPBadRequest, HTTPUnauthorized, HTTPForbidden, HTTPServerError, HTTPConflict, HTTPNotFound, - HTTPTooManyRequests) + HTTPTooManyRequests, Timeout) from fitbit.utils import curry @@ -49,12 +49,19 @@ def __init__(self, client_id, client_secret, } self.refresh_cb = refresh_cb self.oauth = OAuth2Session(client_id) + self.timeout = kwargs.get("timeout", None) def _request(self, method, url, **kwargs): """ A simple wrapper around requests. """ - return self.session.request(method, url, **kwargs) + if self.timeout is not None and 'timeout' not in kwargs: + kwargs['timeout'] = self.timeout + + try: + return self.session.request(method, url, **kwargs) + except requests.Timeout as e: + raise Timeout(*e.args) def make_request(self, url, data={}, method=None, **kwargs): """ diff --git a/fitbit/exceptions.py b/fitbit/exceptions.py index d6249ea..8eb774a 100644 --- a/fitbit/exceptions.py +++ b/fitbit/exceptions.py @@ -15,6 +15,13 @@ class DeleteError(Exception): pass +class Timeout(Exception): + """ + Used when a timeout occurs. + """ + pass + + class HTTPException(Exception): def __init__(self, response, *args, **kwargs): try: diff --git a/fitbit_tests/test_api.py b/fitbit_tests/test_api.py index 651a189..56fae78 100644 --- a/fitbit_tests/test_api.py +++ b/fitbit_tests/test_api.py @@ -1,8 +1,9 @@ from unittest import TestCase import datetime import mock +import requests from fitbit import Fitbit -from fitbit.exceptions import DeleteError +from fitbit.exceptions import DeleteError, Timeout URLBASE = "%s/%s/user" % (Fitbit.API_ENDPOINT, Fitbit.API_VERSION) @@ -24,6 +25,49 @@ def verify_raises(self, funcname, args, kwargs, exc): self.assertRaises(exc, getattr(self.fb, funcname), *args, **kwargs) +class TimeoutTest(TestCase): + + def setUp(self): + self.fb = Fitbit('x', 'y') + self.fb_timeout = Fitbit('x', 'y', timeout=10) + + self.test_url = 'invalid://do.not.connect' + + def test_fb_without_timeout(self): + with mock.patch.object(self.fb.client.session, 'request') as request: + mock_response = mock.Mock() + mock_response.status_code = 200 + mock_response.content = b'{}' + request.return_value = mock_response + result = self.fb.make_request(self.test_url) + + request.assert_called_once() + self.assertNotIn('timeout', request.call_args[1]) + self.assertEqual({}, result) + + def test_fb_with_timeout__timing_out(self): + with mock.patch.object(self.fb_timeout.client.session, 'request') as request: + request.side_effect = requests.Timeout('Timed out') + with self.assertRaisesRegexp(Timeout, 'Timed out'): + self.fb_timeout.make_request(self.test_url) + + request.assert_called_once() + self.assertEqual(10, request.call_args[1]['timeout']) + + def test_fb_with_timeout__not_timing_out(self): + with mock.patch.object(self.fb_timeout.client.session, 'request') as request: + mock_response = mock.Mock() + mock_response.status_code = 200 + mock_response.content = b'{}' + request.return_value = mock_response + + result = self.fb_timeout.make_request(self.test_url) + + request.assert_called_once() + self.assertEqual(10, request.call_args[1]['timeout']) + self.assertEqual({}, result) + + class APITest(TestBase): """ Tests for python-fitbit API, not directly involved in getting