Skip to content

Commit 2c4cc4c

Browse files
committed
BUG: Add API KEY for Quandl
Add support for the Quandl API key closes #484
1 parent 05220cd commit 2c4cc4c

File tree

4 files changed

+117
-49
lines changed

4 files changed

+117
-49
lines changed

docs/source/whatsnew/v0.7.0.txt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
.. _whatsnew_070:
2+
3+
v0.7.0 (Xxxxxxx YY, 20ZZ)
4+
---------------------------
5+
6+
7+
Highlights include:
8+
9+
10+
.. contents:: What's new in v0.7.0
11+
:local:
12+
:backlinks: none
13+
14+
.. _whatsnew_070.enhancements:
15+
16+
Enhancements
17+
~~~~~~~~~~~~
18+
19+
.. _whatsnew_070.api_breaking:
20+
21+
Backwards incompatible API changes
22+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
23+
24+
.. _whatsnew_070.bug_fixes:
25+
26+
Bug Fixes
27+
~~~~~~~~~
28+
29+
- Added support for passing the API KEY to QuandlReader either directly or by
30+
setting the environmental variable QUANDL_API_KEY (:issue:`485`).

pandas_datareader/compat/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
if PANDAS_0190:
1919
from pandas.api.types import is_number
20+
from pandas.util.testing import assert_frame_equal
2021
else:
2122
from pandas.core.common import is_number
23+
from pandas.testing import assert_frame_equal
2224

2325
if PANDAS_0200:
2426
from pandas.util.testing import assert_raises_regex

pandas_datareader/quandl.py

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
import os
12
import re
23

34
from pandas_datareader.base import _DailyBaseReader
45

56

67
class QuandlReader(_DailyBaseReader):
7-
88
"""
99
Returns DataFrame of historical stock prices from symbol, over date
1010
range, start to end.
@@ -37,10 +37,25 @@ class QuandlReader(_DailyBaseReader):
3737
Number of symbols to download consecutively before intiating pause.
3838
session : Session, default None
3939
requests.sessions.Session instance to be used
40+
api_key : str, optional
41+
Quandl API key . If not provided the environmental variable
42+
QUANDL_API_KEY is read. The API key is *required*.
4043
"""
4144

4245
_BASE_URL = "https://www.quandl.com/api/v3/datasets/"
4346

47+
def __init__(self, symbols, start=None, end=None, retry_count=3, pause=0.1,
48+
session=None, chunksize=25, api_key=None):
49+
super(QuandlReader, self).__init__(symbols, start, end, retry_count,
50+
pause, session, chunksize)
51+
if api_key is None:
52+
api_key = os.getenv('QUANDL_API_KEY')
53+
if not api_key or not isinstance(api_key, str):
54+
raise ValueError('The Quandl API key must be provided either '
55+
'through the api_key variable or through the '
56+
'environmental variable QUANDL_API_KEY.')
57+
self.api_key = api_key
58+
4459
@property
4560
def url(self):
4661
"""API URL"""
@@ -65,37 +80,39 @@ def url(self):
6580
'start_date': self.start.strftime('%Y-%m-%d'),
6681
'end_date': self.end.strftime('%Y-%m-%d'),
6782
'order': "asc",
83+
'api_key': self.api_key
6884
}
6985
paramstring = '&'.join(['%s=%s' % (k, v) for k, v in params.items()])
70-
return '%s%s/%s.csv?%s' % (self._BASE_URL, datasetname,
71-
symbol, paramstring)
86+
url = '{url}{dataset}/{symbol}.csv?{params}'
87+
return url.format(url=self._BASE_URL, dataset=datasetname,
88+
symbol=symbol, params=paramstring)
7289

7390
def _fullmatch(self, regex, string, flags=0):
7491
"""Emulate python-3.4 re.fullmatch()."""
7592
return re.match("(?:" + regex + r")\Z", string, flags=flags)
7693

7794
_COUNTRYCODE_TO_DATASET = dict(
78-
# https://www.quandl.com/data/EURONEXT-Euronext-Stock-Exchange
79-
BE='EURONEXT',
80-
# https://www.quandl.com/data/HKEX-Hong-Kong-Exchange
81-
CN='HKEX',
82-
# https://www.quandl.com/data/SSE-Boerse-Stuttgart
83-
DE='SSE',
84-
FR='EURONEXT',
85-
# https://www.quandl.com/data/NSE-National-Stock-Exchange-of-India
86-
IN='NSE',
87-
# https://www.quandl.com/data/TSE-Tokyo-Stock-Exchange
88-
JP='TSE',
89-
NL='EURONEXT',
90-
PT='EURONEXT',
91-
# https://www.quandl.com/data/LSE-London-Stock-Exchange
92-
UK='LSE',
93-
# https://www.quandl.com/data/WIKI-Wiki-EOD-Stock-Prices
94-
US='WIKI',
95-
)
95+
# https://www.quandl.com/data/EURONEXT-Euronext-Stock-Exchange
96+
BE='EURONEXT',
97+
# https://www.quandl.com/data/HKEX-Hong-Kong-Exchange
98+
CN='HKEX',
99+
# https://www.quandl.com/data/SSE-Boerse-Stuttgart
100+
DE='SSE',
101+
FR='EURONEXT',
102+
# https://www.quandl.com/data/NSE-National-Stock-Exchange-of-India
103+
IN='NSE',
104+
# https://www.quandl.com/data/TSE-Tokyo-Stock-Exchange
105+
JP='TSE',
106+
NL='EURONEXT',
107+
PT='EURONEXT',
108+
# https://www.quandl.com/data/LSE-London-Stock-Exchange
109+
UK='LSE',
110+
# https://www.quandl.com/data/WIKI-Wiki-EOD-Stock-Prices
111+
US='WIKI',
112+
)
96113

97114
def _db_from_countrycode(self, code):
98-
assert code in self._COUNTRYCODE_TO_DATASET,\
115+
assert code in self._COUNTRYCODE_TO_DATASET, \
99116
"No Quandl dataset known for country code '%s'" % code
100117
return self._COUNTRYCODE_TO_DATASET[code]
101118

@@ -106,12 +123,12 @@ def read(self):
106123
"""Read data"""
107124
df = super(QuandlReader, self).read()
108125
df.rename(columns=lambda n: n.replace(' ', '')
109-
.replace('.', '')
110-
.replace('/', '')
111-
.replace('%', '')
112-
.replace('(', '')
113-
.replace(')', '')
114-
.replace("'", '')
115-
.replace('-', ''),
126+
.replace('.', '')
127+
.replace('/', '')
128+
.replace('%', '')
129+
.replace('(', '')
130+
.replace(')', '')
131+
.replace("'", '')
132+
.replace('-', ''),
116133
inplace=True)
117134
return df

pandas_datareader/tests/test_quandl.py

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1+
import os
2+
3+
import pytest
4+
15
import pandas_datareader.data as web
2-
from pandas_datareader._utils import RemoteDataError
3-
from pandas_datareader._testing import skip_on_exception
6+
from pandas_datareader.compat import assert_frame_equal
7+
8+
TEST_API_KEY = os.getenv('QUANDL_API_KEY')
9+
# Ensure blank TEST_API_KEY not used in pull request
10+
TEST_API_KEY = None if not TEST_API_KEY else TEST_API_KEY
411

512

613
class TestQuandl(object):
@@ -17,76 +24,88 @@ def check_headers(self, df, expected_cols):
1724
act_cols = frozenset(df.columns.tolist())
1825
assert expected_cols == act_cols, "unexpected cols: " + str(act_cols)
1926

20-
@skip_on_exception(RemoteDataError)
27+
@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
2128
def test_db_wiki_us(self):
22-
df = web.DataReader('F', 'quandl', self.start10, self.end10)
29+
df = web.DataReader('F', 'quandl', self.start10, self.end10,
30+
access_key=TEST_API_KEY)
2331
self.check_headers(df, ['Open', 'High', 'Low', 'Close', 'Volume',
2432
'ExDividend', 'SplitRatio', 'AdjOpen',
2533
'AdjHigh', 'AdjLow', 'AdjClose', 'AdjVolume'])
2634
assert df.Close.at[self.day10] == 7.70
2735

28-
@skip_on_exception(RemoteDataError)
36+
@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
2937
def test_db_fse_frankfurt(self):
3038
# ALV_X: Allianz SE
31-
df = web.DataReader('FSE/ALV_X', 'quandl', self.start10, self.end10)
39+
df = web.DataReader('FSE/ALV_X', 'quandl', self.start10, self.end10,
40+
access_key=TEST_API_KEY)
3241
self.check_headers(df, ['Open', 'High', 'Low', 'Close', 'Change',
3342
'TradedVolume', 'Turnover',
3443
'LastPriceoftheDay', 'DailyTradedUnits',
3544
'DailyTurnover'])
3645
assert df.Close.at[self.day10] == 159.45
3746

38-
@skip_on_exception(RemoteDataError)
47+
@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
3948
def test_db_sse_de_stuttgart(self):
4049
# ALV: Allianz SE
41-
df = web.DataReader('SSE/ALV', 'quandl', self.start2, self.end2)
50+
df = web.DataReader('SSE/ALV', 'quandl', self.start2, self.end2,
51+
access_key=TEST_API_KEY)
4252
self.check_headers(df, [
43-
"High", "Low", "Last", "PreviousDayPrice", "Volume"])
53+
"High", "Low", "Last", "PreviousDayPrice", "Volume"])
4454
# as of 2017-06-11: PreviousDayPrice can be outside Low/High range;
4555
# Volume can be NaN
4656
assert df.Last.at[self.day2] == 136.47
4757
df2 = web.DataReader('ALV.DE', 'quandl', self.start2, self.end2)
4858
assert (df.Last == df2.Last).all()
4959

50-
@skip_on_exception(RemoteDataError)
60+
@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
5161
def test_db_euronext_be_fr_nl_pt(self):
5262
# FP: Total SA
5363
# as of 2017-06-11, some datasets end a few months after their start,
5464
# e.g. ALVD, BASD
55-
df = web.DataReader('EURONEXT/FP', 'quandl', self.start2, self.end2)
65+
df = web.DataReader('EURONEXT/FP', 'quandl', self.start2, self.end2,
66+
access_key=TEST_API_KEY)
5667
self.check_headers(df, [
57-
"Open", "High", "Low", "Last", "Turnover", "Volume"])
68+
"Open", "High", "Low", "Last", "Turnover", "Volume"])
5869
assert df.Last.at[self.day2] == 42.525
5970
df2 = web.DataReader('FP.FR', 'quandl', self.start2, self.end2)
6071
assert (df.Last == df2.Last).all()
6172

62-
@skip_on_exception(RemoteDataError)
73+
@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
6374
def test_db_lse_uk(self):
6475
# RBS: Royal Bank of Scotland
65-
df = web.DataReader('LSE/RBS', 'quandl', self.start10, self.end10)
76+
df = web.DataReader('LSE/RBS', 'quandl', self.start10, self.end10,
77+
access_key=TEST_API_KEY)
6678
self.check_headers(df, ["High", "Low", "LastClose", "Price",
6779
"Volume", "Change", "Var"])
6880
# as of 2017-06-11, Price == LastClose, all others are NaN
6981
assert df.Price.at[self.day10] == 5950.983
7082

71-
@skip_on_exception(RemoteDataError)
83+
@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
7284
def test_db_nse_in(self):
7385
# TCS: Tata Consutancy Services
74-
df = web.DataReader('NSE/TCS', 'quandl', self.start10, self.end10)
86+
df = web.DataReader('NSE/TCS', 'quandl', self.start10, self.end10,
87+
access_key=TEST_API_KEY)
7588
self.check_headers(df, ['Open', 'High', 'Low', 'Last', 'Close',
7689
'TotalTradeQuantity', 'TurnoverLacs'])
7790
assert df.Close.at[self.day10] == 1259.05
7891

79-
@skip_on_exception(RemoteDataError)
92+
@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
8093
def test_db_tse_jp(self):
8194
# TSE/6758: Sony Corp.
82-
df = web.DataReader('TSE/6758', 'quandl', self.start10, self.end10)
95+
df = web.DataReader('TSE/6758', 'quandl', self.start10, self.end10,
96+
access_key=TEST_API_KEY)
8397
self.check_headers(df, ['Open', 'High', 'Low', 'Close', 'Volume'])
8498
assert df.Close.at[self.day10] == 5190.0
8599

86-
@skip_on_exception(RemoteDataError)
100+
df2 = web.get_data_quandl('TSE/6758', self.start10, self.end10,
101+
api_key=TEST_API_KEY)
102+
assert_frame_equal(df, df2)
103+
104+
@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
87105
def test_db_hkex_cn(self):
88106
# HKEX/00941: China Mobile
89-
df = web.DataReader('HKEX/00941', 'quandl', self.start2, self.end2)
107+
df = web.DataReader('HKEX/00941', 'quandl', self.start2, self.end2,
108+
access_key=TEST_API_KEY)
90109
self.check_headers(df,
91110
['NominalPrice', 'NetChange', 'Change', 'Bid',
92111
'Ask', 'PEx', 'High', 'Low', 'PreviousClose',

0 commit comments

Comments
 (0)