Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit e744fad

Browse files
authored
Merge pull request #187 from cloudant/60008-optional-429
Made 429 replays optional and configurable
2 parents 0926276 + d150a61 commit e744fad

File tree

6 files changed

+136
-18
lines changed

6 files changed

+136
-18
lines changed

CHANGES.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
- [NEW] Added support for Cloudant Search execution.
44
- [NEW] Added support for Cloudant Search index management.
55
- [NEW] Added ``rewrites`` accessor property for URL rewriting.
6+
- [NEW] Added support for a custom ``requests.HTTPAdapter`` to be configured using an optional ``adapter`` arg e.g.
7+
``Cloudant(USERNAME, PASSWORD, account=ACCOUNT_NAME, adapter=Replay429Adapter())``.
8+
- [IMPROVED] Made the 429 response code backoff optional and configurable. To enable the backoff add
9+
an ``adapter`` arg of a ``Replay429Adapter`` with the desired number of retries and initial backoff. To replicate
10+
the 2.0.0 behaviour use: ``adapter=Replay429Adapter(retries=10, initialBackoff=0.25)``. If ``retries`` or
11+
``initialBackoff`` are not specified they will default to 3 retries and a 0.25 s initial backoff.
612

713
2.0.3 (2016-06-03)
814
==================

docs/adapters.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
adapters
2+
========
3+
4+
.. automodule:: cloudant.adapters
5+
:members:
6+
:undoc-members:
7+
:special-members: __getitem__, __iter__
8+
:show-inheritance:

docs/modules.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ Modules
1515
replicator
1616
feed
1717
error
18+
adapters

src/cloudant/adapters.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# Copyright © 2016 IBM Corp. All rights reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
"""
17+
Module that contains default transport adapters for use with requests.
18+
"""
19+
from requests.adapters import HTTPAdapter
20+
21+
from requests.packages.urllib3.util import Retry
22+
23+
class Replay429Adapter(HTTPAdapter):
24+
"""
25+
A requests TransportAdapter that extends the default HTTPAdapter with configuration
26+
to replay requests that receive a 429 Too Many Requests response from the server.
27+
The duration of the sleep between requests will be doubled for each 429 response
28+
received.
29+
30+
Parameters can be passed in to control behavior:
31+
32+
:param int retries: the number of times the request can be replayed before failing.
33+
:param float initialBackoff: time in seconds for the first backoff.
34+
"""
35+
def __init__(self, retries=3, initialBackoff=0.25):
36+
super(Replay429Adapter, self).__init__(max_retries=Retry(
37+
# Configure the number of retries for status codes
38+
total=retries,
39+
# No retries for connect|read errors
40+
connect=0,
41+
read=0,
42+
# Allow retries for all the CouchDB HTTP method types
43+
method_whitelist=frozenset(['GET', 'HEAD', 'PUT', 'POST',
44+
'DELETE', 'COPY']),
45+
# Only retry for a 429 too many requests status code
46+
status_forcelist=[429],
47+
# Configure the start value of the doubling backoff
48+
backoff_factor=initialBackoff))

src/cloudant/client.py

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python
2-
# Copyright (c) 2015, 2016 IBM. All rights reserved.
2+
# Copyright (C) 2015, 2016 IBM Corp. All rights reserved.
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
55
# you may not use this file except in compliance with the License.
@@ -21,8 +21,6 @@
2121
import posixpath
2222
import requests
2323

