Repository: ranaroussi/yfinance

Issue:
Tutorial crashes
### Describe bug

This tutorial code crashes now:
```
import yfinance
ticker = yfinance.Ticker("MSFT")
ticker.info

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[1], line 3
      1 import yfinance
      2 ticker = yfinance.Ticker("MSFT")
----> 3 ticker.info

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/ticker.py:142, in Ticker.info(self)
    140 @property
    141 def info(self) -> dict:
--> 142     return self.get_info()

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/base.py:1736, in TickerBase.get_info(self, proxy)
   1734 def get_info(self, proxy=None) -> dict:
   1735     self._quote.proxy = proxy
-> 1736     data = self._quote.info
   1737     return data

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/scrapers/quote.py:572, in Quote.info(self)
    569 @property
    570 def info(self) -> dict:
    571     if self._info is None:
--> 572         self._fetch(self.proxy)
    573         self._fetch_complementary(self.proxy)
    575     return self._info

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/scrapers/quote.py:602, in Quote._fetch(self, proxy)
    600 modules = ','.join(modules)
    601 params_dict = {"modules": modules, "ssl": "true"}
--> 602 result = self._data.get_raw_json(
    603     _BASIC_URL_ + f"/{self._symbol}", params=params_dict, proxy=proxy
    604 )
    605 result["quoteSummary"]["result"][0]["symbol"] = self._symbol
    606 query1_info = next(
    607     (info for info in result.get("quoteSummary", {}).get("result", []) if info["symbol"] == self._symbol),
    608     None,
    609 )

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/data.py:395, in YfData.get_raw_json(self, url, user_agent_headers, params, proxy, timeout)
    393 def get_raw_json(self, url, user_agent_headers=None, params=None, proxy=None, timeout=30):
    394     utils.get_yf_logger().debug(f'get_raw_json(): {url}')
--> 395     response = self.get(url, user_agent_headers=user_agent_headers, params=params, proxy=proxy, timeout=timeout)
    396     response.raise_for_status()
    397     return response.json()

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/utils.py:108, in log_indent_decorator.<locals>.wrapper(*args, **kwargs)
    105 logger.debug(f'Entering {func.__name__}()')
    107 with IndentationContext():
--> 108     result = func(*args, **kwargs)
    110 logger.debug(f'Exiting {func.__name__}()')
    111 return result

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/data.py:346, in YfData.get(self, url, user_agent_headers, params, proxy, timeout)
    343 if 'crumb' in params:
    344     raise Exception("Don't manually add 'crumb' to params dict, let data.py handle it")
--> 346 cookie, crumb, strategy = self._get_cookie_and_crumb()
    347 if crumb is not None:
    348     crumbs = {'crumb': crumb}

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/utils.py:108, in log_indent_decorator.<locals>.wrapper(*args, **kwargs)
    105 logger.debug(f'Entering {func.__name__}()')
    107 with IndentationContext():
--> 108     result = func(*args, **kwargs)
    110 logger.debug(f'Exiting {func.__name__}()')
    111 return result

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/data.py:322, in YfData._get_cookie_and_crumb(self, proxy, timeout)
    319         cookie, crumb = self._get_cookie_and_crumb_basic(proxy, timeout)
    320 else:
    321     # Fallback strategy
--> 322     cookie, crumb = self._get_cookie_and_crumb_basic(proxy, timeout)
    323     if cookie is None or crumb is None:
    324         # Fail
    325         self._set_cookie_strategy('csrf', have_lock=True)

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/utils.py:108, in log_indent_decorator.<locals>.wrapper(*args, **kwargs)
    105 logger.debug(f'Entering {func.__name__}()')
    107 with IndentationContext():
--> 108     result = func(*args, **kwargs)
    110 logger.debug(f'Exiting {func.__name__}()')
    111 return result

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/data.py:213, in YfData._get_cookie_and_crumb_basic(self, proxy, timeout)
    211 @utils.log_indent_decorator
    212 def _get_cookie_and_crumb_basic(self, proxy, timeout):
--> 213     cookie = self._get_cookie_basic(proxy, timeout)
    214     crumb = self._get_crumb_basic(proxy, timeout)
    215     return cookie, crumb

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/data.py:157, in YfData._get_cookie_basic(self, proxy, timeout)
    154     utils.get_yf_logger().debug('reusing cookie')
    155     return self._cookie
--> 157 self._cookie = self._load_cookie_basic()
    158 if self._cookie is not None:
    159     return self._cookie

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/data.py:143, in YfData._load_cookie_basic(self)
    142 def _load_cookie_basic(self):
--> 143     cookie_dict = cache.get_cookie_cache().lookup('basic')
    144     if cookie_dict is None:
    145         return None

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/cache.py:362, in _CookieCache.lookup(self, strategy)
    360     data =  _CookieSchema.get(_CookieSchema.strategy == strategy)
    361     cookie = _pkl.loads(data.cookie_bytes)
--> 362     return {'cookie':cookie, 'age':_datetime.datetime.now()-data.fetch_date}
    363 except _CookieSchema.DoesNotExist:
    364     return None

TypeError: unsupported operand type(s) for -: 'datetime.datetime' and 'str'
```

