Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ you want to use:
token = os.environ['ACCESS_TOKEN']
client = gocardless_pro.Client(access_token=token, environment='live')

Requests time out after 60 seconds by default. To change this, pass ``timeout`` in
seconds, or a ``(connect, read)`` tuple to set the two limits separately, or ``None``
to wait indefinitely. A request that times out raises ``requests.Timeout`` once the
library has exhausted its automatic retries:

.. code:: python

client = gocardless_pro.Client(access_token=token, environment='live', timeout=10)

Access API endpoints using the corresponding methods on the client object:

.. code:: python
Expand Down
19 changes: 14 additions & 5 deletions gocardless_pro/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,22 @@
from . import errors
from .rate_limit import RateLimit, update_rate_limit

DEFAULT_TIMEOUT = 60

class ApiClient(object):
"""Client for interacting with a JSON HTTP API, using OAuth2-style auth.

Args:
base_url (string): The prefix that's prepended to all request paths.
access_token (string): Token used in the Authorization header.
timeout (float or tuple, optional): Passed to ``requests`` as its
``timeout`` argument. Defaults to 60 seconds; ``None`` waits indefinitely.
"""

def __init__(self, base_url, access_token):
def __init__(self, base_url, access_token, timeout=DEFAULT_TIMEOUT):
self.base_url = base_url
self.access_token = access_token
self.timeout = timeout
self.rate_limit = RateLimit()

@update_rate_limit
Expand All @@ -46,7 +51,8 @@ def get(self, path, params=None, headers=None):
response = requests.get(
self._url_for(path),
params=params,
headers=self._headers(headers)
headers=self._headers(headers),
timeout=self.timeout
)
self._handle_errors(response)
return response
Expand All @@ -69,7 +75,8 @@ def post(self, path, body, headers=None):
response = requests.post(
self._url_for(path),
data=json.dumps(body),
headers=self._headers(headers)
headers=self._headers(headers),
timeout=self.timeout
)
self._handle_errors(response)
return response
Expand All @@ -91,7 +98,8 @@ def put(self, path, body, headers=None):
response = requests.put(
self._url_for(path),
data=json.dumps(body),
headers=self._headers(headers)
headers=self._headers(headers),
timeout=self.timeout
)
self._handle_errors(response)
return response
Expand All @@ -113,7 +121,8 @@ def delete(self, path, body, headers=None):
response = requests.delete(
self._url_for(path),
data=json.dumps(body),
headers=self._headers(headers)
headers=self._headers(headers),
timeout=self.timeout
)
self._handle_errors(response)
return response
Expand Down
10 changes: 7 additions & 3 deletions gocardless_pro/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#

from . import services
from .api_client import ApiClient
from .api_client import ApiClient, DEFAULT_TIMEOUT

class Client(object):
"""Client for interacting with the GoCardless API.
Expand All @@ -19,22 +19,26 @@ class Client(object):
base_url (str): Manually set a base URL. Most people should use
`environment` instead.
raise_on_idempotency_conflict (bool): Configure refetching of conflicting resource
timeout (float or tuple): Seconds to wait for the API to respond before
raising ``requests.Timeout``. Pass a ``(connect, read)`` tuple to set
the two limits separately. Defaults to 60 seconds; pass ``None``
to wait indefinitely.

Example:
client = Client(access_token=ACCESS_TOKEN, environment='sandbox')
for customer in client.customers.list():
print('{} {}'.format(customer.family_name, customer.given_name))
"""

def __init__(self, access_token=None, environment=None, base_url=None, raise_on_idempotency_conflict=False):
def __init__(self, access_token=None, environment=None, base_url=None, raise_on_idempotency_conflict=False, timeout=DEFAULT_TIMEOUT):
if access_token is None:
raise ValueError('No access_token provided')

if environment is None and base_url is None:
raise ValueError('No environment or base_url specified')

base_url = base_url or self._environment_url(environment)
self._api_client = ApiClient(base_url, access_token)
self._api_client = ApiClient(base_url, access_token, timeout=timeout)
self._raise_on_idempotency_conflict = raise_on_idempotency_conflict

@property
Expand Down
24 changes: 24 additions & 0 deletions tests/api_client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,30 @@ def test_uses_correct_url():
responses.add(responses.GET, 'http://example.com/test', body='{}')
client.get('/test')

@responses.activate
def test_sends_a_default_timeout():
responses.add(responses.GET, 'http://example.com/test', body='{}')
client.get('/test')
assert responses.calls[0].request.req_kwargs['timeout'] == 60

@responses.activate
def test_timeout_can_be_disabled_with_none():
no_timeout_client = api_client.ApiClient('http://example.com', access_token, timeout=None)
responses.add(responses.GET, 'http://example.com/test', body='{}')
no_timeout_client.get('/test')
assert responses.calls[0].request.req_kwargs['timeout'] is None

@responses.activate
@pytest.mark.parametrize('method', ['get', 'post', 'put', 'delete'])
def test_passes_timeout_to_requests(method):
timeout_client = api_client.ApiClient('http://example.com', access_token, timeout=(3, 10))
responses.add(getattr(responses, method.upper()), 'http://example.com/test', body='{}')
if method == 'get':
timeout_client.get('/test')
else:
getattr(timeout_client, method)('/test', {})
assert responses.calls[0].request.req_kwargs['timeout'] == (3, 10)

@responses.activate
def test_authorization_header_present():
responses.add(responses.GET, 'http://example.com/test', body='{}')
Expand Down
7 changes: 7 additions & 0 deletions tests/client_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ def test_requires_valid_environment():
with pytest.raises(ValueError):
Client(access_token=access_token, environment='invalid')

def test_has_a_default_timeout():
assert client._api_client.timeout == 60

def test_passes_timeout_to_api_client():
timeout_client = Client(access_token=access_token, base_url='http://example.com', timeout=5)
assert timeout_client._api_client.timeout == 5


def test_balances_returns_service():
assert isinstance(client.balances, services.BalancesService)
Expand Down
Loading