24-
from requests.adapters import HTTPAdapter
25-
from requests.packages.urllib3.util import Retry
2624
from ._2to3 import bytes_, unicode_
2725
from .database import CloudantDatabase, CouchDatabase
2826
from .feed import Feed, InfiniteFeed
@@ -46,6 +44,7 @@ class CouchDB(dict):
4644
CouchDB. Defaults to ``False``.
4745
:param str encoder: Optional json Encoder object used to encode
4846
documents for storage. Defaults to json.JSONEncoder.
47+
:param requests.HTTPAdapter adapter: Optional adapter to use for configuring requests.
4948
"""
5049
_DATABASE_CLASS = CouchDatabase
5150

@@ -58,6 +57,7 @@ def __init__(self, user, auth_token, admin_party=False, **kwargs):
5857
self._client_user_header = None
5958
self.admin_party = admin_party
6059
self.encoder = kwargs.get('encoder') or json.JSONEncoder
60+
self.adapter = kwargs.get('adapter')
6161
self.r_session = None
6262

6363
def connect(self):
@@ -66,21 +66,9 @@ def connect(self):
6666
authentication.
6767
"""
6868
self.r_session = requests.Session()
69-
# Configure a Transport Adapter for custom retry behaviour
70-
self.r_session.mount(self.server_url, HTTPAdapter(
71-
max_retries=Retry(
72-
# Allow 10 retries for status
73-
total=10,
74-
# No retries for connect|read errors
75-
connect=0,
76-
read=0,
77-
# Allow retries for all the CouchDB HTTP method types
78-
method_whitelist=frozenset(['GET', 'HEAD', 'PUT', 'POST',
79-
'DELETE', 'COPY']),
80-
# Only retry for a 429 too many requests status code
81-
status_forcelist=[429],
82-
# Configure the doubling backoff to start at 0.25 s
83-
backoff_factor=0.25)))
69+
# If a Transport Adapter was supplied add it to the session
70+
if self.adapter is not None:
71+
self.r_session.mount(self.server_url, self.adapter)
8472
if self._client_user_header is not None:
8573
self.r_session.headers.update(self._client_user_header)
8674
if not self.admin_party:
@@ -394,6 +382,7 @@ class Cloudant(CouchDB):
394382
the x_cloudant_user parameter setting is ignored.
395383
:param str encoder: Optional json Encoder object used to encode
396384
documents for storage. Defaults to json.JSONEncoder.
385+
:param requests.HTTPAdapter adapter: Optional adapter to use for configuring requests.
397386
"""
398387
_DATABASE_CLASS = CloudantDatabase
399388

tests/unit/adapter_tests.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# Copyright © 2016 IBM Corp. All rights reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
from cloudant.client import CouchDB
17+
from cloudant.adapters import Replay429Adapter
18+
from requests.packages.urllib3.util import Retry
19+
from .unit_t_db_base import UnitTestDbBase
20+
21+
class AdapterTests(UnitTestDbBase):
22+
"""
23+
Unit tests for transport adapters
24+
"""
25+
26+
def test_new_Replay429Adapter(self):
27+
"""
28+
Test that a new Replay429Adapter is accepted as a parameter for a client.
29+
"""
30+
self.client = CouchDB(
31+
self.user,
32+
self.pwd,
33+
url=self.url,
34+
adapter=Replay429Adapter())
35+
36+
def test_retries_arg_Replay429Adapter(self):
37+
"""
38+
Test constructing a new Replay429Adapter with a configured number of retries.
39+
"""
40+
self.client = CouchDB(
41+
self.user,
42+
self.pwd,
43+
url=self.url,
44+
adapter=Replay429Adapter(retries=10))
45+
46+
47+
48+
def test_backoff_arg_Replay429Adapter(self):
49+
"""
50+
Test constructing a new Replay429Adapter with a configured initial backoff.
51+
"""
52+
self.client = CouchDB(
53+
self.user,
54+
self.pwd,
55+
url=self.url,
56+
adapter=Replay429Adapter(initialBackoff=0.1))
57+
58+
def test_args_Replay429Adapter(self):
59+
"""
60+
Test constructing a new Replay429Adapter with configured retries and initial backoff.
61+
"""
62+
self.client = CouchDB(
63+
self.user,
64+
self.pwd,
65+
url=self.url,
66+
adapter=Replay429Adapter(retries=10, initialBackoff=0.01))

0 commit comments

Comments
 (0)