### Simple code that reproduces your problem

See above

### Debug log

```
DEBUG    get_raw_json(): https://query2.finance.yahoo.com/v10/finance/quoteSummary/MSFT
DEBUG    Entering get()
DEBUG     url=https://query2.finance.yahoo.com/v10/finance/quoteSummary/MSFT
DEBUG     params={'modules': 'financialData,quoteType,defaultKeyStatistics,assetProfile,summaryDetail', 'ssl': 'true'}
DEBUG     Entering _get_cookie_and_crumb()
DEBUG      cookie_mode = 'basic'
DEBUG      Entering _get_cookie_and_crumb_basic()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[3], line 3
      1 import yfinance
      2 ticker = yfinance.Ticker("MSFT")
----> 3 ticker.info

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/ticker.py:142, in Ticker.info(self)
    140 @property
    141 def info(self) -> dict:
--> 142     return self.get_info()

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/base.py:1736, in TickerBase.get_info(self, proxy)
   1734 def get_info(self, proxy=None) -> dict:
   1735     self._quote.proxy = proxy
-> 1736     data = self._quote.info
   1737     return data

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/scrapers/quote.py:572, in Quote.info(self)
    569 @property
    570 def info(self) -> dict:
    571     if self._info is None:
--> 572         self._fetch(self.proxy)
    573         self._fetch_complementary(self.proxy)
    575     return self._info

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/scrapers/quote.py:602, in Quote._fetch(self, proxy)
    600 modules = ','.join(modules)
    601 params_dict = {"modules": modules, "ssl": "true"}
--> 602 result = self._data.get_raw_json(
    603     _BASIC_URL_ + f"/{self._symbol}", params=params_dict, proxy=proxy
    604 )
    605 result["quoteSummary"]["result"][0]["symbol"] = self._symbol
    606 query1_info = next(
    607     (info for info in result.get("quoteSummary", {}).get("result", []) if info["symbol"] == self._symbol),
    608     None,
    609 )

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/data.py:395, in YfData.get_raw_json(self, url, user_agent_headers, params, proxy, timeout)
    393 def get_raw_json(self, url, user_agent_headers=None, params=None, proxy=None, timeout=30):
    394     utils.get_yf_logger().debug(f'get_raw_json(): {url}')
--> 395     response = self.get(url, user_agent_headers=user_agent_headers, params=params, proxy=proxy, timeout=timeout)
    396     response.raise_for_status()
    397     return response.json()

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/utils.py:108, in log_indent_decorator.<locals>.wrapper(*args, **kwargs)
    105 logger.debug(f'Entering {func.__name__}()')
    107 with IndentationContext():
--> 108     result = func(*args, **kwargs)
    110 logger.debug(f'Exiting {func.__name__}()')
    111 return result

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/data.py:346, in YfData.get(self, url, user_agent_headers, params, proxy, timeout)
    343 if 'crumb' in params:
    344     raise Exception("Don't manually add 'crumb' to params dict, let data.py handle it")
--> 346 cookie, crumb, strategy = self._get_cookie_and_crumb()
    347 if crumb is not None:
    348     crumbs = {'crumb': crumb}

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/utils.py:108, in log_indent_decorator.<locals>.wrapper(*args, **kwargs)
    105 logger.debug(f'Entering {func.__name__}()')
    107 with IndentationContext():
--> 108     result = func(*args, **kwargs)
    110 logger.debug(f'Exiting {func.__name__}()')
    111 return result

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/data.py:322, in YfData._get_cookie_and_crumb(self, proxy, timeout)
    319         cookie, crumb = self._get_cookie_and_crumb_basic(proxy, timeout)
    320 else:
    321     # Fallback strategy
--> 322     cookie, crumb = self._get_cookie_and_crumb_basic(proxy, timeout)
    323     if cookie is None or crumb is None:
    324         # Fail
    325         self._set_cookie_strategy('csrf', have_lock=True)

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/utils.py:108, in log_indent_decorator.<locals>.wrapper(*args, **kwargs)
    105 logger.debug(f'Entering {func.__name__}()')
    107 with IndentationContext():
--> 108     result = func(*args, **kwargs)
    110 logger.debug(f'Exiting {func.__name__}()')
    111 return result

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/data.py:213, in YfData._get_cookie_and_crumb_basic(self, proxy, timeout)
    211 @utils.log_indent_decorator
    212 def _get_cookie_and_crumb_basic(self, proxy, timeout):
--> 213     cookie = self._get_cookie_basic(proxy, timeout)
    214     crumb = self._get_crumb_basic(proxy, timeout)
    215     return cookie, crumb

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/data.py:157, in YfData._get_cookie_basic(self, proxy, timeout)
    154     utils.get_yf_logger().debug('reusing cookie')
    155     return self._cookie
--> 157 self._cookie = self._load_cookie_basic()
    158 if self._cookie is not None:
    159     return self._cookie

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/data.py:143, in YfData._load_cookie_basic(self)
    142 def _load_cookie_basic(self):
--> 143     cookie_dict = cache.get_cookie_cache().lookup('basic')
    144     if cookie_dict is None:
    145         return None

File /opt/homebrew/Caskroom/mambaforge/base/envs/work/lib/python3.11/site-packages/yfinance/cache.py:362, in _CookieCache.lookup(self, strategy)
    360     data =  _CookieSchema.get(_CookieSchema.strategy == strategy)
    361     cookie = _pkl.loads(data.cookie_bytes)
--> 362     return {'cookie':cookie, 'age':_datetime.datetime.now()-data.fetch_date}
    363 except _CookieSchema.DoesNotExist:
    364     return None

TypeError: unsupported operand type(s) for -: 'datetime.datetime' and 'str'
```

