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

Commit d150a61

Browse files
committed
Made 429 replays optional and configurable
Added support for adapter argument on client constructors. Moved 429 HTTPAdapter configuration into Replay429Adapter class in cloudant.adapters. Added retries and initialBackoff args to Replay429Adapter. Added tests for init and adding adapter with Replay429Adapter and args. Updated documentation including CHANGES.rst and new adapters module.
1 parent 0926276 commit d150a61

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)