99
1010"""
1111import pandas as pd
12- import numpy as np
1312
1413from .context import yfinance as yf
1514from .context import session_gbl
15+ from yfinance .exceptions import YFNotImplementedError
16+
1617
1718import unittest
1819import requests_cache
19-
20+ from typing import Union , Any
21+
22+ ticker_attributes = (
23+ ("major_holders" , pd .DataFrame ),
24+ ("institutional_holders" , pd .DataFrame ),
25+ ("mutualfund_holders" , pd .DataFrame ),
26+ ("splits" , pd .Series ),
27+ ("actions" , pd .DataFrame ),
28+ ("shares" , pd .DataFrame ),
29+ ("info" , dict ),
30+ ("calendar" , pd .DataFrame ),
31+ ("recommendations" , Union [pd .DataFrame , dict ]),
32+ ("earnings" , pd .DataFrame ),
33+ ("quarterly_earnings" , pd .DataFrame ),
34+ ("recommendations_summary" , Union [pd .DataFrame , dict ]),
35+ ("quarterly_cashflow" , pd .DataFrame ),
36+ ("cashflow" , pd .DataFrame ),
37+ ("quarterly_balance_sheet" , pd .DataFrame ),
38+ ("balance_sheet" , pd .DataFrame ),
39+ ("quarterly_income_stmt" , pd .DataFrame ),
40+ ("income_stmt" , pd .DataFrame ),
41+ ("analyst_price_target" , pd .DataFrame ),
42+ ("revenue_forecasts" , pd .DataFrame ),
43+ ("sustainability" , pd .DataFrame ),
44+ ("options" , tuple ),
45+ ("news" , Any ),
46+ ("earnings_trend" , pd .DataFrame ),
47+ ("earnings_dates" , pd .DataFrame ),
48+ ("earnings_forecasts" , pd .DataFrame ),
49+ )
50+
51+ def assert_attribute_type (testClass : unittest .TestCase , instance , attribute_name , expected_type ):
52+ try :
53+ attribute = getattr (instance , attribute_name )
54+ if attribute is not None and expected_type is not Any :
55+ testClass .assertEqual (type (attribute ), expected_type )
56+ except Exception :
57+ testClass .assertRaises (
58+ YFNotImplementedError , lambda : getattr (instance , attribute_name )
59+ )
2060
2161class TestTicker (unittest .TestCase ):
2262 session = None
@@ -61,39 +101,9 @@ def test_badTicker(self):
61101 for k in dat .fast_info :
62102 dat .fast_info [k ]
63103
64- dat .isin
65- dat .major_holders
66- dat .institutional_holders
67- dat .mutualfund_holders
68- dat .dividends
69- dat .splits
70- dat .actions
71- dat .get_shares_full ()
72- dat .options
73- dat .news
74- dat .earnings_dates
75-
76- dat .income_stmt
77- dat .quarterly_income_stmt
78- dat .balance_sheet
79- dat .quarterly_balance_sheet
80- dat .cashflow
81- dat .quarterly_cashflow
82-
83- # These haven't been ported Yahoo API
84- # dat.shares
85- # dat.info
86- # dat.calendar
87- # dat.recommendations
88- # dat.earnings
89- # dat.quarterly_earnings
90- # dat.recommendations_summary
91- # dat.analyst_price_target
92- # dat.revenue_forecasts
93- # dat.sustainability
94- # dat.earnings_trend
95- # dat.earnings_forecasts
96-
104+ for attribute_name , attribute_type in ticker_attributes :
105+ assert_attribute_type (self , dat , attribute_name , attribute_type )
106+
97107 def test_goodTicker (self ):
98108 # that yfinance works when full api is called on same instance of ticker
99109
@@ -113,61 +123,20 @@ def test_goodTicker(self):
113123 for k in dat .fast_info :
114124 dat .fast_info [k ]
115125
116- dat .isin
117- dat .major_holders
118- dat .institutional_holders
119- dat .mutualfund_holders
120- dat .dividends
121- dat .splits
122- dat .actions
123- dat .get_shares_full ()
124- dat .options
125- dat .news
126- dat .earnings_dates
127-
128- dat .income_stmt
129- dat .quarterly_income_stmt
130- dat .balance_sheet
131- dat .quarterly_balance_sheet
132- dat .cashflow
133- dat .quarterly_cashflow
134-
135- # These require decryption which is broken:
136- # dat.shares
137- # dat.info
138- # dat.calendar
139- # dat.recommendations
140- # dat.earnings
141- # dat.quarterly_earnings
142- # dat.recommendations_summary
143- # dat.analyst_price_target
144- # dat.revenue_forecasts
145- # dat.sustainability
146- # dat.earnings_trend
147- # dat.earnings_forecasts
148-
126+ for attribute_name , attribute_type in ticker_attributes :
127+ assert_attribute_type (self , dat , attribute_name , attribute_type )
128+
129+ #TODO:: Refactor with `assert_attribute` once proxy is accepted as a parameter of `Ticker`
149130 def test_goodTicker_withProxy (self ):
150131 # that yfinance works when full api is called on same instance of ticker
151132
152133 tkr = "IBM"
153134 dat = yf .Ticker (tkr , session = self .session )
154135
155- dat ._fetch_ticker_tz (proxy = self .proxy , timeout = 5 , debug_mode = False , raise_errors = False )
156- dat ._get_ticker_tz (proxy = self .proxy , timeout = 5 , debug_mode = False , raise_errors = False )
136+ dat ._fetch_ticker_tz (proxy = self .proxy , timeout = 5 )
137+ dat ._get_ticker_tz (proxy = self .proxy , timeout = 5 )
157138 dat .history (period = "1wk" , proxy = self .proxy )
158139
159- v = dat .stats (proxy = self .proxy )
160- self .assertIsNotNone (v )
161- self .assertTrue (len (v ) > 0 )
162-
163- v = dat .get_recommendations (proxy = self .proxy )
164- self .assertIsNotNone (v )
165- self .assertFalse (v .empty )
166-
167- v = dat .get_calendar (proxy = self .proxy )
168- self .assertIsNotNone (v )
169- self .assertFalse (v .empty )
170-
171140 v = dat .get_major_holders (proxy = self .proxy )
172141 self .assertIsNotNone (v )
173142 self .assertFalse (v .empty )
@@ -184,38 +153,6 @@ def test_goodTicker_withProxy(self):
184153 self .assertIsNotNone (v )
185154 self .assertTrue (len (v ) > 0 )
186155
187- v = dat .get_sustainability (proxy = self .proxy )
188- self .assertIsNotNone (v )
189- self .assertFalse (v .empty )
190-
191- v = dat .get_recommendations_summary (proxy = self .proxy )
192- self .assertIsNotNone (v )
193- self .assertFalse (v .empty )
194-
195- v = dat .get_analyst_price_target (proxy = self .proxy )
196- self .assertIsNotNone (v )
197- self .assertFalse (v .empty )
198-
199- v = dat .get_rev_forecast (proxy = self .proxy )
200- self .assertIsNotNone (v )
201- self .assertFalse (v .empty )
202-
203- v = dat .get_earnings_forecast (proxy = self .proxy )
204- self .assertIsNotNone (v )
205- self .assertFalse (v .empty )
206-
207- v = dat .get_trend_details (proxy = self .proxy )
208- self .assertIsNotNone (v )
209- self .assertFalse (v .empty )
210-
211- v = dat .get_earnings_trend (proxy = self .proxy )
212- self .assertIsNotNone (v )
213- self .assertFalse (v .empty )
214-
215- v = dat .get_earnings (proxy = self .proxy )
216- self .assertIsNotNone (v )
217- self .assertFalse (v .empty )
218-
219156 v = dat .get_income_stmt (proxy = self .proxy )
220157 self .assertIsNotNone (v )
221158 self .assertFalse (v .empty )
@@ -244,10 +181,6 @@ def test_goodTicker_withProxy(self):
244181 self .assertIsNotNone (v )
245182 self .assertFalse (v .empty )
246183
247- v = dat .get_shares (proxy = self .proxy )
248- self .assertIsNotNone (v )
249- self .assertFalse (v .empty )
250-
251184 v = dat .get_shares_full (proxy = self .proxy )
252185 self .assertIsNotNone (v )
253186 self .assertFalse (v .empty )
@@ -264,11 +197,60 @@ def test_goodTicker_withProxy(self):
264197 self .assertIsNotNone (v )
265198 self .assertFalse (v .empty )
266199
267- # TODO: enable after merge
268- # dat.get_history_metadata(proxy=self.proxy)
200+ dat .get_history_metadata (proxy = self .proxy )
201+ self .assertIsNotNone (v )
202+ self .assertTrue (len (v ) > 0 )
203+
204+ # Below will fail because not ported to Yahoo API
205+
206+ # v = dat.stats(proxy=self.proxy)
269207 # self.assertIsNotNone(v)
270208 # self.assertTrue(len(v) > 0)
271209
210+ # v = dat.get_recommendations(proxy=self.proxy)
211+ # self.assertIsNotNone(v)
212+ # self.assertFalse(v.empty)
213+
214+ # v = dat.get_calendar(proxy=self.proxy)
215+ # self.assertIsNotNone(v)
216+ # self.assertFalse(v.empty)
217+
218+ # v = dat.get_sustainability(proxy=self.proxy)
219+ # self.assertIsNotNone(v)
220+ # self.assertFalse(v.empty)
221+
222+ # v = dat.get_recommendations_summary(proxy=self.proxy)
223+ # self.assertIsNotNone(v)
224+ # self.assertFalse(v.empty)
225+
226+ # v = dat.get_analyst_price_target(proxy=self.proxy)
227+ # self.assertIsNotNone(v)
228+ # self.assertFalse(v.empty)
229+
230+ # v = dat.get_rev_forecast(proxy=self.proxy)
231+ # self.assertIsNotNone(v)
232+ # self.assertFalse(v.empty)
233+
234+ # v = dat.get_earnings_forecast(proxy=self.proxy)
235+ # self.assertIsNotNone(v)
236+ # self.assertFalse(v.empty)
237+
238+ # v = dat.get_trend_details(proxy=self.proxy)
239+ # self.assertIsNotNone(v)
240+ # self.assertFalse(v.empty)
241+
242+ # v = dat.get_earnings_trend(proxy=self.proxy)
243+ # self.assertIsNotNone(v)
244+ # self.assertFalse(v.empty)
245+
246+ # v = dat.get_earnings(proxy=self.proxy)
247+ # self.assertIsNotNone(v)
248+ # self.assertFalse(v.empty)
249+
250+ # v = dat.get_shares(proxy=self.proxy)
251+ # self.assertIsNotNone(v)
252+ # self.assertFalse(v.empty)
253+
272254
273255class TestTickerHistory (unittest .TestCase ):
274256 session = None
@@ -312,16 +294,21 @@ def test_no_expensive_calls_introduced(self):
312294 As doing other type of scraping calls than "query2.finance.yahoo.com/v8/finance/chart" to yahoo website
313295 will quickly trigger spam-block when doing bulk download of history data.
314296 """
315- session = requests_cache .CachedSession (backend = 'memory' )
316- ticker = yf .Ticker ("GOOGL" , session = session )
317- ticker .history ("1y" )
318- actual_urls_called = tuple ([r .url for r in session .cache .filter ()])
319- session .close ()
297+ symbol = "GOOGL"
298+ range = "1y"
299+ with requests_cache .CachedSession (backend = "memory" ) as session :
300+ ticker = yf .Ticker (symbol , session = session )
301+ ticker .history (range )
302+ actual_urls_called = tuple ([r .url for r in session .cache .filter ()])
303+
320304 expected_urls = (
321- 'https://query2.finance.yahoo.com/v8/finance/chart/GOOGL?events=div,splits,capitalGains&includePrePost=False&interval=1d&range=1y' ,
305+ f"https://query2.finance.yahoo.com/v8/finance/chart/{ symbol } ?events=div%2Csplits%2CcapitalGains&includePrePost=False&interval=1d&range={ range } " ,
306+ )
307+ self .assertEqual (
308+ expected_urls ,
309+ actual_urls_called ,
310+ "Different than expected url used to fetch history."
322311 )
323- self .assertEqual (expected_urls , actual_urls_called , "Different than expected url used to fetch history." )
324-
325312 def test_dividends (self ):
326313 data = self .ticker .dividends
327314 self .assertIsInstance (data , pd .Series , "data has wrong type" )
0 commit comments