### Bad data proof

_No response_

### `yfinance` version

0.2.32 (pip installed directly from Github GIT repository)

### Python version

3.11.6

### Operating system

MacOS Sonoma 14.1.1


Discussion:
Where are you from? Which country?
France. I could try through proxies if it can help.
Hello,

I have the same error message, when try to use ticker.info functionality. Even after the last update from 6 Dec (0.2.33) the issue still exisit. I am from Bulgaria and do not use VPN, could you give some info does it possible to fix the issues locally using VPN or other option.

@NinoNinov 
try, please:

```
pip uninstall yfinance
pip install yfinance --upgrade --no-cache-dir
```

and remove yfinance.cache if it exists

> and remove yfinance.cache if it exists

I prefer they keep cache and debug the error. What is the string?
2 feedbacks:
- @NinoNinov your pip deinstall/reinstall doesn't improve the situation, same error. I don't use any disk cache (so I assume by default there is none, right?)
- @ValueRaider, the `data.fetch_date` string is `"2023-11-21T11:32:20.866294"` so it represents a valid date but, being a string, python refuses to "add" it to a `datetime.datetime`. I am not familiar with yfinance internals but I assume there is a type issue with the `data.fetch_date` field
Sounds easy to fix. #1084
> > and remove yfinance.cache if it exists
> 
> I prefer they keep cache and debug the error. What is the string?

The script is from public example. Is this bug general or only happen to me and dmoklaf?
```
import yfinance as yf

msft = yf.Ticker("MSFT")

# get all stock info
msft.info

# get historical market data
hist = msft.history(period="1mo")
```

and the issues is:

