Repository: ranaroussi/yfinance

Issue:
0.2.19b3: download(): KeyError when storing Tracebacks
- Simple code that reproduces your problem, that we can copy-paste-run:

```
ticker_list_r3000=pd.read_csv("RUSSELL3000.csv", sep='\t', dtype=str ,header=None)
ticker_list_r3000=ticker_list_r3000.iloc[:,0].tolist()
ticker_list_r3000.remove("MSGE")
ticker_list_r3000.remove("-")
stockdata = yf.download(ticker_list_r3000,'2021-1-1', group_by="ticker",auto_adjust=True, threads=True)
```

Please use the file attached to this message.

- Exception message with full traceback, or proof `yfinance` returning bad data:
  The pre-release version 0.2.19b3 generates an error bug (UNDESIRED BEHAVIOR!!!!):
         https://github.com/ranaroussi/yfinance/issues/1526#issuecomment-1559588523
  The release version  0.2.18 does not generate an error bug (DESIRED BEHAVIOR!!!!):
         https://github.com/ranaroussi/yfinance/issues/1526#issuecomment-1559707072

It is not desired that an error bug is generated if some tickers can not be downloaded when a large list of tickers is passed on to yf.download. When the user gives a long list of tickers to yf.download, chances are that some tickers can not be downloaded. If this is the case, yf-download should simply continue with the next symbol without generating a bug. It should simply continue to behave like up in version 0.2.18. It should not generate a bug like in 0.2.19b3.

- `yfinance` version and Python version:
   Python version: 3.11.2
   Yfinance: 0.2.19b3 (=pre-release version) and 0.2.18 (=release version)



- Operating system type: Windows 10


