Skip to content

Commit 3398e41

Browse files
dtemkinbashtage
authored andcommitted
Morningstar Retry Fix and Google API Patch (#515)
* Fixed Morningstar Retry Issue and Failed Morningstar Test * Fixed Morningstar Retry Issue and Failed Morningstar Test Implemented temporary, functioning, replacement url for failing Google API Fixed Google API tests * updated WhatsNew 0.7.0 * removed useragent to base request headers * Rolled back changes to google api tests. * updated WhatsNew 0.7.0 * Fixed flake8 Issues * Attempting to fix stooq tests * Attempting to fix stooq tests * Fixing flake8 issues * Fixed Stooq to include necessary information regarding country * Fixing flake8 issues
1 parent 2f68649 commit 3398e41

File tree

12 files changed

+75
-49
lines changed

12 files changed

+75
-49
lines changed

docs/source/whatsnew/v0.7.0.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,5 @@ Bug Fixes
3030
setting the environmental variable QUANDL_API_KEY (:issue:`485`).
3131
- Added back support for Yahoo! price data
3232
- Handle Morningstar index volume data properly (:issue:`486`).
33+
- Fixed Morningstar 'retry' incrementation (:issue:`513`)
34+
- Updated Google Daily Price API to functional url (:issue:`502`)

pandas_datareader/base.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def _sanitize_response(response):
113113
"""
114114
return response.content
115115

116-
def _get_response(self, url, params=None, headers=None):
116+
def _get_response(self, url, params=None):
117117
""" send raw HTTP request to get requests.Response from the specified url
118118
Parameters
119119
----------
@@ -128,8 +128,7 @@ def _get_response(self, url, params=None, headers=None):
128128
last_response_text = ''
129129
for i in range(self.retry_count + 1):
130130
response = self.session.get(url,
131-
params=params,
132-
headers=headers)
131+
params=params)
133132
if response.status_code == requests.codes.ok:
134133
return response
135134

@@ -170,6 +169,9 @@ def _output_error(self, out):
170169
def _read_lines(self, out):
171170
rs = read_csv(out, index_col=0, parse_dates=True,
172171
na_values=('-', 'null'))[::-1]
172+
# Needed to remove blank space character in header names
173+
rs.columns = list(map(lambda x: x.strip(), rs.columns.values.tolist()))
174+
173175
# Yahoo! Finance sometimes does this awesome thing where they
174176
# return 2 rows for the most recent business day
175177
if len(rs) > 2 and rs.index[-1] == rs.index[-2]: # pragma: no cover
@@ -181,6 +183,7 @@ def _read_lines(self, out):
181183
except AttributeError:
182184
# Python 3 string has no decode method.
183185
rs.index.name = rs.index.name.encode('ascii', 'ignore').decode()
186+
184187
return rs
185188

186189