```
HTTPError                                 Traceback (most recent call last)
Untitled-1.ipynb Cell 1 line 6
      [3](vscode-notebook-cell:Untitled-1.ipynb?jupyter-notebook#W1sdW50aXRsZWQ%3D?line=2) msft = yf.Ticker("MSFT")
      [5](vscode-notebook-cell:Untitled-1.ipynb?jupyter-notebook#W1sdW50aXRsZWQ%3D?line=4) # get all stock info
----> [6](vscode-notebook-cell:Untitled-1.ipynb?jupyter-notebook#W1sdW50aXRsZWQ%3D?line=5) msft.info
      [8](vscode-notebook-cell:Untitled-1.ipynb?jupyter-notebook#W1sdW50aXRsZWQ%3D?line=7) # get historical market data
      [9](vscode-notebook-cell:Untitled-1.ipynb?jupyter-notebook#W1sdW50aXRsZWQ%3D?line=8) hist = msft.history(period="1mo")

File [c:\Users\Nino\anaconda3\lib\site-packages\yfinance\ticker.py:138](file:///C:/Users/Nino/anaconda3/lib/site-packages/yfinance/ticker.py:138), in Ticker.info(self)
    [136](file:///C:/Users/Nino/anaconda3/lib/site-packages/yfinance/ticker.py:136) @property
    [137](file:///C:/Users/Nino/anaconda3/lib/site-packages/yfinance/ticker.py:137) def info(self) -> dict:
--> [138](file:///C:/Users/Nino/anaconda3/lib/site-packages/yfinance/ticker.py:138)     return self.get_info()

File [c:\Users\Nino\anaconda3\lib\site-packages\yfinance\base.py:1501](file:///C:/Users/Nino/anaconda3/lib/site-packages/yfinance/base.py:1501), in TickerBase.get_info(self, proxy)
   [1499](file:///C:/Users/Nino/anaconda3/lib/site-packages/yfinance/base.py:1499) def get_info(self, proxy=None) -> dict:
   [1500](file:///C:/Users/Nino/anaconda3/lib/site-packages/yfinance/base.py:1500)     self._quote.proxy = proxy
-> [1501](file:///C:/Users/Nino/anaconda3/lib/site-packages/yfinance/base.py:1501)     data = self._quote.info
   [1502](file:///C:/Users/Nino/anaconda3/lib/site-packages/yfinance/base.py:1502)     return data

File [c:\Users\Nino\anaconda3\lib\site-packages\yfinance\scrapers\quote.py:562](file:///C:/Users/Nino/anaconda3/lib/site-packages/yfinance/scrapers/quote.py:562), in Quote.info(self)
    [559](file:///C:/Users/Nino/anaconda3/lib/site-packages/yfinance/scrapers/quote.py:559) @property
    [560](file:///C:/Users/Nino/anaconda3/lib/site-packages/yfinance/scrapers/quote.py:560) def info(self) -> dict:
    [561](file:///C:/Users/Nino/anaconda3/lib/site-packages/yfinance/scrapers/quote.py:561)     if self._info is None:
--> [562](file:///C:/Users/Nino/anaconda3/lib/site-packages/yfinance/scrapers/quote.py:562)         self._fetch(self.proxy)
...
   [1018](file:///C:/Users/Nino/anaconda3/lib/site-packages/requests/models.py:1018)     )
   [1020](file:///C:/Users/Nino/anaconda3/lib/site-packages/requests/models.py:1020) if http_error_msg:
-> [1021](file:///C:/Users/Nino/anaconda3/lib/site-packages/requests/models.py:1021)     raise HTTPError(http_error_msg, response=self)

HTTPError: 404 Client Error: Not Found for url: https://query2.finance.yahoo.com/v6/finance/quoteSummary/MSFT?modules=financialData&modules=quoteType&modules=defaultKeyStatistics&modules=assetProfile&modules=summaryDetail&ssl=true
Output is truncated. View as a [scrollable element](command:cellOutput.enableScrolling?63b15f4e-4d30-460c-99b0-76e1dc7db8b2) or open in a [text editor](command:workbench.action.openLargeOutput?63b15f4e-4d30-460c-99b0-76e1dc7db8b2). Adjust cell output [settings](command:workbench.action.openSettings?%5B%22%40tag%3AnotebookOutputLayout%22%5D)...
```
@NinoNinov it's error from old code. Try latest version