[Russell3000.csv](https://github.com/ranaroussi/yfinance/files/11546474/Russell3000.csv)



Discussion:

Resolving patch:
diff --git a/yfinance/multi.py b/yfinance/multi.py
--- a/yfinance/multi.py
+++ b/yfinance/multi.py
@@ -150,7 +150,6 @@ def download(tickers, start=None, end=None, actions=False, threads=True, ignore_
                                  back_adjust=back_adjust, repair=repair, keepna=keepna,
                                  proxy=proxy,
                                  rounding=rounding, timeout=timeout)
-            shared._DFS[ticker.upper()] = data
             if progress:
                 shared._PROGRESS_BAR.animate()
 
@@ -158,10 +157,12 @@ def download(tickers, start=None, end=None, actions=False, threads=True, ignore_
         shared._PROGRESS_BAR.completed()
 
     if shared._ERRORS:
+        # Send errors to logging module
         logger = utils.get_yf_logger()
         logger.error('\n%.f Failed download%s:' % (
             len(shared._ERRORS), 's' if len(shared._ERRORS) > 1 else ''))
-        # Print each distinct error once, with list of symbols affected
+
+        # Log each distinct error once, with list of symbols affected
         errors = {}
         for ticker in shared._ERRORS:
             err = shared._ERRORS[ticker]
@@ -172,9 +173,9 @@ def download(tickers, start=None, end=None, actions=False, threads=True, ignore_
         for err in errors.keys():
             logger.error(f'{errors[err]}: ' + err)
 
-        # Print each distinct traceback once, with list of symbols affected
+        # Log each distinct traceback once, with list of symbols affected
         tbs = {}
-        for ticker in shared._ERRORS:
+        for ticker in shared._TRACEBACKS:
             tb = shared._TRACEBACKS[ticker]
             if not tb in tbs:
                 tbs[tb] = [ticker]
@@ -239,17 +240,9 @@ def _download_one_threaded(ticker, start=None, end=None,
                            actions=False, progress=True, period="max",
                            interval="1d", prepost=False, proxy=None,
                            keepna=False, rounding=False, timeout=10):
-    try:
-        data = _download_one(ticker, start, end, auto_adjust, back_adjust, repair,
-                             actions, period, interval, prepost, proxy, rounding,
-                             keepna, timeout)
-    except Exception as e:
-        # glob try/except needed as current thead implementation breaks if exception is raised.
-        shared._TRACEBACKS[ticker] = traceback.format_exc()
-        shared._DFS[ticker] = utils.empty_df()
-        shared._ERRORS[ticker] = repr(e)
-    else:
-        shared._DFS[ticker.upper()] = data
+    data = _download_one(ticker, start, end, auto_adjust, back_adjust, repair,
+                         actions, period, interval, prepost, proxy, rounding,
+                         keepna, timeout)
     if progress:
         shared._PROGRESS_BAR.animate()
 
@@ -259,11 +252,22 @@ def _download_one(ticker, start=None, end=None,
                   actions=False, period="max", interval="1d",
                   prepost=False, proxy=None, rounding=False,
                   keepna=False, timeout=10):
-    return Ticker(ticker).history(
-        period=period, interval=interval,
-        start=start, end=end, prepost=prepost,
-        actions=actions, auto_adjust=auto_adjust,
-        back_adjust=back_adjust, repair=repair, proxy=proxy,
-        rounding=rounding, keepna=keepna, timeout=timeout,
-        raise_errors=False  # stop individual threads raising errors
-    )
+    data = None
+    try:
+        data = Ticker(ticker).history(
+                period=period, interval=interval,
+                start=start, end=end, prepost=prepost,
+                actions=actions, auto_adjust=auto_adjust,
+                back_adjust=back_adjust, repair=repair, proxy=proxy,
+                rounding=rounding, keepna=keepna, timeout=timeout,
+                raise_errors=True
+        )
+    except Exception as e:
+        # glob try/except needed as current thead implementation breaks if exception is raised.
+        shared._DFS[ticker.upper()] = utils.empty_df()
+        shared._ERRORS[ticker.upper()] = repr(e)
+        shared._TRACEBACKS[ticker.upper()] = traceback.format_exc()
+    else:
+        shared._DFS[ticker.upper()] = data
+
+    return data
diff --git a/yfinance/scrapers/quote.py b/yfinance/scrapers/quote.py
--- a/yfinance/scrapers/quote.py
+++ b/yfinance/scrapers/quote.py
@@ -531,6 +531,8 @@ def market_cap(self):
         except Exception as e:
             if "Cannot retrieve share count" in str(e):
                 shares = None
+            elif "failed to decrypt Yahoo" in str(e):
+                shares = None
             else:
                 raise
 


Test patch:
diff --git a/tests/context.py b/tests/context.py
--- a/tests/context.py
+++ b/tests/context.py
@@ -7,3 +7,32 @@
 sys.path.insert(0, _src_dp)
 
 import yfinance
+
+
+# Optional: see the exact requests that are made during tests:
+# import logging
+# logging.basicConfig(level=logging.DEBUG)
+
+
+# Setup a session to rate-limit and cache persistently:
+import datetime as _dt
+import os
+import appdirs as _ad
+from requests import Session
+from requests_cache import CacheMixin, SQLiteCache
+from requests_ratelimiter import LimiterMixin, MemoryQueueBucket
+class CachedLimiterSession(CacheMixin, LimiterMixin, Session):
+    pass
+from pyrate_limiter import Duration, RequestRate, Limiter
+history_rate = RequestRate(1, Duration.SECOND*2)
+limiter = Limiter(history_rate)
+session_gbl = CachedLimiterSession(
+    limiter=limiter,
+    bucket_class=MemoryQueueBucket,
+    backend=SQLiteCache(os.path.join(_ad.user_cache_dir(), "py-yfinance", "unittests-cache"), 
+    					expire_after=_dt.timedelta(hours=1)),
+)
+# Use this instead if only want rate-limiting:
+# from requests_ratelimiter import LimiterSession
+# session_gbl = LimiterSession(limiter=limiter)
+
diff --git a/tests/prices.py b/tests/prices.py
--- a/tests/prices.py
+++ b/tests/prices.py
@@ -1,4 +1,5 @@
 from .context import yfinance as yf
+from .context import session_gbl
 
 import unittest
 
@@ -7,15 +8,11 @@
 import numpy as _np
 import pandas as _pd
 
-import requests_cache
-
 
 class TestPriceHistory(unittest.TestCase):
-    session = None
-
     @classmethod
     def setUpClass(cls):
-        cls.session = requests_cache.CachedSession(backend='memory')
+        cls.session = session_gbl
 
     @classmethod
     def tearDownClass(cls):
@@ -34,11 +31,23 @@ def test_daily_index(self):
                 f = df.index.time == _dt.time(0)
                 self.assertTrue(f.all())
 
+    def test_download(self):
+        tkrs = ["BHP.AX", "IMP.JO", "BP.L", "PNL.L", "INTC"]
+        intervals = ["1d", "1wk", "1mo"]
+        for interval in intervals:
+            df = yf.download(tkrs, period="5y", interval=interval)
+
+            f = df.index.time == _dt.time(0)
+            self.assertTrue(f.all())
+
+            df_tkrs = df.columns.levels[1]
+            self.assertEqual(sorted(tkrs), sorted(df_tkrs))
+
     def test_duplicatingHourly(self):
         tkrs = ["IMP.JO", "BHG.JO", "SSW.JO", "BP.L", "INTC"]
         for tkr in tkrs:
             dat = yf.Ticker(tkr, session=self.session)
-            tz = dat._get_ticker_tz(debug_mode=False, proxy=None, timeout=None)
+            tz = dat._get_ticker_tz(proxy=None, timeout=None)
 
             dt_utc = _tz.timezone("UTC").localize(_dt.datetime.utcnow())
             dt = dt_utc.astimezone(_tz.timezone(tz))
@@ -58,7 +67,7 @@ def test_duplicatingDaily(self):
         test_run = False
         for tkr in tkrs:
             dat = yf.Ticker(tkr, session=self.session)
-            tz = dat._get_ticker_tz(debug_mode=False, proxy=None, timeout=None)
+            tz = dat._get_ticker_tz(proxy=None, timeout=None)
 
             dt_utc = _tz.timezone("UTC").localize(_dt.datetime.utcnow())
             dt = dt_utc.astimezone(_tz.timezone(tz))
@@ -84,7 +93,7 @@ def test_duplicatingWeekly(self):
         test_run = False
         for tkr in tkrs:
             dat = yf.Ticker(tkr, session=self.session)
-            tz = dat._get_ticker_tz(debug_mode=False, proxy=None, timeout=None)
+            tz = dat._get_ticker_tz(proxy=None, timeout=None)
 
             dt = _tz.timezone(tz).localize(_dt.datetime.now())
             if dt.date().weekday() not in [1, 2, 3, 4]:
@@ -401,7 +410,7 @@ class TestPriceRepair(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
-        cls.session = requests_cache.CachedSession(backend='memory')
+        cls.session = session_gbl
 
     @classmethod
     def tearDownClass(cls):
@@ -653,11 +662,3 @@ def test_repair_zeroes_hourly(self):
 
 if __name__ == '__main__':
     unittest.main()
-
-# # Run tests sequentially:
-# import inspect
-# test_src = inspect.getsource(TestPriceHistory)
-# unittest.TestLoader.sortTestMethodsUsing = lambda _, x, y: (
-#     test_src.index(f"def {x}") - test_src.index(f"def {y}")
-# )
-# unittest.main(verbosity=2)
diff --git a/tests/ticker.py b/tests/ticker.py
--- a/tests/ticker.py
+++ b/tests/ticker.py
@@ -12,25 +12,18 @@
 import numpy as np
 
 from .context import yfinance as yf
+from .context import session_gbl
 
 import unittest
 import requests_cache
 
-# Set this to see the exact requests that are made during tests
-DEBUG_LOG_REQUESTS = False
-
-if DEBUG_LOG_REQUESTS:
-    import logging
-
-    logging.basicConfig(level=logging.DEBUG)
-
 
 class TestTicker(unittest.TestCase):
     session = None
 
     @classmethod
     def setUpClass(cls):
-        cls.session = requests_cache.CachedSession(backend='memory')
+        cls.session = session_gbl
 
     @classmethod
     def tearDownClass(cls):
@@ -45,7 +38,7 @@ def test_getTz(self):
 
             # Test:
             dat = yf.Ticker(tkr, session=self.session)
-            tz = dat._get_ticker_tz(debug_mode=False, proxy=None, timeout=None)
+            tz = dat._get_ticker_tz(proxy=None, timeout=None)
 
             self.assertIsNotNone(tz)
 
@@ -54,10 +47,14 @@ def test_badTicker(self):
 
         tkr = "DJI"  # typo of "^DJI"
         dat = yf.Ticker(tkr, session=self.session)
+
         dat.history(period="1wk")
         dat.history(start="2022-01-01")
         dat.history(start="2022-01-01", end="2022-03-01")
-        yf.download([tkr], period="1wk")
+        yf.download([tkr], period="1wk", threads=False, ignore_tz=False)
+        yf.download([tkr], period="1wk", threads=True, ignore_tz=False)
+        yf.download([tkr], period="1wk", threads=False, ignore_tz=True)
+        yf.download([tkr], period="1wk", threads=True, ignore_tz=True)
 
         for k in dat.fast_info:
             dat.fast_info[k]
@@ -69,28 +66,30 @@ def test_badTicker(self):
         dat.dividends
         dat.splits
         dat.actions
-        dat.shares
         dat.get_shares_full()
-        dat.info
-        dat.calendar
-        dat.recommendations
-        dat.earnings
-        dat.quarterly_earnings
-        dat.income_stmt
-        dat.quarterly_income_stmt
-        dat.balance_sheet
-        dat.quarterly_balance_sheet
-        dat.cashflow
-        dat.quarterly_cashflow
-        dat.recommendations_summary
-        dat.analyst_price_target
-        dat.revenue_forecasts
-        dat.sustainability
         dat.options
         dat.news
-        dat.earnings_trend
         dat.earnings_dates
-        dat.earnings_forecasts
+
+        # These require decryption which is broken:
+        # dat.shares
+        # dat.info
+        # dat.calendar
+        # dat.recommendations
+        # dat.earnings
+        # dat.quarterly_earnings
+        # dat.income_stmt
+        # dat.quarterly_income_stmt
+        # dat.balance_sheet
+        # dat.quarterly_balance_sheet
+        # dat.cashflow
+        # dat.quarterly_cashflow
+        # dat.recommendations_summary
+        # dat.analyst_price_target
+        # dat.revenue_forecasts
+        # dat.sustainability
+        # dat.earnings_trend
+        # dat.earnings_forecasts
 
     def test_goodTicker(self):
         # that yfinance works when full api is called on same instance of ticker
@@ -103,7 +102,10 @@ def test_goodTicker(self):
             dat.history(period="1wk")
             dat.history(start="2022-01-01")
             dat.history(start="2022-01-01", end="2022-03-01")
-            yf.download([tkr], period="1wk")
+            yf.download([tkr], period="1wk", threads=False, ignore_tz=False)
+            yf.download([tkr], period="1wk", threads=True, ignore_tz=False)
+            yf.download([tkr], period="1wk", threads=False, ignore_tz=True)
+            yf.download([tkr], period="1wk", threads=True, ignore_tz=True)
 
             for k in dat.fast_info:
                 dat.fast_info[k]
@@ -115,28 +117,30 @@ def test_goodTicker(self):
             dat.dividends
             dat.splits
             dat.actions
-            dat.shares
             dat.get_shares_full()
-            dat.info
-            dat.calendar
-            dat.recommendations
-            dat.earnings
-            dat.quarterly_earnings
-            dat.income_stmt
-            dat.quarterly_income_stmt
-            dat.balance_sheet
-            dat.quarterly_balance_sheet
-            dat.cashflow
-            dat.quarterly_cashflow
-            dat.recommendations_summary
-            dat.analyst_price_target
-            dat.revenue_forecasts
-            dat.sustainability
             dat.options
             dat.news
-            dat.earnings_trend
             dat.earnings_dates
-            dat.earnings_forecasts
+
+            # These require decryption which is broken:
+            # dat.shares
+            # dat.info
+            # dat.calendar
+            # dat.recommendations
+            # dat.earnings
+            # dat.quarterly_earnings
+            # dat.income_stmt
+            # dat.quarterly_income_stmt
+            # dat.balance_sheet
+            # dat.quarterly_balance_sheet
+            # dat.cashflow
+            # dat.quarterly_cashflow
+            # dat.recommendations_summary
+            # dat.analyst_price_target
+            # dat.revenue_forecasts
+            # dat.sustainability
+            # dat.earnings_trend
+            # dat.earnings_forecasts
 
 
 class TestTickerHistory(unittest.TestCase):
@@ -144,7 +148,7 @@ class TestTickerHistory(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
-        cls.session = requests_cache.CachedSession(backend='memory')
+        cls.session = session_gbl
 
     @classmethod
     def tearDownClass(cls):
@@ -153,19 +157,28 @@ def tearDownClass(cls):
 
     def setUp(self):
         # use a ticker that has dividends
-        self.ticker = yf.Ticker("IBM", session=self.session)
+        self.symbol = "IBM"
+        self.ticker = yf.Ticker(self.symbol, session=self.session)
+
+        self.symbols = ["AMZN", "MSFT", "NVDA"]
 
     def tearDown(self):
         self.ticker = None
 
     def test_history(self):
-        with self.assertRaises(RuntimeError):
-            self.ticker.history_metadata
+        md = self.ticker.history_metadata
+        self.assertIn("IBM", md.values(), "metadata missing")
         data = self.ticker.history("1y")
-        self.assertIn("IBM", self.ticker.history_metadata.values(), "metadata missing")
         self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
         self.assertFalse(data.empty, "data is empty")
 
+    def test_download(self):
+        for t in [False, True]:
+            for i in [False, True]:
+                data = yf.download(self.symbols, threads=t, ignore_tz=i)
+                self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+                self.assertFalse(data.empty, "data is empty")
+
     def test_no_expensive_calls_introduced(self):
         """
         Make sure calling history to get price data has not introduced more calls to yahoo than absolutely necessary.
@@ -178,7 +191,7 @@ def test_no_expensive_calls_introduced(self):
         actual_urls_called = tuple([r.url for r in session.cache.filter()])
         session.close()
         expected_urls = (
-            'https://query2.finance.yahoo.com/v8/finance/chart/GOOGL?range=1y&interval=1d&includePrePost=False&events=div%2Csplits%2CcapitalGains',
+            'https://query2.finance.yahoo.com/v8/finance/chart/GOOGL?events=div,splits,capitalGains&includePrePost=False&interval=1d&range=1y',
         )
         self.assertEqual(expected_urls, actual_urls_called, "Different than expected url used to fetch history.")
 
@@ -198,75 +211,76 @@ def test_actions(self):
         self.assertFalse(data.empty, "data is empty")
 
 
-class TestTickerEarnings(unittest.TestCase):
-    session = None
+# Below will fail because decryption broken
+# class TestTickerEarnings(unittest.TestCase):
+#     session = None
 
-    @classmethod
-    def setUpClass(cls):
-        cls.session = requests_cache.CachedSession(backend='memory')
+#     @classmethod
+#     def setUpClass(cls):
+#         cls.session = session_gbl
 
-    @classmethod
-    def tearDownClass(cls):
-        if cls.session is not None:
-            cls.session.close()
+#     @classmethod
+#     def tearDownClass(cls):
+#         if cls.session is not None:
+#             cls.session.close()
 
-    def setUp(self):
-        self.ticker = yf.Ticker("GOOGL", session=self.session)
+#     def setUp(self):
+#         self.ticker = yf.Ticker("GOOGL", session=self.session)
 
-    def tearDown(self):
-        self.ticker = None
+#     def tearDown(self):
+#         self.ticker = None
 
-    def test_earnings(self):
-        data = self.ticker.earnings
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
+#     def test_earnings(self):
+#         data = self.ticker.earnings
+#         self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+#         self.assertFalse(data.empty, "data is empty")
 
-        data_cached = self.ticker.earnings
-        self.assertIs(data, data_cached, "data not cached")
+#         data_cached = self.ticker.earnings
+#         self.assertIs(data, data_cached, "data not cached")
 
-    def test_quarterly_earnings(self):
-        data = self.ticker.quarterly_earnings
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
+#     def test_quarterly_earnings(self):
+#         data = self.ticker.quarterly_earnings
+#         self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+#         self.assertFalse(data.empty, "data is empty")
 
-        data_cached = self.ticker.quarterly_earnings
-        self.assertIs(data, data_cached, "data not cached")
+#         data_cached = self.ticker.quarterly_earnings
+#         self.assertIs(data, data_cached, "data not cached")
 
-    def test_earnings_forecasts(self):
-        data = self.ticker.earnings_forecasts
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
+#     def test_earnings_forecasts(self):
+#         data = self.ticker.earnings_forecasts
+#         self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+#         self.assertFalse(data.empty, "data is empty")
 
-        data_cached = self.ticker.earnings_forecasts
-        self.assertIs(data, data_cached, "data not cached")
+#         data_cached = self.ticker.earnings_forecasts
+#         self.assertIs(data, data_cached, "data not cached")
 
-    def test_earnings_dates(self):
-        data = self.ticker.earnings_dates
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
+#     def test_earnings_dates(self):
+#         data = self.ticker.earnings_dates
+#         self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+#         self.assertFalse(data.empty, "data is empty")
 
-        data_cached = self.ticker.earnings_dates
-        self.assertIs(data, data_cached, "data not cached")
+#         data_cached = self.ticker.earnings_dates
+#         self.assertIs(data, data_cached, "data not cached")
 
-    def test_earnings_trend(self):
-        data = self.ticker.earnings_trend
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
+#     def test_earnings_trend(self):
+#         data = self.ticker.earnings_trend
+#         self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+#         self.assertFalse(data.empty, "data is empty")
 
-        data_cached = self.ticker.earnings_trend
-        self.assertIs(data, data_cached, "data not cached")
+#         data_cached = self.ticker.earnings_trend
+#         self.assertIs(data, data_cached, "data not cached")
 
-    def test_earnings_dates_with_limit(self):
-        # use ticker with lots of historic earnings
-        ticker = yf.Ticker("IBM")
-        limit = 110
-        data = ticker.get_earnings_dates(limit=limit)
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        self.assertEqual(len(data), limit, "Wrong number or rows")
+#     def test_earnings_dates_with_limit(self):
+#         # use ticker with lots of historic earnings
+#         ticker = yf.Ticker("IBM")
+#         limit = 110
+#         data = ticker.get_earnings_dates(limit=limit)
+#         self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+#         self.assertFalse(data.empty, "data is empty")
+#         self.assertEqual(len(data), limit, "Wrong number or rows")
 
-        data_cached = ticker.get_earnings_dates(limit=limit)
-        self.assertIs(data, data_cached, "data not cached")
+#         data_cached = ticker.get_earnings_dates(limit=limit)
+#         self.assertIs(data, data_cached, "data not cached")
 
 
 class TestTickerHolders(unittest.TestCase):
@@ -274,7 +288,7 @@ class TestTickerHolders(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
-        cls.session = requests_cache.CachedSession(backend='memory')
+        cls.session = session_gbl
 
     @classmethod
     def tearDownClass(cls):
@@ -317,7 +331,7 @@ class TestTickerMiscFinancials(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
-        cls.session = requests_cache.CachedSession(backend='memory')
+        cls.session = session_gbl
 
     @classmethod
     def tearDownClass(cls):
@@ -335,318 +349,6 @@ def setUp(self):
     def tearDown(self):
         self.ticker = None
 
-    def test_income_statement(self):
-        expected_keys = ["Total Revenue", "Basic EPS"]
-        expected_periods_days = 365
-
-        # Test contents of table
-        data = self.ticker.get_income_stmt(pretty=True)
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        for k in expected_keys:
-            self.assertIn(k, data.index, "Did not find expected row in index")
-        period = abs((data.columns[0]-data.columns[1]).days)
-        self.assertLess(abs(period-expected_periods_days), 20, "Not returning annual financials")
-
-        # Test property defaults
-        data2 = self.ticker.income_stmt
-        self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
-
-        # Test pretty=False
-        expected_keys = [k.replace(' ', '') for k in expected_keys]
-        data = self.ticker.get_income_stmt(pretty=False)
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        for k in expected_keys:
-            self.assertIn(k, data.index, "Did not find expected row in index")
-
-        # Test to_dict
-        data = self.ticker.get_income_stmt(as_dict=True)
-        self.assertIsInstance(data, dict, "data has wrong type")
-
-
-    def test_quarterly_income_statement(self):
-        expected_keys = ["Total Revenue", "Basic EPS"]
-        expected_periods_days = 365//4
-
-        # Test contents of table
-        data = self.ticker.get_income_stmt(pretty=True, freq="quarterly")
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        for k in expected_keys:
-            self.assertIn(k, data.index, "Did not find expected row in index")
-        period = abs((data.columns[0]-data.columns[1]).days)
-        self.assertLess(abs(period-expected_periods_days), 20, "Not returning quarterly financials")
-
-        # Test property defaults
-        data2 = self.ticker.quarterly_income_stmt
-        self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
-
-        # Test pretty=False
-        expected_keys = [k.replace(' ', '') for k in expected_keys]
-        data = self.ticker.get_income_stmt(pretty=False, freq="quarterly")
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        for k in expected_keys:
-            self.assertIn(k, data.index, "Did not find expected row in index")
-
-        # Test to_dict
-        data = self.ticker.get_income_stmt(as_dict=True)
-        self.assertIsInstance(data, dict, "data has wrong type")
-
-    def test_quarterly_income_statement_old_fmt(self):
-        expected_row = "TotalRevenue"
-        data = self.ticker_old_fmt.get_income_stmt(freq="quarterly", legacy=True)
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        self.assertIn(expected_row, data.index, "Did not find expected row in index")
-
-        data_cached = self.ticker_old_fmt.get_income_stmt(freq="quarterly", legacy=True)
-        self.assertIs(data, data_cached, "data not cached")
-
-    def test_balance_sheet(self):
-        expected_keys = ["Total Assets", "Net PPE"]
-        expected_periods_days = 365
-
-        # Test contents of table
-        data = self.ticker.get_balance_sheet(pretty=True)
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        for k in expected_keys:
-            self.assertIn(k, data.index, "Did not find expected row in index")
-        period = abs((data.columns[0]-data.columns[1]).days)
-        self.assertLess(abs(period-expected_periods_days), 20, "Not returning annual financials")
-
-        # Test property defaults
-        data2 = self.ticker.balance_sheet
-        self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
-
-        # Test pretty=False
-        expected_keys = [k.replace(' ', '') for k in expected_keys]
-        data = self.ticker.get_balance_sheet(pretty=False)
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        for k in expected_keys:
-            self.assertIn(k, data.index, "Did not find expected row in index")
-
-        # Test to_dict
-        data = self.ticker.get_balance_sheet(as_dict=True)
-        self.assertIsInstance(data, dict, "data has wrong type")
-
-    def test_quarterly_balance_sheet(self):
-        expected_keys = ["Total Assets", "Net PPE"]
-        expected_periods_days = 365//4
-
-        # Test contents of table
-        data = self.ticker.get_balance_sheet(pretty=True, freq="quarterly")
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        for k in expected_keys:
-            self.assertIn(k, data.index, "Did not find expected row in index")
-        period = abs((data.columns[0]-data.columns[1]).days)
-        self.assertLess(abs(period-expected_periods_days), 20, "Not returning quarterly financials")
-
-        # Test property defaults
-        data2 = self.ticker.quarterly_balance_sheet
-        self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
-
-        # Test pretty=False
-        expected_keys = [k.replace(' ', '') for k in expected_keys]
-        data = self.ticker.get_balance_sheet(pretty=False, freq="quarterly")
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        for k in expected_keys:
-            self.assertIn(k, data.index, "Did not find expected row in index")
-
-        # Test to_dict
-        data = self.ticker.get_balance_sheet(as_dict=True, freq="quarterly")
-        self.assertIsInstance(data, dict, "data has wrong type")
-
-    def test_quarterly_balance_sheet_old_fmt(self):
-        expected_row = "TotalAssets"
-        data = self.ticker_old_fmt.get_balance_sheet(freq="quarterly", legacy=True)
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        self.assertIn(expected_row, data.index, "Did not find expected row in index")
-
-        data_cached = self.ticker_old_fmt.get_balance_sheet(freq="quarterly", legacy=True)
-        self.assertIs(data, data_cached, "data not cached")
-
-    def test_cash_flow(self):
-        expected_keys = ["Operating Cash Flow", "Net PPE Purchase And Sale"]
-        expected_periods_days = 365
-
-        # Test contents of table
-        data = self.ticker.get_cashflow(pretty=True)
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        for k in expected_keys:
-            self.assertIn(k, data.index, "Did not find expected row in index")
-        period = abs((data.columns[0]-data.columns[1]).days)
-        self.assertLess(abs(period-expected_periods_days), 20, "Not returning annual financials")
-
-        # Test property defaults
-        data2 = self.ticker.cashflow
-        self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
-
-        # Test pretty=False
-        expected_keys = [k.replace(' ', '') for k in expected_keys]
-        data = self.ticker.get_cashflow(pretty=False)
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        for k in expected_keys:
-            self.assertIn(k, data.index, "Did not find expected row in index")
-
-        # Test to_dict
-        data = self.ticker.get_cashflow(as_dict=True)
-        self.assertIsInstance(data, dict, "data has wrong type")
-
-    def test_quarterly_cash_flow(self):
-        expected_keys = ["Operating Cash Flow", "Net PPE Purchase And Sale"]
-        expected_periods_days = 365//4
-
-        # Test contents of table
-        data = self.ticker.get_cashflow(pretty=True, freq="quarterly")
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        for k in expected_keys:
-            self.assertIn(k, data.index, "Did not find expected row in index")
-        period = abs((data.columns[0]-data.columns[1]).days)
-        self.assertLess(abs(period-expected_periods_days), 20, "Not returning quarterly financials")
-
-        # Test property defaults
-        data2 = self.ticker.quarterly_cashflow
-        self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
-
-        # Test pretty=False
-        expected_keys = [k.replace(' ', '') for k in expected_keys]
-        data = self.ticker.get_cashflow(pretty=False, freq="quarterly")
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        for k in expected_keys:
-            self.assertIn(k, data.index, "Did not find expected row in index")
-
-        # Test to_dict
-        data = self.ticker.get_cashflow(as_dict=True)
-        self.assertIsInstance(data, dict, "data has wrong type")
-
-    def test_quarterly_cashflow_old_fmt(self):
-        expected_row = "NetIncome"
-        data = self.ticker_old_fmt.get_cashflow(legacy=True, freq="quarterly")
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-        self.assertIn(expected_row, data.index, "Did not find expected row in index")
-
-        data_cached = self.ticker_old_fmt.get_cashflow(legacy=True, freq="quarterly")
-        self.assertIs(data, data_cached, "data not cached")
-
-    def test_income_alt_names(self):
-        i1 = self.ticker.income_stmt
-        i2 = self.ticker.incomestmt
-        self.assertTrue(i1.equals(i2))
-        i3 = self.ticker.financials
-        self.assertTrue(i1.equals(i3))
-
-        i1 = self.ticker.get_income_stmt()
-        i2 = self.ticker.get_incomestmt()
-        self.assertTrue(i1.equals(i2))
-        i3 = self.ticker.get_financials()
-        self.assertTrue(i1.equals(i3))
-
-        i1 = self.ticker.quarterly_income_stmt
-        i2 = self.ticker.quarterly_incomestmt
-        self.assertTrue(i1.equals(i2))
-        i3 = self.ticker.quarterly_financials
-        self.assertTrue(i1.equals(i3))
-
-        i1 = self.ticker.get_income_stmt(freq="quarterly")
-        i2 = self.ticker.get_incomestmt(freq="quarterly")
-        self.assertTrue(i1.equals(i2))
-        i3 = self.ticker.get_financials(freq="quarterly")
-        self.assertTrue(i1.equals(i3))
-
-    def test_balance_sheet_alt_names(self):
-        i1 = self.ticker.balance_sheet
-        i2 = self.ticker.balancesheet
-        self.assertTrue(i1.equals(i2))
-
-        i1 = self.ticker.get_balance_sheet()
-        i2 = self.ticker.get_balancesheet()
-        self.assertTrue(i1.equals(i2))
-
-        i1 = self.ticker.quarterly_balance_sheet
-        i2 = self.ticker.quarterly_balancesheet
-        self.assertTrue(i1.equals(i2))
-
-        i1 = self.ticker.get_balance_sheet(freq="quarterly")
-        i2 = self.ticker.get_balancesheet(freq="quarterly")
-        self.assertTrue(i1.equals(i2))
-
-    def test_cash_flow_alt_names(self):
-        i1 = self.ticker.cash_flow
-        i2 = self.ticker.cashflow
-        self.assertTrue(i1.equals(i2))
-
-        i1 = self.ticker.get_cash_flow()
-        i2 = self.ticker.get_cashflow()
-        self.assertTrue(i1.equals(i2))
-
-        i1 = self.ticker.quarterly_cash_flow
-        i2 = self.ticker.quarterly_cashflow
-        self.assertTrue(i1.equals(i2))
-
-        i1 = self.ticker.get_cash_flow(freq="quarterly")
-        i2 = self.ticker.get_cashflow(freq="quarterly")
-        self.assertTrue(i1.equals(i2))
-
-    def test_sustainability(self):
-        data = self.ticker.sustainability
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-
-        data_cached = self.ticker.sustainability
-        self.assertIs(data, data_cached, "data not cached")
-
-    def test_recommendations(self):
-        data = self.ticker.recommendations
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-
-        data_cached = self.ticker.recommendations
-        self.assertIs(data, data_cached, "data not cached")
-
-    def test_recommendations_summary(self):
-        data = self.ticker.recommendations_summary
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-
-        data_cached = self.ticker.recommendations_summary
-        self.assertIs(data, data_cached, "data not cached")
-
-    def test_analyst_price_target(self):
-        data = self.ticker.analyst_price_target
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-
-        data_cached = self.ticker.analyst_price_target
-        self.assertIs(data, data_cached, "data not cached")
-
-    def test_revenue_forecasts(self):
-        data = self.ticker.revenue_forecasts
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-
-        data_cached = self.ticker.revenue_forecasts
-        self.assertIs(data, data_cached, "data not cached")
-
-    def test_calendar(self):
-        data = self.ticker.calendar
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-
-        data_cached = self.ticker.calendar
-        self.assertIs(data, data_cached, "data not cached")
-
     def test_isin(self):
         data = self.ticker.isin
         self.assertIsInstance(data, str, "data has wrong type")
@@ -660,18 +362,331 @@ def test_options(self):
         self.assertIsInstance(data, tuple, "data has wrong type")
         self.assertTrue(len(data) > 1, "data is empty")
 
-    def test_shares(self):
-        data = self.ticker.shares
-        self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
-        self.assertFalse(data.empty, "data is empty")
-
     def test_shares_full(self):
         data = self.ticker.get_shares_full()
         self.assertIsInstance(data, pd.Series, "data has wrong type")
         self.assertFalse(data.empty, "data is empty")
 
-    def test_bad_freq_value_raises_exception(self):
-        self.assertRaises(ValueError, lambda: self.ticker.get_cashflow(freq="badarg"))
+    # Below will fail because decryption broken
+
+    # def test_income_statement(self):
+    #     expected_keys = ["Total Revenue", "Basic EPS"]
+    #     expected_periods_days = 365
+
+    #     # Test contents of table
+    #     data = self.ticker.get_income_stmt(pretty=True)
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+    #     for k in expected_keys:
+    #         self.assertIn(k, data.index, "Did not find expected row in index")
+    #     period = abs((data.columns[0]-data.columns[1]).days)
+    #     self.assertLess(abs(period-expected_periods_days), 20, "Not returning annual financials")
+
+    #     # Test property defaults
+    #     data2 = self.ticker.income_stmt
+    #     self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
+
+    #     # Test pretty=False
+    #     expected_keys = [k.replace(' ', '') for k in expected_keys]
+    #     data = self.ticker.get_income_stmt(pretty=False)
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+    #     for k in expected_keys:
+    #         self.assertIn(k, data.index, "Did not find expected row in index")
+
+    #     # Test to_dict
+    #     data = self.ticker.get_income_stmt(as_dict=True)
+    #     self.assertIsInstance(data, dict, "data has wrong type")
+
+    # def test_quarterly_income_statement(self):
+    #     expected_keys = ["Total Revenue", "Basic EPS"]
+    #     expected_periods_days = 365//4
+
+    #     # Test contents of table
+    #     data = self.ticker.get_income_stmt(pretty=True, freq="quarterly")
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+    #     for k in expected_keys:
+    #         self.assertIn(k, data.index, "Did not find expected row in index")
+    #     period = abs((data.columns[0]-data.columns[1]).days)
+    #     self.assertLess(abs(period-expected_periods_days), 20, "Not returning quarterly financials")
+
+    #     # Test property defaults
+    #     data2 = self.ticker.quarterly_income_stmt
+    #     self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
+
+    #     # Test pretty=False
+    #     expected_keys = [k.replace(' ', '') for k in expected_keys]
+    #     data = self.ticker.get_income_stmt(pretty=False, freq="quarterly")
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+    #     for k in expected_keys:
+    #         self.assertIn(k, data.index, "Did not find expected row in index")
+
+    #     # Test to_dict
+    #     data = self.ticker.get_income_stmt(as_dict=True)
+    #     self.assertIsInstance(data, dict, "data has wrong type")
+
+    # def test_quarterly_income_statement_old_fmt(self):
+    #     expected_row = "TotalRevenue"
+    #     data = self.ticker_old_fmt.get_income_stmt(freq="quarterly", legacy=True)
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+    #     self.assertIn(expected_row, data.index, "Did not find expected row in index")
+
+    #     data_cached = self.ticker_old_fmt.get_income_stmt(freq="quarterly", legacy=True)
+    #     self.assertIs(data, data_cached, "data not cached")
+
+    # def test_balance_sheet(self):
+    #     expected_keys = ["Total Assets", "Net PPE"]
+    #     expected_periods_days = 365
+
+    #     # Test contents of table
+    #     data = self.ticker.get_balance_sheet(pretty=True)
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+    #     for k in expected_keys:
+    #         self.assertIn(k, data.index, "Did not find expected row in index")
+    #     period = abs((data.columns[0]-data.columns[1]).days)
+    #     self.assertLess(abs(period-expected_periods_days), 20, "Not returning annual financials")
+
+    #     # Test property defaults
+    #     data2 = self.ticker.balance_sheet
+    #     self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
+
+    #     # Test pretty=False
+    #     expected_keys = [k.replace(' ', '') for k in expected_keys]
+    #     data = self.ticker.get_balance_sheet(pretty=False)
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+    #     for k in expected_keys:
+    #         self.assertIn(k, data.index, "Did not find expected row in index")
+
+    #     # Test to_dict
+    #     data = self.ticker.get_balance_sheet(as_dict=True)
+    #     self.assertIsInstance(data, dict, "data has wrong type")
+
+    # def test_quarterly_balance_sheet(self):
+    #     expected_keys = ["Total Assets", "Net PPE"]
+    #     expected_periods_days = 365//4
+
+    #     # Test contents of table
+    #     data = self.ticker.get_balance_sheet(pretty=True, freq="quarterly")
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+    #     for k in expected_keys:
+    #         self.assertIn(k, data.index, "Did not find expected row in index")
+    #     period = abs((data.columns[0]-data.columns[1]).days)
+    #     self.assertLess(abs(period-expected_periods_days), 20, "Not returning quarterly financials")
+
+    #     # Test property defaults
+    #     data2 = self.ticker.quarterly_balance_sheet
+    #     self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
+
+    #     # Test pretty=False
+    #     expected_keys = [k.replace(' ', '') for k in expected_keys]
+    #     data = self.ticker.get_balance_sheet(pretty=False, freq="quarterly")
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+    #     for k in expected_keys:
+    #         self.assertIn(k, data.index, "Did not find expected row in index")
+
+    #     # Test to_dict
+    #     data = self.ticker.get_balance_sheet(as_dict=True, freq="quarterly")
+    #     self.assertIsInstance(data, dict, "data has wrong type")
+
+    # def test_quarterly_balance_sheet_old_fmt(self):
+    #     expected_row = "TotalAssets"
+    #     data = self.ticker_old_fmt.get_balance_sheet(freq="quarterly", legacy=True)
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+    #     self.assertIn(expected_row, data.index, "Did not find expected row in index")
+
+    #     data_cached = self.ticker_old_fmt.get_balance_sheet(freq="quarterly", legacy=True)
+    #     self.assertIs(data, data_cached, "data not cached")
+
+    # def test_cash_flow(self):
+    #     expected_keys = ["Operating Cash Flow", "Net PPE Purchase And Sale"]
+    #     expected_periods_days = 365
+
+    #     # Test contents of table
+    #     data = self.ticker.get_cashflow(pretty=True)
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+    #     for k in expected_keys:
+    #         self.assertIn(k, data.index, "Did not find expected row in index")
+    #     period = abs((data.columns[0]-data.columns[1]).days)
+    #     self.assertLess(abs(period-expected_periods_days), 20, "Not returning annual financials")
+
+    #     # Test property defaults
+    #     data2 = self.ticker.cashflow
+    #     self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
+
+    #     # Test pretty=False
+    #     expected_keys = [k.replace(' ', '') for k in expected_keys]
+    #     data = self.ticker.get_cashflow(pretty=False)
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+    #     for k in expected_keys:
+    #         self.assertIn(k, data.index, "Did not find expected row in index")
+
+    #     # Test to_dict
+    #     data = self.ticker.get_cashflow(as_dict=True)
+    #     self.assertIsInstance(data, dict, "data has wrong type")
+
+    # def test_quarterly_cash_flow(self):
+    #     expected_keys = ["Operating Cash Flow", "Net PPE Purchase And Sale"]
+    #     expected_periods_days = 365//4
+
+    #     # Test contents of table
+    #     data = self.ticker.get_cashflow(pretty=True, freq="quarterly")
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+    #     for k in expected_keys:
+    #         self.assertIn(k, data.index, "Did not find expected row in index")
+    #     period = abs((data.columns[0]-data.columns[1]).days)
+    #     self.assertLess(abs(period-expected_periods_days), 20, "Not returning quarterly financials")
+
+    #     # Test property defaults
+    #     data2 = self.ticker.quarterly_cashflow
+    #     self.assertTrue(data.equals(data2), "property not defaulting to 'pretty=True'")
+
+    #     # Test pretty=False
+    #     expected_keys = [k.replace(' ', '') for k in expected_keys]
+    #     data = self.ticker.get_cashflow(pretty=False, freq="quarterly")
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+    #     for k in expected_keys:
+    #         self.assertIn(k, data.index, "Did not find expected row in index")
+
+    #     # Test to_dict
+    #     data = self.ticker.get_cashflow(as_dict=True)
+    #     self.assertIsInstance(data, dict, "data has wrong type")
+
+    # def test_quarterly_cashflow_old_fmt(self):
+    #     expected_row = "NetIncome"
+    #     data = self.ticker_old_fmt.get_cashflow(legacy=True, freq="quarterly")
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+    #     self.assertIn(expected_row, data.index, "Did not find expected row in index")
+
+    #     data_cached = self.ticker_old_fmt.get_cashflow(legacy=True, freq="quarterly")
+    #     self.assertIs(data, data_cached, "data not cached")
+
+    # def test_income_alt_names(self):
+    #     i1 = self.ticker.income_stmt
+    #     i2 = self.ticker.incomestmt
+    #     self.assertTrue(i1.equals(i2))
+    #     i3 = self.ticker.financials
+    #     self.assertTrue(i1.equals(i3))
+
+    #     i1 = self.ticker.get_income_stmt()
+    #     i2 = self.ticker.get_incomestmt()
+    #     self.assertTrue(i1.equals(i2))
+    #     i3 = self.ticker.get_financials()
+    #     self.assertTrue(i1.equals(i3))
+
+    #     i1 = self.ticker.quarterly_income_stmt
+    #     i2 = self.ticker.quarterly_incomestmt
+    #     self.assertTrue(i1.equals(i2))
+    #     i3 = self.ticker.quarterly_financials
+    #     self.assertTrue(i1.equals(i3))
+
+    #     i1 = self.ticker.get_income_stmt(freq="quarterly")
+    #     i2 = self.ticker.get_incomestmt(freq="quarterly")
+    #     self.assertTrue(i1.equals(i2))
+    #     i3 = self.ticker.get_financials(freq="quarterly")
+    #     self.assertTrue(i1.equals(i3))
+
+    # def test_balance_sheet_alt_names(self):
+    #     i1 = self.ticker.balance_sheet
+    #     i2 = self.ticker.balancesheet
+    #     self.assertTrue(i1.equals(i2))
+
+    #     i1 = self.ticker.get_balance_sheet()
+    #     i2 = self.ticker.get_balancesheet()
+    #     self.assertTrue(i1.equals(i2))
+
+    #     i1 = self.ticker.quarterly_balance_sheet
+    #     i2 = self.ticker.quarterly_balancesheet
+    #     self.assertTrue(i1.equals(i2))
+
+    #     i1 = self.ticker.get_balance_sheet(freq="quarterly")
+    #     i2 = self.ticker.get_balancesheet(freq="quarterly")
+    #     self.assertTrue(i1.equals(i2))
+
+    # def test_cash_flow_alt_names(self):
+    #     i1 = self.ticker.cash_flow
+    #     i2 = self.ticker.cashflow
+    #     self.assertTrue(i1.equals(i2))
+
+    #     i1 = self.ticker.get_cash_flow()
+    #     i2 = self.ticker.get_cashflow()
+    #     self.assertTrue(i1.equals(i2))
+
+    #     i1 = self.ticker.quarterly_cash_flow
+    #     i2 = self.ticker.quarterly_cashflow
+    #     self.assertTrue(i1.equals(i2))
+
+    #     i1 = self.ticker.get_cash_flow(freq="quarterly")
+    #     i2 = self.ticker.get_cashflow(freq="quarterly")
+    #     self.assertTrue(i1.equals(i2))
+
+    # def test_sustainability(self):
+    #     data = self.ticker.sustainability
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+
+    #     data_cached = self.ticker.sustainability
+    #     self.assertIs(data, data_cached, "data not cached")
+
+    # def test_recommendations(self):
+    #     data = self.ticker.recommendations
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+
+    #     data_cached = self.ticker.recommendations
+    #     self.assertIs(data, data_cached, "data not cached")
+
+    # def test_recommendations_summary(self):
+    #     data = self.ticker.recommendations_summary
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+
+    #     data_cached = self.ticker.recommendations_summary
+    #     self.assertIs(data, data_cached, "data not cached")
+
+    # def test_analyst_price_target(self):
+    #     data = self.ticker.analyst_price_target
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+
+    #     data_cached = self.ticker.analyst_price_target
+    #     self.assertIs(data, data_cached, "data not cached")
+
+    # def test_revenue_forecasts(self):
+    #     data = self.ticker.revenue_forecasts
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+
+    #     data_cached = self.ticker.revenue_forecasts
+    #     self.assertIs(data, data_cached, "data not cached")
+
+    # def test_calendar(self):
+    #     data = self.ticker.calendar
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+
+    #     data_cached = self.ticker.calendar
+    #     self.assertIs(data, data_cached, "data not cached")
+
+    # def test_shares(self):
+    #     data = self.ticker.shares
+    #     self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
+    #     self.assertFalse(data.empty, "data is empty")
+
+    # def test_bad_freq_value_raises_exception(self):
+    #     self.assertRaises(ValueError, lambda: self.ticker.get_cashflow(freq="badarg"))
 
 
 class TestTickerInfo(unittest.TestCase):
@@ -679,7 +694,7 @@ class TestTickerInfo(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
-        cls.session = requests_cache.CachedSession(backend='memory')
+        cls.session = session_gbl
 
     @classmethod
     def tearDownClass(cls):
@@ -697,115 +712,117 @@ def setUp(self):
     def tearDown(self):
         self.ticker = None
 
-    def test_info(self):
-        data = self.tickers[0].info
-        self.assertIsInstance(data, dict, "data has wrong type")
-        self.assertIn("symbol", data.keys(), "Did not find expected key in info dict")
-        self.assertEqual(self.symbols[0], data["symbol"], "Wrong symbol value in info dict")
-
     def test_fast_info(self):
         f = yf.Ticker("AAPL", session=self.session).fast_info
         for k in f:
             self.assertIsNotNone(f[k])
 
-    def test_fast_info_matches_info(self):
-        yf.scrapers.quote.PRUNE_INFO = False
-
-        fast_info_keys = set()
-        for ticker in self.tickers:
-            fast_info_keys.update(set(ticker.fast_info.keys()))
-        fast_info_keys = sorted(list(fast_info_keys))
-
-        key_rename_map = {}
-        key_rename_map["currency"] = "currency"
-        key_rename_map["quote_type"] = "quoteType"
-        key_rename_map["timezone"] = "exchangeTimezoneName"
-
-        key_rename_map["last_price"] = ["currentPrice", "regularMarketPrice"]
-        key_rename_map["open"] = ["open", "regularMarketOpen"]
-        key_rename_map["day_high"] = ["dayHigh", "regularMarketDayHigh"]
-        key_rename_map["day_low"] = ["dayLow", "regularMarketDayLow"]
-        key_rename_map["previous_close"] = ["previousClose"]
-        key_rename_map["regular_market_previous_close"] = ["regularMarketPreviousClose"]
-
-        key_rename_map["fifty_day_average"] = "fiftyDayAverage"
-        key_rename_map["two_hundred_day_average"] = "twoHundredDayAverage"
-        key_rename_map["year_change"] = ["52WeekChange", "fiftyTwoWeekChange"]
-        key_rename_map["year_high"] = "fiftyTwoWeekHigh"
-        key_rename_map["year_low"] = "fiftyTwoWeekLow"
-
-        key_rename_map["last_volume"] = ["volume", "regularMarketVolume"]
-        key_rename_map["ten_day_average_volume"] = ["averageVolume10days", "averageDailyVolume10Day"]
-        key_rename_map["three_month_average_volume"] = "averageVolume"
-
-        key_rename_map["market_cap"] = "marketCap"
-        key_rename_map["shares"] = "sharesOutstanding"
-
-        for k in list(key_rename_map.keys()):
-            if '_' in k:
-                key_rename_map[yf.utils.snake_case_2_camelCase(k)] = key_rename_map[k]
-
-        # Note: share count items in info[] are bad. Sometimes the float > outstanding!
-        # So often fast_info["shares"] does not match. 
-        # Why isn't fast_info["shares"] wrong? Because using it to calculate market cap always correct.
-        bad_keys = {"shares"}
-
-        # Loose tolerance for averages, no idea why don't match info[]. Is info wrong?
-        custom_tolerances = {}
-        custom_tolerances["year_change"] = 1.0
-        # custom_tolerances["ten_day_average_volume"] = 1e-3
-        custom_tolerances["ten_day_average_volume"] = 1e-1
-        # custom_tolerances["three_month_average_volume"] = 1e-2
-        custom_tolerances["three_month_average_volume"] = 5e-1
-        custom_tolerances["fifty_day_average"] = 1e-2
-        custom_tolerances["two_hundred_day_average"] = 1e-2
-        for k in list(custom_tolerances.keys()):
-            if '_' in k:
-                custom_tolerances[yf.utils.snake_case_2_camelCase(k)] = custom_tolerances[k]
-
-        for k in fast_info_keys:
-            if k in key_rename_map:
-                k2 = key_rename_map[k]
-            else:
-                k2 = k
-
-            if not isinstance(k2, list):
-                k2 = [k2]
-
-            for m in k2:
-                for ticker in self.tickers:
-                    if not m in ticker.info:
-                        # print(f"symbol={ticker.ticker}: fast_info key '{k}' mapped to info key '{m}' but not present in info")
-                        continue
-
-                    if k in bad_keys:
-                        continue
-
-                    if k in custom_tolerances:
-                        rtol = custom_tolerances[k]
-                    else:
-                        rtol = 5e-3
-                        # rtol = 1e-4
-
-                    correct = ticker.info[m]
-                    test = ticker.fast_info[k]
-                    # print(f"Testing: symbol={ticker.ticker} m={m} k={k}: test={test} vs correct={correct}")
-                    if k in ["market_cap","marketCap"] and ticker.fast_info["currency"] in ["GBp", "ILA"]:
-                        # Adjust for currency to match Yahoo:
-                        test *= 0.01
-                    try:
-                        if correct is None:
-                            self.assertTrue(test is None or (not np.isnan(test)), f"{k}: {test} must be None or real value because correct={correct}")
-                        elif isinstance(test, float) or isinstance(correct, int):
-                            self.assertTrue(np.isclose(test, correct, rtol=rtol), f"{ticker.ticker} {k}: {test} != {correct}")
-                        else:
-                            self.assertEqual(test, correct, f"{k}: {test} != {correct}")
-                    except:
-                        if k in ["regularMarketPreviousClose"] and ticker.ticker in ["ADS.DE"]:
-                            # Yahoo is wrong, is returning post-market close not regular
-                            continue
-                        else:
-                            raise
+    # Below will fail because decryption broken
+
+    # def test_info(self):
+    #     data = self.tickers[0].info
+    #     self.assertIsInstance(data, dict, "data has wrong type")
+    #     self.assertIn("symbol", data.keys(), "Did not find expected key in info dict")
+    #     self.assertEqual(self.symbols[0], data["symbol"], "Wrong symbol value in info dict")
+
+    # def test_fast_info_matches_info(self):
+    #     yf.scrapers.quote.PRUNE_INFO = False
+
+    #     fast_info_keys = set()
+    #     for ticker in self.tickers:
+    #         fast_info_keys.update(set(ticker.fast_info.keys()))
+    #     fast_info_keys = sorted(list(fast_info_keys))
+
+    #     key_rename_map = {}
+    #     key_rename_map["currency"] = "currency"
+    #     key_rename_map["quote_type"] = "quoteType"
+    #     key_rename_map["timezone"] = "exchangeTimezoneName"
+
+    #     key_rename_map["last_price"] = ["currentPrice", "regularMarketPrice"]
+    #     key_rename_map["open"] = ["open", "regularMarketOpen"]
+    #     key_rename_map["day_high"] = ["dayHigh", "regularMarketDayHigh"]
+    #     key_rename_map["day_low"] = ["dayLow", "regularMarketDayLow"]
+    #     key_rename_map["previous_close"] = ["previousClose"]
+    #     key_rename_map["regular_market_previous_close"] = ["regularMarketPreviousClose"]
+
+    #     key_rename_map["fifty_day_average"] = "fiftyDayAverage"
+    #     key_rename_map["two_hundred_day_average"] = "twoHundredDayAverage"
+    #     key_rename_map["year_change"] = ["52WeekChange", "fiftyTwoWeekChange"]
+    #     key_rename_map["year_high"] = "fiftyTwoWeekHigh"
+    #     key_rename_map["year_low"] = "fiftyTwoWeekLow"
+
+    #     key_rename_map["last_volume"] = ["volume", "regularMarketVolume"]
+    #     key_rename_map["ten_day_average_volume"] = ["averageVolume10days", "averageDailyVolume10Day"]
+    #     key_rename_map["three_month_average_volume"] = "averageVolume"
+
+    #     key_rename_map["market_cap"] = "marketCap"
+    #     key_rename_map["shares"] = "sharesOutstanding"
+
+    #     for k in list(key_rename_map.keys()):
+    #         if '_' in k:
+    #             key_rename_map[yf.utils.snake_case_2_camelCase(k)] = key_rename_map[k]
+
+    #     # Note: share count items in info[] are bad. Sometimes the float > outstanding!
+    #     # So often fast_info["shares"] does not match. 
+    #     # Why isn't fast_info["shares"] wrong? Because using it to calculate market cap always correct.
+    #     bad_keys = {"shares"}
+
+    #     # Loose tolerance for averages, no idea why don't match info[]. Is info wrong?
+    #     custom_tolerances = {}
+    #     custom_tolerances["year_change"] = 1.0
+    #     # custom_tolerances["ten_day_average_volume"] = 1e-3
+    #     custom_tolerances["ten_day_average_volume"] = 1e-1
+    #     # custom_tolerances["three_month_average_volume"] = 1e-2
+    #     custom_tolerances["three_month_average_volume"] = 5e-1
+    #     custom_tolerances["fifty_day_average"] = 1e-2
+    #     custom_tolerances["two_hundred_day_average"] = 1e-2
+    #     for k in list(custom_tolerances.keys()):
+    #         if '_' in k:
+    #             custom_tolerances[yf.utils.snake_case_2_camelCase(k)] = custom_tolerances[k]
+
+    #     for k in fast_info_keys:
+    #         if k in key_rename_map:
+    #             k2 = key_rename_map[k]
+    #         else:
+    #             k2 = k
+
+    #         if not isinstance(k2, list):
+    #             k2 = [k2]
+
+    #         for m in k2:
+    #             for ticker in self.tickers:
+    #                 if not m in ticker.info:
+    #                     # print(f"symbol={ticker.ticker}: fast_info key '{k}' mapped to info key '{m}' but not present in info")
+    #                     continue
+
+    #                 if k in bad_keys:
+    #                     continue
+
+    #                 if k in custom_tolerances:
+    #                     rtol = custom_tolerances[k]
+    #                 else:
+    #                     rtol = 5e-3
+    #                     # rtol = 1e-4
+
+    #                 correct = ticker.info[m]
+    #                 test = ticker.fast_info[k]
+    #                 # print(f"Testing: symbol={ticker.ticker} m={m} k={k}: test={test} vs correct={correct}")
+    #                 if k in ["market_cap","marketCap"] and ticker.fast_info["currency"] in ["GBp", "ILA"]:
+    #                     # Adjust for currency to match Yahoo:
+    #                     test *= 0.01
+    #                 try:
+    #                     if correct is None:
+    #                         self.assertTrue(test is None or (not np.isnan(test)), f"{k}: {test} must be None or real value because correct={correct}")
+    #                     elif isinstance(test, float) or isinstance(correct, int):
+    #                         self.assertTrue(np.isclose(test, correct, rtol=rtol), f"{ticker.ticker} {k}: {test} != {correct}")
+    #                     else:
+    #                         self.assertEqual(test, correct, f"{k}: {test} != {correct}")
+    #                 except:
+    #                     if k in ["regularMarketPreviousClose"] and ticker.ticker in ["ADS.DE"]:
+    #                         # Yahoo is wrong, is returning post-market close not regular
+    #                         continue
+    #                     else:
+    #                         raise