pandas_datareader/google/daily.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ def __init__(self, symbols=None, start=None, end=None, retry_count=3,
4545
@property
4646
def url(self):
4747
"""API URL"""
48-
return 'https://finance.google.com/finance/historical'
48+
# Thanks user:vnmabus for pointing this out.
49+
return 'https://finance.google.co.uk/bctzjpnsun/historical'
4950

5051
def _get_params(self, symbol):
5152
params = {

pandas_datareader/mstar/daily.py

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@ def __init__(self, symbols, start=None, end=None, retry_count=3,
6262
self.currency = currency
6363
self.interval = interval
6464

65-
self._symbol_data_cache = []
66-
6765
def _url_params(self):
6866
if self.interval not in ['d', 'wk', 'mo', 'm', 'w']:
6967
raise ValueError("Invalid interval: valid values are 'd', 'wk' "
@@ -98,17 +96,18 @@ def _get_crumb(self, *args):
9896
"""Not required """
9997
pass
10098

101-
def _dl_mult_symbols(self, symbols):
99+
def _dl_mult_symbols(self, symbols, symbol_data=None):
102100
failed = []
103-
symbol_data = []
101+
if symbol_data is None:
102+
symbol_data = []
104103
for symbol in symbols:
105-
106104
params = self._url_params()
107105
params.update({"ticker": symbol})
108106

109107
try:
110108
resp = requests.get(self.url, params=params)
111-
except Exception:
109+
except (requests.HTTPError, requests.ConnectionError,
110+
requests.RequestException):
112111
if symbol not in failed:
113112
if self.retry_count == 0:
114113
warn("skipping symbol %s: number of retries "
@@ -122,25 +121,24 @@ def _dl_mult_symbols(self, symbols):
122121
jsondata = resp.json()
123122
if jsondata is None:
124123
failed.append(symbol)
125-
continue
126-
jsdata = self._restruct_json(symbol=symbol,
127-
jsondata=jsondata)
128-
symbol_data.extend(jsdata)
124+
pass
125+
else:
126+
jsdata = self._restruct_json(symbol=symbol,
127+
jsondata=jsondata)
128+
symbol_data.extend(jsdata)
129129
else:
130130
raise Exception("Request Error!: %s : %s" % (
131131
resp.status_code, resp.reason))
132132

133133
time.sleep(self.pause)
134134

135135
if len(failed) > 0 and self.retry_count > 0:
136-
# TODO: This appears to do nothing since
137-
# TODO: successful symbols are not added to
138-
self._dl_mult_symbols(symbols=failed)
139136
self.retry_count -= 1
137+
self._dl_mult_symbols(symbols=failed, symbol_data=symbol_data)
140138
else:
141139
self.retry_count = 0
142140

143-
if not symbol_data:
141+
if len(symbol_data) == 0 and self.retry_count == 0:
144142
raise ValueError('All symbols were invalid')
145143
elif self.retry_count == 0 and len(failed) > 0:
146144
warn("The following symbols were excluded do to http "
@@ -156,8 +154,7 @@ def _convert_index2date(indexvals):
156154
return [base + pd.to_timedelta(iv, unit='d') for iv in indexvals]
157155

158156
def _restruct_json(self, symbol, jsondata):
159-
if jsondata is None:
160-
return
157+
161158
divdata = jsondata["DividendData"]
162159

163160
pricedata = jsondata["PriceDataList"][0]["Datapoints"]
@@ -174,8 +171,7 @@ def _restruct_json(self, symbol, jsondata):
174171
d = dates[p]
175172
bardict = {
176173
"Symbol": symbol, "Date": d, "Close": bar[0], "High": bar[1],
177-
"Low": bar[2], "Open": bar[3]
178-
}
174+
"Low": bar[2], "Open": bar[3]}
179175
if len(divdata) == 0:
180176
pass
181177
else:
@@ -216,7 +212,7 @@ def read(self):
216212
is_str = False
217213
try:
218214
is_str = all(map(lambda v: isinstance(v, str), symbols))
219-
except Exception:
215+
except ValueError:
220216
pass
221217

222218
if not is_str:

pandas_datareader/stooq.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ class StooqDailyReader(_DailyBaseReader):
55

66
"""
77
Returns DataFrame/Panel of historical stock prices from symbols, over date
8-
range, start to end. To avoid being penalized by Google Finance servers,
9-
pauses between downloading 'chunks' of symbols can be specified.
8+
range, start to end.
109
1110
Parameters
1211
----------
@@ -23,6 +22,7 @@ class StooqDailyReader(_DailyBaseReader):
2322
session : Session, default None
2423
requests.sessions.Session instance to be used
2524
25+
2626
Notes
2727
-----
2828
See `Stooq <https://stooq.com>`__
@@ -33,7 +33,15 @@ def url(self):
3333
"""API URL"""
3434
return 'https://stooq.com/q/d/l/'
3535

36-
def _get_params(self, symbol):
36+
def _get_params(self, symbol, country="US"):
37+
symbol_parts = symbol.split(".")
38+
if len(symbol_parts) == 1:
39+
symbol = ".".join([symbol, country])
40+
else:
41+
if symbol_parts[1].lower() not in ['de', 'hk', 'hu', 'jp',
42+
'pl', 'uk', 'us']:
43+
symbol = ".".join([symbol, "US"])
44+
3745
params = {
3846
's': symbol,
3947
'i': "d"

pandas_datareader/tests/google/test_google.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,20 @@ def teardown_class(cls):
5555

5656
@skip_on_exception(RemoteDataError)
5757
def test_google(self):
58+
5859
# asserts that google is minimally working and that it throws
5960
# an exception when DataReader can't get a 200 response from
6061
# google
62+
6163
start = datetime(2010, 1, 1)
6264
end = datetime(2013, 1, 27)
63-
6465
for locale in self.locales:
6566
with tm.set_locale(locale):
66-
panel = web.DataReader("NYSE:F", 'google', start, end)
67-
assert panel.Close[-1] == 13.68
67+
panel = web.DataReader("NYSE:F", 'google', start, end)
68+
assert panel.Close[-1] == 13.68
6869

69-
with pytest.raises(Exception):
70-
web.DataReader('NON EXISTENT TICKER', 'google', start, end)
70+
with pytest.raises(Exception):
71+
web.DataReader('NON EXISTENT TICKER', 'google', start, end)
7172

7273
def assert_option_result(self, df):
7374
"""
@@ -99,6 +100,7 @@ def test_get_quote_stringlist(self):
99100

100101
@skip_on_exception(RemoteDataError)
101102
def test_get_goog_volume(self):
103+
102104
for locale in self.locales:
103105
with tm.set_locale(locale):
104106
df = web.get_data_google('GOOG').sort_index()
@@ -120,16 +122,14 @@ def test_get_multi1(self):
120122

121123
@skip_on_exception(RemoteDataError)
122124
def test_get_multi_invalid(self):
123-
with warnings.catch_warnings(record=True):
124-
sl = ['AAPL', 'AMZN', 'INVALID']
125-
pan = web.get_data_google(sl, '2012')
126-
assert 'INVALID' in pan.minor_axis
125+
sl = ['AAPL', 'AMZN', 'INVALID']
126+
pan = web.get_data_google(sl, '2012')
127+
assert 'INVALID' in pan.minor_axis
127128

128129
def test_get_multi_all_invalid(self):
129-
with warnings.catch_warnings(record=True):
130-
sl = ['INVALID', 'INVALID2', 'INVALID3']
131-
with pytest.raises(RemoteDataError):
132-
web.get_data_google(sl, '2012')
130+
sl = ['INVALID', 'INVALID2', 'INVALID3']
131+
with pytest.raises(RemoteDataError):
132+
web.get_data_google(sl, '2012')
133133

134134
@skip_on_exception(RemoteDataError)
135135
def test_get_multi2(self):
@@ -165,6 +165,7 @@ def test_dtypes(self):
165165
@skip_on_exception(RemoteDataError)
166166
def test_unicode_date(self):
167167
# see gh-8967
168+
168169
data = web.get_data_google(
169170
'NYSE:F',
170171
start='JAN-01-10',
@@ -173,6 +174,7 @@ def test_unicode_date(self):
173174

174175
@skip_on_exception(RemoteDataError)
175176
def test_google_reader_class(self):
177+
176178
r = GoogleDailyReader('GOOG')
177179
df = r.read()
178180
assert df.Volume.loc['JAN-02-2015'] == 1446662

pandas_datareader/tests/mstar/test_daily.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from pandas_datareader._testing import skip_on_exception
1010
from pandas_datareader._utils import RemoteDataError
1111
from pandas_datareader.data import MorningstarDailyReader
12+
from pandas_datareader._utils import SymbolWarning
1213

1314

1415
class TestMorningstarDaily(object):
@@ -22,8 +23,10 @@ def test_invalid_date(self):
2223
end="1999-03-03")
2324

2425
def test_invalid_partial_multi_symbols(self):
25-
df = web.DataReader(['MSFT', "21##", ""], "morningstar", retry_count=0)
26-
assert (len(df.index.levels[0]) == 1)
26+
with pytest.warns(SymbolWarning):
27+
df = web.DataReader(['MSFT', "21##", ""],
28+
"morningstar", retry_count=0)
29+
assert (len(df.index.levels[0]) == 1)
2730

2831
def test_invalid_multi_symbols(self):
2932
with pytest.raises(ValueError):

pandas_datareader/tests/test_data.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from pandas import DataFrame
44
from pandas_datareader.data import DataReader
5+
from pandas_datareader.exceptions import UnstableAPIWarning
56
from pandas_datareader._utils import RemoteDataError
67
from pandas_datareader._testing import skip_on_exception
78

@@ -10,8 +11,9 @@ class TestDataReader(object):
1011

1112
@skip_on_exception(RemoteDataError)
1213
def test_read_google(self):
13-
gs = DataReader("GS", "google")
14-
assert isinstance(gs, DataFrame)
14+
with pytest.warns(UnstableAPIWarning):
15+
gs = DataReader("GS", "google")
16+
assert isinstance(gs, DataFrame)
1517

1618
def test_read_iex(self):
1719
gs = DataReader("GS", "iex-last")
@@ -21,6 +23,10 @@ def test_read_fred(self):
2123
vix = DataReader("VIXCLS", "fred")
2224
assert isinstance(vix, DataFrame)
2325

26+
def test_read_mstar(self):
27+
gs = DataReader("GS", data_source="morningstar")
28+
assert isinstance(gs, DataFrame)
29+
2430
def test_not_implemented(self):
2531
with pytest.raises(NotImplementedError):
2632
DataReader("NA", "NA")

pandas_datareader/tests/test_fred.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313

1414
class TestFred(object):
15+
1516
def test_fred(self):
1617

1718
# Raises an exception when DataReader can't
@@ -70,6 +71,7 @@ def test_fred_multi(self): # pragma: no cover
7071
end = datetime(2013, 1, 27)
7172

7273
received = web.DataReader(names, "fred", start, end).head(1)
74+
7375
expected = DataFrame([[217.488, 99.68746, 220.633]], columns=names,
7476
index=[pd.tslib.Timestamp('2010-01-01 00:00:00')])
7577
expected.index.rename('DATE', inplace=True)

pandas_datareader/tests/test_iex.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from pandas_datareader.data import (DataReader, get_summary_iex, get_last_iex,
77
get_dailysummary_iex, get_iex_symbols,
88
get_iex_book)
9+
from pandas_datareader.exceptions import UnstableAPIWarning
910

1011

1112
class TestIEX(object):
@@ -29,9 +30,10 @@ def test_false_ticker(self):
2930
@pytest.mark.xfail(reason='IEX daily history API is returning 500 as of '
3031
'Jan 2018')
3132
def test_daily(self):
32-
df = get_dailysummary_iex(start=datetime(2017, 5, 5),
33-
end=datetime(2017, 5, 6))
34-
assert df['routedVolume'].iloc[0] == 39974788
33+
with pytest.warns(UnstableAPIWarning):
34+
df = get_dailysummary_iex(start=datetime(2017, 5, 5),
35+
end=datetime(2017, 5, 6))
36+
assert df['routedVolume'].iloc[0] == 39974788
3537

3638
def test_symbols(self):
3739
df = get_iex_symbols()

pandas_datareader/tests/test_iex_daily.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ def test_iex_bad_symbol(self):
2626

2727
def test_iex_bad_symbol_list(self):
2828
with pytest.raises(Exception):
29-
web.DataReader(["AAPL", "BADTICKER"], "iex", self.start, self.end)
29+
web.DataReader(["AAPL", "BADTICKER"], "iex",
30+
self.start, self.end)
3031

3132
def test_daily_invalid_date(self):
3233
start = datetime(2010, 1, 5)

pandas_datareader/tests/test_stooq.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33

44

55
def test_stooq_dji():
6-
f = web.DataReader('^DJI', 'stooq')
6+
f = web.DataReader('GS', 'stooq')
77
assert f.shape[0] > 0
88

99

1010
def test_get_data_stooq_dji():
11-
f = get_data_stooq('^DAX')
11+
f = get_data_stooq('AMZN')
1212
assert f.shape[0] > 0

0 commit comments

Comments
 (0)