![yfinance](https://github.com/ranaroussi/yfinance/assets/32340544/c2837122-01dd-463a-a9d5-b3c553192058)
> @NinoNinov it's error from old code. Try latest version

I tried to updated to last version several time but still the same issue, as you can see from my screenshot made several minutes ago.
[https://i.ibb.co/F4sgLj2/yfinance.jpg](url)
@NinoNinov 
The latest version is 0.2.33, but you have installed 0.2.24
@bot-unit many thanks for support! Now it is working, the issie was that I tried to update with comand:
`pip install yfinance` which was working in the past but is not working now. Instead that used:
`pip install yfinance --upgrade --no-cache-dir ` amd now works.
@dmoklaf try to fix issue with second command 
@NinoNinov I followed your reinstall procedure with the specific flags including `--no-cache-dir` and the bug is still there. I may have time in the next two weeks to dig into these typing issues. The fix seems simple (may be a one liner), what worries me is the other parts of the library that I really don't use and don't know, including the cache whose existence is new to me. And why am I the only one to see this.
@dmoklaf the caching is needed for cookies. Since Yahoo started blocking requests without crumbs, we should obtain cookies, then crumb, and only then make requests.
@dmoklaf Give us the cookie cache database file, I want to understand how error happened. I've updated README to explain where file is.
Here are the cache files. Let me know if I should perform another test with clean (reset) cache files. Even if small, I had to zip them as GitHub doesn't accept .db files
[Archive.zip](https://github.com/ranaroussi/yfinance/files/13702666/Archive.zip)

MacOS 14.2
Python 3.11.6
yfinance 0.2.33


Resolving patch:
diff --git a/yfinance/cache.py b/yfinance/cache.py
--- a/yfinance/cache.py
+++ b/yfinance/cache.py
@@ -197,17 +197,6 @@ def get_tz_cache():
     return _TzCacheManager.get_tz_cache()
 
 
-def set_tz_cache_location(cache_dir: str):
-    """
-    Sets the path to create the "py-yfinance" cache folder in.
-    Useful if the default folder returned by "appdir.user_cache_dir()" is not writable.
-    Must be called before cache is used (that is, before fetching tickers).
-    :param cache_dir: Path to use for caches
-    :return: None
-    """
-    _TzDBManager.set_location(cache_dir)
-
-
 
 # --------------
 # Cookie cache
@@ -300,9 +289,21 @@ def get_location(cls):
 
 
 Cookie_db_proxy = _peewee.Proxy()
+class ISODateTimeField(_peewee.DateTimeField):
+    # Ensure Python datetime is read & written correctly for sqlite, 
+    # because user discovered peewee allowed an invalid datetime
+    # to get written.
+    def db_value(self, value):
+        if value and isinstance(value, _datetime.datetime):
+            return value.isoformat()
+        return super().db_value(value)
+    def python_value(self, value):
+        if value and isinstance(value, str) and 'T' in value:
+            return _datetime.datetime.fromisoformat(value)
+        return super().python_value(value)
 class _CookieSchema(_peewee.Model):
     strategy = _peewee.CharField(primary_key=True)
-    fetch_date = _peewee.DateTimeField(default=_datetime.datetime.now)
+    fetch_date = ISODateTimeField(default=_datetime.datetime.now)
     
     # Which cookie type depends on strategy
     cookie_bytes = _peewee.BlobField()
@@ -398,3 +399,19 @@ def store(self, strategy, cookie):
 def get_cookie_cache():
     return _CookieCacheManager.get_cookie_cache()
 
+
+
+def set_cache_location(cache_dir: str):
+    """
+    Sets the path to create the "py-yfinance" cache folder in.
+    Useful if the default folder returned by "appdir.user_cache_dir()" is not writable.
+    Must be called before cache is used (that is, before fetching tickers).
+    :param cache_dir: Path to use for caches
+    :return: None
+    """
+    _TzDBManager.set_location(cache_dir)
+    _CookieDBManager.set_location(cache_dir)
+
+def set_tz_cache_location(cache_dir: str):
+    set_cache_location(cache_dir)
+


Test patch: