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

Commit 048c50a

Browse files
authored
Merge pull request #412 from cloudant/409-workaround-requests
Replaced usage of requests `response.json()` method
2 parents cdffb88 + cfe4ae1 commit 048c50a

18 files changed

+263
-235
lines changed

CHANGES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# Unreleased
2+
3+
- [FIXED] Unexpected keyword argument errors when using the library with the
4+
`simplejson` module present in the environment caused by `requests` preferentially
5+
loading it over the system `json` module.
6+
17
# 2.10.0 (2018-09-19)
28

39
- [NEW] Add custom JSON encoder/decoder option to `Document` constructor.

Jenkinsfile

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ def getEnvForSuite(suiteName) {
1010
case 'basic':
1111
envVars.add("RUN_BASIC_AUTH_TESTS=1")
1212
break
13-
case 'cookie':
14-
break
1513
case 'iam':
1614
// Setting IAM_API_KEY forces tests to run using an IAM enabled client.
1715
envVars.add("IAM_API_KEY=$DB_IAM_API_KEY")
1816
break
17+
case 'cookie':
18+
case 'simplejson':
19+
break
1920
default:
2021
error("Unknown test suite environment ${suiteName}")
2122
}
@@ -36,6 +37,7 @@ def setupPythonAndTest(pythonVersion, testSuite) {
3637
. ./tmp/bin/activate
3738
pip install -r requirements.txt
3839
pip install -r test-requirements.txt
40+
${'simplejson'.equals(testSuite) ? 'pip install simplejson' : ''}
3941
pylint ./src/cloudant
4042
nosetests -A 'not db or (db is "cloudant" or "cloudant" in db)' -w ./tests/unit --with-xunit
4143
"""
@@ -58,12 +60,15 @@ stage('Checkout'){
5860
}
5961

6062
stage('Test'){
61-
axes = [:]
62-
['2.7.12','3.5.2'].each { version ->
63+
def py2 = '2.7.12'
64+
def py3 = '3.5.2'
65+
def axes = [:]
66+
[py2, py3].each { version ->
6367
['basic','cookie','iam'].each { auth ->
6468
axes.put("Python${version}-${auth}", {setupPythonAndTest(version, auth)})
6569
}
6670
}
71+
axes.put("Python${py3}-simplejson", {setupPythonAndTest(py3, 'simplejson')})
6772
parallel(axes)
6873
}
6974

src/cloudant/_client_session.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python
2-
# Copyright (c) 2015, 2018 IBM Corp. All rights reserved.
2+
# Copyright (C) 2015, 2018 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.
@@ -22,6 +22,7 @@
2222
from requests import RequestException, Session
2323

2424
from ._2to3 import bytes_, unicode_, url_join
25+
from ._common_util import response_to_json_dict
2526
from .error import CloudantException
2627

2728

@@ -75,7 +76,7 @@ def info(self):
7576

7677
resp = self.get(self._session_url)
7778
resp.raise_for_status()
78-
return resp.json()
79+
return response_to_json_dict(resp)
7980

8081
def set_credentials(self, username, password):
8182
"""
@@ -170,7 +171,7 @@ def request(self, method, url, **kwargs):
170171

171172
is_expired = any((
172173
resp.status_code == 403 and
173-
resp.json().get('error') == 'credentials_expired',
174+
response_to_json_dict(resp).get('error') == 'credentials_expired',
174175
resp.status_code == 401
175176
))
176177

@@ -282,10 +283,10 @@ def _get_access_token(self):
282283
'apikey': self._api_key
283284
}
284285
)
285-
err = resp.json().get('errorMessage', err)
286+
err = response_to_json_dict(resp).get('errorMessage', err)
286287
resp.raise_for_status()
287288

288-
return resp.json()['access_token']
289+
return response_to_json_dict(resp)['access_token']
289290

290291
except KeyError:
291292
raise CloudantException('Invalid response from IAM token service')

