This repository has been archived by the owner on Apr 30, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 339
add exporting table files #120
Merged
Merged
Changes from 13 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
319e032
add exporting table files
d3c4e58
modify the way we handle long generation time
87ff77a
modify test
ff664a8
flake 8 fix
debf27c
update docs and some small changes
57aa3b9
fix in test
5ca5c98
support filters, rename function name
c5e998b
minor update in readme
7c35fd4
PR review feedback
1c94ae3
small funtion update
f2e219f
use while to avoid recursion
63eb730
text tweak
c713f0c
update readme
3d8ea55
text tweak
c681509
refactor and add test
87b1a14
change the way parameter is being passed
4c2924e
change parameters
1bbf98e
flake8 fix
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from quandl.errors.quandl_error import InvalidRequestError | ||
from .utils.api_key_util import ApiKeyUtil | ||
from .model.datatable import Datatable | ||
from .message import Message | ||
|
||
|
||
def export_table(datatable_code, **kwargs): | ||
"""Downloads an entire table as a zip file. | ||
:param str datatable_code: The datatable code to download, such as MER/F1 | ||
:param str filename: The filename for the download. \ | ||
If not specified, will download to the current working directory | ||
:param str api_key: Most databases require api_key for bulk download | ||
""" | ||
|
||
# discourage users from using authtoken | ||
if 'authtoken' in kwargs: | ||
raise InvalidRequestError(Message.ERROR_AUTHTOKEN_NOT_SUPPORTED) | ||
|
||
ApiKeyUtil.init_api_key_from_args(kwargs) | ||
|
||
filename = kwargs.pop('filename', '.') | ||
return Datatable(datatable_code).download_file(filename, params=kwargs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,28 @@ | ||
try: | ||
from urllib.request import urlopen | ||
from urllib.parse import urlencode | ||
except ImportError: | ||
from urllib import urlopen | ||
from urllib import urlencode | ||
|
||
from time import sleep | ||
import os | ||
|
||
from quandl.connection import Connection | ||
from quandl.util import Util | ||
from quandl.errors.quandl_error import QuandlError | ||
from quandl.operations.get import GetOperation | ||
from quandl.operations.list import ListOperation | ||
from quandl.util import Util | ||
|
||
from .model_base import ModelBase | ||
from quandl.message import Message | ||
from .data import Data | ||
|
||
|
||
class Datatable(GetOperation, ListOperation, ModelBase): | ||
BULK_CHUNK_SIZE = 16 * 1024 | ||
WAIT_GENERATION_INTERVAL = 30 | ||
IS_FILE_READY = False | ||
|
||
@classmethod | ||
def get_path(cls): | ||
|
@@ -14,3 +31,64 @@ def get_path(cls): | |
def data(self, **options): | ||
updated_options = Util.convert_options(**options) | ||
return Data.page(self, **updated_options) | ||
|
||
def download_file(self, file_or_folder_path, **options): | ||
if not isinstance(file_or_folder_path, str): | ||
raise QuandlError(Message.ERROR_FOLDER_ISSUE) | ||
|
||
if 'params' not in options: | ||
options['params'] = {} | ||
|
||
while True: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
self._request_file_info(file_or_folder_path, **options['params']) | ||
if self.IS_FILE_READY: | ||
break | ||
|
||
def _request_file_info(self, file_or_folder_path, **options): | ||
url = self._download_request_path(**options) | ||
code_name = self.code | ||
|
||
r = Connection.request('get', url, **options) | ||
response_data = r.json() | ||
|
||
file_info = response_data['datatable_bulk_download']['file'] | ||
|
||
status = file_info['status'] | ||
|
||
if status == 'fresh': | ||
file_link = file_info['link'] | ||
self.IS_FILE_READY = True | ||
self._download_file_with_link(file_or_folder_path, file_link, code_name) | ||
else: | ||
print(Message.LONG_GENERATION_TIME) | ||
sleep(self.WAIT_GENERATION_INTERVAL) | ||
|
||
def _download_file_with_link(self, file_or_folder_path, file_link, code_name): | ||
file_path = file_or_folder_path | ||
if os.path.isdir(file_or_folder_path): | ||
file_path = os.path.join(file_or_folder_path, | ||
'{}.{}'.format(code_name.replace('/', '_'), 'zip')) | ||
|
||
res = urlopen(file_link) | ||
|
||
with open(file_path, 'wb') as fd: | ||
while True: | ||
chunk = res.read(self.BULK_CHUNK_SIZE) | ||
if not chunk: | ||
break | ||
fd.write(chunk) | ||
|
||
print(file_path) | ||
|
||
def _download_request_path(self, **options): | ||
url = self.default_path() | ||
url = Util.constructed_path(url, {'id': self.code}) | ||
url += '.json?qopts.export=true&' | ||
|
||
if 'params' not in options: | ||
options['params'] = {} | ||
|
||
if options['params']: | ||
url += urlencode(options['params']) | ||
|
||
return url |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,10 @@ | ||
try: | ||
from urllib.parse import urlparse | ||
from urllib.parse import parse_qs | ||
except ImportError: | ||
from urlparse import urlparse | ||
from cgi import parse_qs | ||
|
||
import re | ||
import unittest | ||
import httpretty | ||
|
@@ -6,6 +13,8 @@ | |
from quandl.model.datatable import Datatable | ||
from mock import patch, call | ||
from test.factories.datatable import DatatableFactory | ||
from quandl.api_config import ApiConfig | ||
from quandl.errors.quandl_error import (InternalServerError, QuandlError) | ||
|
||
|
||
class GetDatatableDatasetTest(unittest.TestCase): | ||
|
@@ -48,3 +57,52 @@ def test_dataset_column_names_match_expected(self): | |
metadata = Datatable('ZACKS/FC').data_fields() | ||
six.assertCountEqual(self, | ||
metadata, [u'datatable_code', u'id', u'name', u'vendor_code']) | ||
|
||
|
||
class ExportDataTableTest(unittest.TestCase): | ||
|
||
@classmethod | ||
def setUpClass(cls): | ||
httpretty.enable() | ||
datatable = {'datatable': DatatableFactory.build( | ||
vendor_code='AUSBS', datatable_code='D')} | ||
httpretty.register_uri(httpretty.GET, | ||
re.compile( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any reason for such formatting? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure, I was looking at the old tests... |
||
'https://www.quandl.com/api/v3/datatables/*'), | ||
body=json.dumps(datatable)) | ||
cls.datatable_instance = Datatable(datatable['datatable']) | ||
|
||
@classmethod | ||
def tearDownClass(cls): | ||
httpretty.disable() | ||
httpretty.reset() | ||
|
||
def setUp(self): | ||
datatable = {'datatable': DatatableFactory.build( | ||
vendor_code='AUSBS', datatable_code='D')} | ||
self.datatable = Datatable(datatable['datatable']['vendor_code'] + '/' + | ||
datatable['datatable']['datatable_code'], datatable['datatable']) | ||
ApiConfig.api_key = 'api_token' | ||
ApiConfig.api_version = '2015-04-09' | ||
|
||
def test_download_get_file_info(self): | ||
url = self.datatable._download_request_path() | ||
parsed_url = urlparse(url) | ||
self.assertEqual(parsed_url.path, 'datatables/AUSBS/D.json') | ||
self.assertDictEqual(parse_qs(parsed_url.query), { | ||
'qopts.export': ['true']}) | ||
|
||
def test_bulk_download_raises_exception_when_no_path(self): | ||
self.assertRaises( | ||
QuandlError, lambda: self.datatable.download_file(None)) | ||
|
||
def test_bulk_download_table_raises_exception_when_error_response(self): | ||
httpretty.register_uri(httpretty.GET, | ||
re.compile( | ||
'https://www.quandl.com/api/v3/datatables/*'), | ||
body=json.dumps( | ||
{'quandl_error': | ||
{'code': 'QEMx01', 'message': 'something went wrong'}}), | ||
status=500) | ||
self.assertRaises( | ||
InternalServerError, lambda: self.datatable.download_file('.')) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you still returning the filename? I think you changed the method to only print the filename.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated