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

Commit bf21b56

Browse files
authored
Use POST request for some datatables routes if request length is long (#126)
* Initial work on getting post requests working for datatable route * Initial work on getting post requests working for datatable export route * Additional work regarding post request body * Begin work on formatting post request arguments correctly * Format dictionary params correctly for post request * Update failing connection tests * Add .zip files to gitignore * Fix some failing tests due to httppretty * Add sanity tests for function determining request type * Add additional tests for modifying arguments to get/post request params * Add venv to flake8 ignore list * Run existing connection tests for both get and post requests * Fix some flake8 warnings * Update some datatable tests to account for get and post requests * Update some datatable data tests for get/post requests * Add config to always use post request for testing purposes * Add some more tests to ensure request made with correct params * Fix incorrect params format being passed to mocked tests * Add comment regarding 8000 character get request limit * Fix line being over 100 characters in length * Update version.py and changelog
1 parent 3314981 commit bf21b56

18 files changed

+437
-73
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
*.pyc
2+
*.zip
23

34
/.tox/
45
/.eggs

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
### unreleased
22
* Remove dependency on unittest2, use unittest instead (#113)
33

4+
### 3.4.5 - 2018-11-21
5+
6+
* Use POST requests for some datatable calls https://github.com/quandl/quandl-python/pull/126
7+
48
### 3.4.4 - 2018-10-24
59

610
* Add functionality to automatically retry failed API calls https://github.com/quandl/quandl-python/pull/124

quandl/model/datatable.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from quandl.errors.quandl_error import QuandlError
1212
from quandl.operations.get import GetOperation
1313
from quandl.operations.list import ListOperation
14+
from quandl.utils.request_type_util import RequestType
1415

1516
from .model_base import ModelBase
1617
from quandl.message import Message
@@ -26,8 +27,9 @@ def get_path(cls):
2627
return "%s/metadata" % cls.default_path()
2728

2829
def data(self, **options):
29-
updated_options = Util.convert_options(**options)
30-
return Data.page(self, **updated_options)
30+
if not options:
31+
options = {'params': {}}
32+
return Data.page(self, **options)
3133

3234
def download_file(self, file_or_folder_path, **options):
3335
if not isinstance(file_or_folder_path, str):
@@ -36,19 +38,21 @@ def download_file(self, file_or_folder_path, **options):
3638
file_is_ready = False
3739

3840
while not file_is_ready:
39-
file_is_ready = self._request_file_info(file_or_folder_path, **options)
41+
file_is_ready = self._request_file_info(file_or_folder_path, params=options)
4042
if not file_is_ready:
4143
print(Message.LONG_GENERATION_TIME)
4244
sleep(self.WAIT_GENERATION_INTERVAL)
4345

4446
def _request_file_info(self, file_or_folder_path, **options):
4547
url = self._download_request_path()
46-
updated_options = Util.convert_options(params=options)
4748
code_name = self.code
49+
options['params']['qopts.export'] = 'true'
4850

49-
updated_options['params']['qopts.export'] = 'true'
51+
request_type = RequestType.get_request_type(url, **options)
5052

51-
r = Connection.request('get', url, **updated_options)
53+
updated_options = Util.convert_options(request_type=request_type, **options)
54+
55+
r = Connection.request(request_type, url, **updated_options)
5256

5357
response_data = r.json()
5458

quandl/operations/list.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from quandl.connection import Connection
33
from quandl.util import Util
44
from quandl.model.paginated_list import PaginatedList
5+
from quandl.utils.request_type_util import RequestType
56

67

78
class ListOperation(Operation):
@@ -21,7 +22,13 @@ def all(cls, **options):
2122
def page(cls, datatable, **options):
2223
params = {'id': str(datatable.code)}
2324
path = Util.constructed_path(datatable.default_path(), params)
24-
r = Connection.request('get', path, **options)
25+
26+
request_type = RequestType.get_request_type(path, **options)
27+
28+
updated_options = Util.convert_options(request_type=request_type, **options)
29+
30+
r = Connection.request(request_type, path, **updated_options)
31+
2532
response_data = r.json()
2633
Util.convert_to_dates(response_data)
2734
resource = cls.create_datatable_list_from_response(response_data)

quandl/util.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,16 @@ def convert_to_date(value):
6060
return value
6161

6262
@staticmethod
63-
def convert_options(**options):
63+
def convert_options(request_type, **options):
64+
if request_type == 'get':
65+
return Util._convert_options_for_get_request(**options)
66+
elif request_type == 'post':
67+
return Util._convert_options_for_post_request(**options)
68+
else:
69+
raise Exception('Can only convert options for get or post requests')
70+
71+
@staticmethod
72+
def _convert_options_for_get_request(**options):
6473
new_options = dict()
6574
if 'params' in options.keys():
6675
for key, value in options['params'].items():
@@ -85,6 +94,28 @@ def convert_options(**options):
8594
new_options[key] = value
8695
return {'params': new_options}
8796

97+
@staticmethod
98+
def _convert_options_for_post_request(**options):
99+
new_options = dict()
100+
if 'params' in options.keys():
101+
for key, value in options['params'].items():
102+
if isinstance(value, dict) and value != {}:
103+
new_value = dict()
104+
is_dict = True
105+
old_key = key
106+
for k, v in value.items():
107+
key = key + '.' + k
108+
new_value[key] = v
109+
key = old_key
110+
else:
111+
is_dict = False
112+
113+
if is_dict:
114+
new_options.update(new_value)
115+
else:
116+
new_options[key] = value
117+
return {'json': new_options}
118+
88119
@staticmethod
89120
def convert_to_columns_list(meta, type):
90121
columns = []

quandl/utils/request_type_util.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
try:
2+
from urllib import urlencode
3+
except ImportError:
4+
from urllib.parse import urlencode
5+
6+
from quandl.api_config import ApiConfig
7+
8+
9+
class RequestType(object):
10+
""" Determines whether a request should be made using a GET or a POST request.
11+
Default limit of 8000 is set here as it appears to be the maximum for many
12+
webservers.
13+
"""
14+
MAX_URL_LENGTH_FOR_GET = 8000
15+
USE_GET_REQUEST = True # This is used to simplify testing code
16+
17+
@classmethod
18+
def get_request_type(cls, url, **params):
19+
query_string = urlencode(params['params'])
20+
request_url = '%s/%s/%s' % (ApiConfig.api_base, url, query_string)
21+
if RequestType.USE_GET_REQUEST and (len(request_url) < cls.MAX_URL_LENGTH_FOR_GET):
22+
return 'get'
23+
else:
24+
return 'post'

quandl/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = '3.4.4'
1+
VERSION = '3.4.5'

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ universal = 1
33

44
[flake8]
55
max-line-length = 100
6-
exclude = .git,__init__.py,tmp,__pycache__,.eggs,Quandl.egg-info,build,dist,.tox
6+
exclude = .git,__init__.py,tmp,__pycache__,.eggs,Quandl.egg-info,build,dist,.tox,venv

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@
6161
'httpretty',
6262
'mock',
6363
'factory_boy',
64-
'jsondate'
64+
'jsondate',
65+
'parameterized'
6566
],
6667
test_suite="nose.collector",
6768
packages=packages

test/helpers/random_data_helper.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import random
2+
import string
3+
4+
5+
def generate_random_string(n=10):
6+
return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(n))
7+
8+
9+
def generate_random_dictionary(n):
10+
random_dictionary = dict()
11+
for _ in range(n):
12+
random_dictionary[generate_random_string()] = generate_random_string()
13+
return random_dictionary

0 commit comments

Comments
 (0)