src/cloudant/_common_util.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def append_response_error_content(response, **kwargs):
270270
"""
271271
if response.status_code >= 400:
272272
try:
273-
resp_dict = response.json()
273+
resp_dict = response_to_json_dict(response)
274274
error = resp_dict.get('error', '')
275275
reason = resp_dict.get('reason', '')
276276
# Append to the existing response's reason
@@ -279,6 +279,17 @@ def append_response_error_content(response, **kwargs):
279279
pass
280280
return response
281281

282+
def response_to_json_dict(response, **kwargs):
283+
"""
284+
Standard place to convert responses to JSON.
285+
286+
:param response: requests response object
287+
:param **kwargs: arguments accepted by json.loads
288+
289+
:returns: dict of JSON response
290+
"""
291+
return json.loads(response.text, **kwargs)
292+
282293
# Classes
283294

284295

src/cloudant/client.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
USER_AGENT,
3636
append_response_error_content,
3737
CloudFoundryService,
38+
response_to_json_dict,
3839
)
3940

4041

@@ -256,7 +257,7 @@ def all_dbs(self):
256257
url = '/'.join((self.server_url, '_all_dbs'))
257258
resp = self.r_session.get(url)
258259
resp.raise_for_status()
259-
return resp.json()
260+
return response_to_json_dict(resp)
260261

261262
def create_database(self, dbname, **kwargs):
262263
"""
@@ -345,7 +346,7 @@ def metadata(self):
345346
"""
346347
resp = self.r_session.get(self.server_url)
347348
resp.raise_for_status()
348-
return resp.json()
349+
return response_to_json_dict(resp)
349350

350351
def keys(self, remote=False):
351352
"""
@@ -622,7 +623,7 @@ def _usage_endpoint(self, endpoint, year=None, month=None):
622623
raise CloudantArgumentError(101, year, month)
623624
else:
624625
resp.raise_for_status()
625-
return resp.json()
626+
return response_to_json_dict(resp)
626627

627628
def bill(self, year=None, month=None):
628629
"""
@@ -687,7 +688,7 @@ def shared_databases(self):
687688
self.server_url, '_api', 'v2', 'user', 'shared_databases'))
688689
resp = self.r_session.get(endpoint)
689690
resp.raise_for_status()
690-
data = resp.json()
691+
data = response_to_json_dict(resp)
691692
return data.get('shared_databases', [])
692693

693694
def generate_api_key(self):
@@ -699,7 +700,7 @@ def generate_api_key(self):
699700
endpoint = '/'.join((self.server_url, '_api', 'v2', 'api_keys'))
700701
resp = self.r_session.post(endpoint)
701702
resp.raise_for_status()
702-
return resp.json()
703+
return response_to_json_dict(resp)
703704

704705
def cors_configuration(self):
705706
"""
@@ -712,7 +713,7 @@ def cors_configuration(self):
712713
resp = self.r_session.get(endpoint)
713714
resp.raise_for_status()
714715

715-
return resp.json()
716+
return response_to_json_dict(resp)
716717

717718
def disable_cors(self):
718719
"""
@@ -807,7 +808,7 @@ def _write_cors_configuration(self, config):
807808
)
808809
resp.raise_for_status()
809810

810-
return resp.json()
811+
return response_to_json_dict(resp)
811812

812813
@classmethod
813814
def bluemix(cls, vcap_services, instance_name=None, service_name=None, **kwargs):

src/cloudant/database.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
SEARCH_INDEX_ARGS,
2727
SPECIAL_INDEX_TYPE,
2828
TEXT_INDEX_TYPE,
29-
get_docs)
29+
get_docs,
30+
response_to_json_dict,
31+
)
3032
from .document import Document
3133
from .design_document import DesignDocument
3234
from .security_document import SecurityDocument
@@ -123,7 +125,7 @@ def metadata(self):
123125
"""
124126
resp = self.r_session.get(self.database_url)
125127
resp.raise_for_status()
126-
return resp.json()
128+
return response_to_json_dict(resp)
127129

128130
def doc_count(self):
129131
"""
@@ -192,7 +194,7 @@ def design_documents(self):
192194
query = "startkey=\"_design\"&endkey=\"_design0\"&include_docs=true"
193195
resp = self.r_session.get(url, params=query)
194196
resp.raise_for_status()
195-
data = resp.json()
197+
data = response_to_json_dict(resp)
196198
return data['rows']
197199

198200
def list_design_documents(self):
@@ -206,7 +208,7 @@ def list_design_documents(self):
206208
query = "startkey=\"_design\"&endkey=\"_design0\""
207209
resp = self.r_session.get(url, params=query)
208210
resp.raise_for_status()
209-
data = resp.json()
211+
data = response_to_json_dict(resp)
210212
return [x.get('key') for x in data.get('rows', [])]
211213

212214
def get_design_document(self, ddoc_id):
@@ -403,7 +405,7 @@ def all_docs(self, **kwargs):
403405
'/'.join([self.database_url, '_all_docs']),
404406
self.client.encoder,
405407
**kwargs)
406-
return resp.json()
408+
return response_to_json_dict(resp)
407409

408410
@contextlib.contextmanager
409411
def custom_result(self, **options):
@@ -718,7 +720,7 @@ def bulk_docs(self, docs):
718720
headers=headers
719721
)
720722
resp.raise_for_status()
721-
return resp.json()
723+
return response_to_json_dict(resp)
722724

723725
def missing_revisions(self, doc_id, *revisions):
724726
"""
@@ -742,7 +744,7 @@ def missing_revisions(self, doc_id, *revisions):
742744
)
743745
resp.raise_for_status()
744746

745-
resp_json = resp.json()
747+
resp_json = response_to_json_dict(resp)
746748
missing_revs = resp_json['missing_revs'].get(doc_id)
747749
if missing_revs is None:
748750
missing_revs = []
@@ -771,7 +773,7 @@ def revisions_diff(self, doc_id, *revisions):
771773
)
772774
resp.raise_for_status()
773775

774-
return resp.json()
776+
return response_to_json_dict(resp)
775777

776778
def get_revision_limit(self):
777779
"""
@@ -787,7 +789,7 @@ def get_revision_limit(self):
787789
try:
788790
ret = int(resp.text)
789791
except ValueError:
790-
raise CloudantDatabaseException(400, resp.json())
792+
raise CloudantDatabaseException(400, response_to_json_dict(resp))
791793

792794
return ret
793795

@@ -806,7 +808,7 @@ def set_revision_limit(self, limit):
806808
resp = self.r_session.put(url, data=json.dumps(limit, cls=self.client.encoder))
807809
resp.raise_for_status()
808810

809-
return resp.json()
811+
return response_to_json_dict(resp)
810812

811813
def view_cleanup(self):
812814
"""
@@ -822,7 +824,7 @@ def view_cleanup(self):
822824
)
823825
resp.raise_for_status()
824826

825-
return resp.json()
827+
return response_to_json_dict(resp)
826828

827829
def get_list_function_result(self, ddoc_id, list_name, view_name, **kwargs):
828830
"""
@@ -974,10 +976,10 @@ def get_query_indexes(self, raw_result=False):
974976
resp.raise_for_status()
975977

976978
if raw_result:
977-
return resp.json()
979+
return response_to_json_dict(resp)
978980

979981
indexes = []
980-
for data in resp.json().get('indexes', []):
982+
for data in response_to_json_dict(resp).get('indexes', []):
981983
if data.get('type') == JSON_INDEX_TYPE:
982984
indexes.append(Index(
983985
self,
@@ -1239,7 +1241,7 @@ def share_database(self, username, roles=None):
12391241
headers={'Content-Type': 'application/json'}
12401242
)
12411243
resp.raise_for_status()
1242-
return resp.json()
1244+
return response_to_json_dict(resp)
12431245

12441246
def unshare_database(self, username):
12451247
"""
@@ -1264,7 +1266,7 @@ def unshare_database(self, username):
12641266
headers={'Content-Type': 'application/json'}
12651267
)
12661268
resp.raise_for_status()
1267-
return resp.json()
1269+
return response_to_json_dict(resp)
12681270

12691271
def shards(self):
12701272
"""
@@ -1276,7 +1278,7 @@ def shards(self):
12761278
resp = self.r_session.get(url)
12771279
resp.raise_for_status()
12781280

1279-
return resp.json()
1281+
return response_to_json_dict(resp)
12801282

12811283
def get_search_result(self, ddoc_id, index_name, **query_params):
12821284
"""
@@ -1399,4 +1401,4 @@ def get_search_result(self, ddoc_id, index_name, **query_params):
13991401
data=json.dumps(query_params, cls=self.client.encoder)
14001402
)
14011403
resp.raise_for_status()
1402-
return resp.json()
1404+
return response_to_json_dict(resp)

src/cloudant/design_document.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python
2-
# Copyright (c) 2015 IBM. All rights reserved.
2+
# Copyright (C) 2015, 2018 IBM. 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.
@@ -16,7 +16,7 @@
1616
API module/class for interacting with a design document in a database.
1717
"""
1818
from ._2to3 import iteritems_, STRTYPE
19-
from ._common_util import QUERY_LANGUAGE, codify
19+
from ._common_util import QUERY_LANGUAGE, codify, response_to_json_dict
2020
from .document import Document
2121
from .view import View, QueryIndexView
2222
from .error import CloudantArgumentError, CloudantDesignDocumentException
@@ -686,7 +686,7 @@ def info(self):
686686
ddoc_info = self.r_session.get(
687687
'/'.join([self.document_url, '_info']))
688688
ddoc_info.raise_for_status()
689-
return ddoc_info.json()
689+
return response_to_json_dict(ddoc_info)
690690

691691
def search_info(self, search_index):
692692
"""
@@ -698,7 +698,7 @@ def search_info(self, search_index):
698698
ddoc_search_info = self.r_session.get(
699699
'/'.join([self.document_url, '_search_info', search_index]))
700700
ddoc_search_info.raise_for_status()
701-
return ddoc_search_info.json()
701+
return response_to_json_dict(ddoc_search_info)
702702

703703
def search_disk_size(self, search_index):
704704
"""
@@ -710,4 +710,4 @@ def search_disk_size(self, search_index):
710710
ddoc_search_disk_size = self.r_session.get(
711711
'/'.join([self.document_url, '_search_disk_size', search_index]))
712712
ddoc_search_disk_size.raise_for_status()
713-
return ddoc_search_disk_size.json()
713+
return response_to_json_dict(ddoc_search_disk_size)

0 commit comments

Comments
 (0)