Merge branch 'dev' into fix/download-tz
This commit is contained in:
@@ -1,12 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
|
||||
- package-ecosystem: pip
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
steps:
|
||||
- name: Check if issue uses custom template
|
||||
# Verify: curl -s "https://api.github.com/repos/actions/github-script/commits?sha=v8&until=$(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ')&per_page=1" | jq -r '.[0].sha'
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
|
||||
@@ -16,6 +16,6 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
# Verify: curl -s "https://api.github.com/repos/astral-sh/ruff-action/commits?sha=v3&until=$(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ')&per_page=1" | jq -r '.[0].sha'
|
||||
- uses: astral-sh/ruff-action@4919ec5cf1f49eff0871dbcea0da843445b837e6 # v3
|
||||
- uses: astral-sh/ruff-action@0ce1b0bf8b818ef400413f810f8a11cdbda0034b # v4.0.0
|
||||
with:
|
||||
args: check --ignore E702 . --exclude yfinance/pricing_pb2.py
|
||||
|
||||
@@ -20,6 +20,6 @@ jobs:
|
||||
persist-credentials: false
|
||||
- name: Run zizmor
|
||||
# Verify: curl -s "https://api.github.com/repos/zizmorcore/zizmor-action/commits?sha=v0.5.2&until=$(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ')&per_page=1" | jq -r '.[0].sha'
|
||||
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
||||
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Merge pull request #2802 from dokson/feature/optional-curl-cffi ([#2802]()) (9ca1c55)
|
||||
- Make curl_cffi optional with graceful fallback to requests (5d10c0c)
|
||||
- Merge pull request #2827 from ranaroussi/main ([#2827]()) (628a417)
|
||||
- Merge pull request #2821 from dokson/feature/drop-frozendict-dep ([#2821]()) (0c36060)
|
||||
- Drop frozendict hard dependency in favour of an internal fallback (593b47c)
|
||||
- Merge pull request #2752 from ranaroussi/dependabot/github_actions/astral-sh/ruff-action-4.0.0 ([#2752]()) (a766e8f)
|
||||
- Merge pull request #2754 from ranaroussi/dependabot/github_actions/zizmorcore/zizmor-action-0.5.3 ([#2754]()) (75ed996)
|
||||
- Merge pull request #2753 from ranaroussi/dependabot/github_actions/actions/github-script-9.0.0 ([#2753]()) (803893a)
|
||||
- Merge pull request #2789 from gabrielchangamire-arch/fix-market-api-docs-reference-link ([#2789]()) (a497d4f)
|
||||
- Fix Market API docs reference link (38ad379)
|
||||
- Dependabot: disable no-reason version updates (4c544ca)
|
||||
- Version 1.3.0 (a17e8b1)
|
||||
- Merge pull request #2757 from ranaroussi/dev ([#2757]()) (d6afeeb)
|
||||
- Bump zizmorcore/zizmor-action from 0.5.2 to 0.5.3 (6cebd23)
|
||||
- Bump actions/github-script from 8.0.0 to 9.0.0 (a07bf32)
|
||||
- Bump astral-sh/ruff-action from 3.6.1 to 4.0.0 (cb9884c)
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
Change Log
|
||||
===========
|
||||
|
||||
1.3.0
|
||||
-----
|
||||
Features:
|
||||
- Add Valuations Measures Table from Statistics Page #2742
|
||||
- Add ETFQuery #2749
|
||||
Fix type regresssion in Ticker.dividends
|
||||
|
||||
1.2.2
|
||||
-----
|
||||
Forgot to merge in #2695 and #2718 for release 1.2.1
|
||||
|
||||
@@ -53,6 +53,8 @@ Install `yfinance` from PYPI using `pip`:
|
||||
$ pip install yfinance
|
||||
```
|
||||
|
||||
To install without `curl_cffi` for requests fallback, see [Advanced ▸ Installation](https://ranaroussi.github.io/yfinance/advanced/install.html).
|
||||
|
||||
### [yfinance relies on the community to investigate bugs and contribute code. Here's how you can help.](https://github.com/ranaroussi/yfinance/blob/main/CONTRIBUTING.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -5,6 +5,7 @@ Advanced
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
install
|
||||
logging
|
||||
config
|
||||
caching
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
To install without ``curl_cffi`` for requests fallback:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl -fsSL https://raw.githubusercontent.com/ranaroussi/yfinance/main/requirements.txt | grep -vi '^curl_cffi' | pip install -r /dev/stdin
|
||||
pip install --no-deps yfinance
|
||||
@@ -15,7 +15,7 @@ The following are the publicly available classes, and functions exposed by the `
|
||||
|
||||
- :attr:`Ticker <yfinance.Ticker>`: Class for accessing single ticker data.
|
||||
- :attr:`Tickers <yfinance.Tickers>`: Class for handling multiple tickers.
|
||||
- :attr:`Market <yfinance.Market>`: Class for accessing market summary.
|
||||
- :doc:`Market <yfinance.market>`: Class for accessing market summary.
|
||||
- :attr:`Calendars <yfinance.Calendars>`: Class for accessing calendar events data.
|
||||
- :attr:`download <yfinance.download>`: Function to download market data for multiple tickers.
|
||||
- :attr:`Search <yfinance.Search>`: Class for accessing search results.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% set name = "yfinance" %}
|
||||
{% set version = "1.2.2" %}
|
||||
{% set version = "1.3.0" %}
|
||||
|
||||
package:
|
||||
name: "{{ name|lower }}"
|
||||
|
||||
@@ -4,7 +4,6 @@ requests>=2.31
|
||||
multitasking>=0.0.7
|
||||
platformdirs>=2.0.0
|
||||
pytz>=2022.5
|
||||
frozendict>=2.3.4
|
||||
beautifulsoup4>=4.11.1
|
||||
lxml>=4.9.0
|
||||
peewee>=3.16.2
|
||||
|
||||
@@ -62,7 +62,7 @@ setup(
|
||||
install_requires=['pandas>=1.3.0', 'numpy>=1.16.5',
|
||||
'requests>=2.31', 'multitasking>=0.0.7',
|
||||
'platformdirs>=2.0.0', 'pytz>=2022.5',
|
||||
'frozendict>=2.3.4', 'peewee>=3.16.2',
|
||||
'peewee>=3.16.2',
|
||||
'beautifulsoup4>=4.11.1', 'curl_cffi>=0.15',
|
||||
'protobuf>=3.19.0', 'websockets>=13.0'],
|
||||
extras_require={
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
"""Tests for yfinance.data internal helpers."""
|
||||
import unittest
|
||||
from functools import lru_cache
|
||||
|
||||
from yfinance.data import lru_cache_freezeargs
|
||||
from yfinance.utils import frozendict
|
||||
|
||||
|
||||
class TestFrozenDict(unittest.TestCase):
|
||||
def test_is_dict_subclass(self):
|
||||
d = frozendict({"a": 1, "b": 2})
|
||||
self.assertEqual(d["a"], 1)
|
||||
self.assertEqual(d.get("b"), 2)
|
||||
self.assertEqual(set(d.keys()), {"a", "b"})
|
||||
self.assertIsInstance(d, dict)
|
||||
|
||||
def test_is_hashable(self):
|
||||
d1 = frozendict({"a": 1, "b": 2})
|
||||
d2 = frozendict({"b": 2, "a": 1})
|
||||
self.assertEqual(hash(d1), hash(d2))
|
||||
self.assertEqual(len({d1, d2}), 1)
|
||||
|
||||
def test_mutation_raises(self):
|
||||
d = frozendict({"a": 1})
|
||||
with self.assertRaises(TypeError):
|
||||
d["b"] = 2
|
||||
with self.assertRaises(TypeError):
|
||||
del d["a"]
|
||||
with self.assertRaises(AttributeError):
|
||||
d.pop("a")
|
||||
with self.assertRaises(AttributeError):
|
||||
d.clear()
|
||||
with self.assertRaises(AttributeError):
|
||||
d.update({"x": 9})
|
||||
|
||||
def test_lru_cache_integration(self):
|
||||
call_count = {"n": 0}
|
||||
|
||||
@lru_cache_freezeargs
|
||||
@lru_cache(maxsize=8)
|
||||
def fn(params):
|
||||
call_count["n"] += 1
|
||||
return sum(params.values())
|
||||
|
||||
self.assertEqual(fn({"a": 1, "b": 2}), 3)
|
||||
self.assertEqual(fn({"a": 1, "b": 2}), 3)
|
||||
self.assertEqual(call_count["n"], 1)
|
||||
self.assertEqual(fn({"a": 1, "b": 3}), 4)
|
||||
self.assertEqual(call_count["n"], 2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,75 @@
|
||||
"""Tests for the HTTP backend abstraction.
|
||||
|
||||
Verifies the fallback to plain ``requests`` when ``curl_cffi`` is
|
||||
unavailable, and the ``YF_DISABLE_CURL_CFFI`` env-var opt-out.
|
||||
"""
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
def _reload_http(curl_cffi_available: bool, disable_env: bool):
|
||||
"""Reload ``yfinance._http`` simulating curl_cffi presence / opt-out."""
|
||||
saved_modules = {k: sys.modules[k] for k in list(sys.modules) if k == "yfinance._http"}
|
||||
saved_env = os.environ.get("YF_DISABLE_CURL_CFFI")
|
||||
if disable_env:
|
||||
os.environ["YF_DISABLE_CURL_CFFI"] = "1"
|
||||
elif "YF_DISABLE_CURL_CFFI" in os.environ:
|
||||
del os.environ["YF_DISABLE_CURL_CFFI"]
|
||||
|
||||
saved_curl = sys.modules.get("curl_cffi")
|
||||
if not curl_cffi_available:
|
||||
sys.modules["curl_cffi"] = None # type: ignore[assignment]
|
||||
|
||||
try:
|
||||
sys.modules.pop("yfinance._http", None)
|
||||
return importlib.import_module("yfinance._http")
|
||||
finally:
|
||||
# Restore originals so other tests aren't affected.
|
||||
if saved_curl is not None:
|
||||
sys.modules["curl_cffi"] = saved_curl
|
||||
elif not curl_cffi_available:
|
||||
sys.modules.pop("curl_cffi", None)
|
||||
if saved_env is None:
|
||||
os.environ.pop("YF_DISABLE_CURL_CFFI", None)
|
||||
else:
|
||||
os.environ["YF_DISABLE_CURL_CFFI"] = saved_env
|
||||
sys.modules.pop("yfinance._http", None)
|
||||
for k, v in saved_modules.items():
|
||||
sys.modules[k] = v
|
||||
|
||||
|
||||
class TestHttpBackend(unittest.TestCase):
|
||||
|
||||
def test_fallback_when_curl_cffi_missing(self):
|
||||
import requests as _stdlib_requests
|
||||
mod = _reload_http(curl_cffi_available=False, disable_env=False)
|
||||
self.assertFalse(mod.HAS_CURL_CFFI)
|
||||
session = mod.new_session()
|
||||
self.assertIsInstance(session, _stdlib_requests.Session)
|
||||
self.assertIn("Chrome", session.headers["User-Agent"])
|
||||
|
||||
def test_disable_env_var_forces_fallback(self):
|
||||
import requests as _stdlib_requests
|
||||
mod = _reload_http(curl_cffi_available=True, disable_env=True)
|
||||
self.assertFalse(mod.HAS_CURL_CFFI)
|
||||
self.assertIsInstance(mod.new_session(), _stdlib_requests.Session)
|
||||
|
||||
def test_cookie_jar_works_for_requests_session(self):
|
||||
mod = _reload_http(curl_cffi_available=False, disable_env=False)
|
||||
session = mod.new_session()
|
||||
jar = mod.cookie_jar(session)
|
||||
# requests.Session.cookies subclasses CookieJar directly.
|
||||
from http.cookiejar import CookieJar
|
||||
self.assertIsInstance(jar, CookieJar)
|
||||
|
||||
def test_is_supported_session_accepts_requests_session(self):
|
||||
import requests as _stdlib_requests
|
||||
mod = _reload_http(curl_cffi_available=False, disable_env=False)
|
||||
self.assertTrue(mod.is_supported_session(_stdlib_requests.Session()))
|
||||
self.assertFalse(mod.is_supported_session(object()))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,101 @@
|
||||
"""HTTP backend abstraction.
|
||||
|
||||
Prefers ``curl_cffi`` for browser TLS impersonation. Falls back to plain
|
||||
``requests`` with a realistic ``User-Agent`` when ``curl_cffi`` cannot be
|
||||
imported (e.g. binary not buildable on the host platform — see issue #2692)
|
||||
or when ``YF_DISABLE_CURL_CFFI`` is set in the environment (downstream
|
||||
packagers may ship without ``curl_cffi`` even if it is installed).
|
||||
|
||||
The fallback is best-effort: plain ``requests`` cannot replicate the
|
||||
JA3/JA4 fingerprint and HTTP/2 settings that ``curl_cffi`` provides, so
|
||||
Yahoo Finance may rate-limit or block this client. ``curl_cffi`` remains
|
||||
the preferred backend and the default install dependency.
|
||||
"""
|
||||
import functools
|
||||
import os
|
||||
|
||||
from . import utils
|
||||
|
||||
_DISABLE = os.environ.get("YF_DISABLE_CURL_CFFI", "").lower() in ("1", "true", "yes")
|
||||
|
||||
if not _DISABLE:
|
||||
try:
|
||||
from curl_cffi import requests as _curl_backend
|
||||
_backend = _curl_backend
|
||||
HAS_CURL_CFFI = True
|
||||
except ImportError:
|
||||
import requests as _requests_backend
|
||||
_backend = _requests_backend
|
||||
HAS_CURL_CFFI = False
|
||||
else:
|
||||
import requests as _requests_backend
|
||||
_backend = _requests_backend
|
||||
HAS_CURL_CFFI = False
|
||||
|
||||
requests = _backend
|
||||
HTTPError = _backend.exceptions.HTTPError
|
||||
|
||||
_FALLBACK_USER_AGENT = (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
||||
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
)
|
||||
|
||||
_fallback_warned = False
|
||||
|
||||
|
||||
def _warn_once_on_fallback():
|
||||
global _fallback_warned
|
||||
if HAS_CURL_CFFI or _fallback_warned:
|
||||
return
|
||||
_fallback_warned = True
|
||||
utils.get_yf_logger().warning(
|
||||
"curl_cffi not available; falling back to requests without browser TLS "
|
||||
"impersonation. Yahoo Finance may rate-limit or block this client. "
|
||||
"Install curl_cffi (>=0.15) for the supported configuration."
|
||||
)
|
||||
|
||||
|
||||
def new_session():
|
||||
"""Create a default Session for the active backend."""
|
||||
if HAS_CURL_CFFI:
|
||||
return _backend.Session(impersonate="chrome")
|
||||
_warn_once_on_fallback()
|
||||
s = _backend.Session()
|
||||
s.headers.update({
|
||||
"User-Agent": _FALLBACK_USER_AGENT,
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
})
|
||||
return s
|
||||
|
||||
|
||||
def cookie_jar(session):
|
||||
"""Return the underlying ``http.cookiejar.CookieJar`` for either backend.
|
||||
|
||||
``curl_cffi`` exposes the jar as ``session.cookies.jar``; ``requests``
|
||||
uses ``session.cookies`` directly (it subclasses ``CookieJar``).
|
||||
"""
|
||||
cookies = session.cookies
|
||||
return getattr(cookies, "jar", cookies)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def _supported_session_classes() -> tuple:
|
||||
classes = []
|
||||
try:
|
||||
from curl_cffi.requests.session import Session as _CurlSession
|
||||
classes.append(_CurlSession)
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
from requests.sessions import Session as _ReqSession
|
||||
classes.append(_ReqSession)
|
||||
except ImportError:
|
||||
pass
|
||||
return tuple(classes)
|
||||
|
||||
|
||||
def is_supported_session(obj) -> bool:
|
||||
"""True if ``obj`` is a Session from either supported backend."""
|
||||
classes = _supported_session_classes()
|
||||
return bool(classes) and isinstance(obj, classes)
|
||||
+2
-2
@@ -27,7 +27,7 @@ from urllib.parse import quote as urlencode
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from curl_cffi import requests
|
||||
from ._http import requests, new_session
|
||||
|
||||
|
||||
from . import utils, cache
|
||||
@@ -81,7 +81,7 @@ class TickerBase:
|
||||
ticker = base_symbol
|
||||
|
||||
self.ticker = ticker.upper()
|
||||
self.session = session or requests.Session(impersonate="chrome")
|
||||
self.session = session or new_session()
|
||||
self._tz = None
|
||||
|
||||
self._isin = None
|
||||
|
||||
+10
-10
@@ -4,14 +4,13 @@ import socket
|
||||
import time as _time
|
||||
import json as _json
|
||||
|
||||
from curl_cffi import requests
|
||||
from ._http import requests, new_session, is_supported_session, cookie_jar
|
||||
from urllib.parse import urlsplit, urljoin
|
||||
from bs4 import BeautifulSoup
|
||||
import datetime
|
||||
|
||||
from frozendict import frozendict
|
||||
|
||||
from . import utils, cache
|
||||
from .utils import frozendict
|
||||
from .config import YfConfig
|
||||
import threading
|
||||
|
||||
@@ -91,7 +90,7 @@ class YfData(metaclass=SingletonMeta):
|
||||
self._cookie_lock = threading.Lock()
|
||||
|
||||
self._session = None
|
||||
self._set_session(session or requests.Session(impersonate="chrome"))
|
||||
self._set_session(session or new_session())
|
||||
|
||||
def set_login_cookies(self, cookie_t, cookie_y):
|
||||
with self._cookie_lock:
|
||||
@@ -115,11 +114,12 @@ class YfData(metaclass=SingletonMeta):
|
||||
# Can't simply use a non-caching session to fetch cookie & crumb,
|
||||
# because then the caching-session won't have cookie.
|
||||
self._session_is_caching = True
|
||||
# But since switch to curl_cffi, can't use requests_cache with it.
|
||||
raise YFDataException("request_cache sessions don't work with curl_cffi, which is necessary now for Yahoo API. Solution: stop setting session, let YF handle.")
|
||||
# requests_cache wraps the stdlib Session; it doesn't work with
|
||||
# curl_cffi and tends to miss anyway because the Yahoo crumb rotates.
|
||||
raise YFDataException("Caching sessions (e.g. requests_cache) are not supported. Solution: stop setting session, let yfinance handle.")
|
||||
|
||||
if not isinstance(session, requests.session.Session):
|
||||
raise YFDataException(f"Yahoo API requires curl_cffi session not {type(session)}. Solution: stop setting session, let YF handle.")
|
||||
if not is_supported_session(session):
|
||||
raise YFDataException(f"Unsupported session type {type(session)}; expected curl_cffi or requests Session. Solution: stop setting session, let yfinance handle.")
|
||||
|
||||
with self._cookie_lock:
|
||||
self._session = session
|
||||
@@ -153,7 +153,7 @@ class YfData(metaclass=SingletonMeta):
|
||||
def _save_cookie_curlCffi(self):
|
||||
if self._session is None:
|
||||
return False
|
||||
cookies = self._session.cookies.jar._cookies
|
||||
cookies = cookie_jar(self._session)._cookies
|
||||
if len(cookies) == 0:
|
||||
return False
|
||||
yh_domains = [k for k in cookies.keys() if 'yahoo' in k]
|
||||
@@ -189,7 +189,7 @@ class YfData(metaclass=SingletonMeta):
|
||||
if expired:
|
||||
utils.get_yf_logger().debug('cached cookie expired')
|
||||
return False
|
||||
self._session.cookies.jar._cookies.update(cookies)
|
||||
cookie_jar(self._session)._cookies.update(cookies)
|
||||
self._cookie = cookie
|
||||
return True
|
||||
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@ from typing import Union
|
||||
import multitasking as _multitasking
|
||||
import pandas as _pd
|
||||
import numpy as _np
|
||||
from curl_cffi import requests
|
||||
from ._http import new_session
|
||||
|
||||
from . import Ticker, utils
|
||||
from .data import YfData
|
||||
@@ -123,7 +123,7 @@ def _download_impl(ctx, tickers, start=None, end=None, actions=False, threads=Tr
|
||||
prepost=False, rounding=False, timeout=10, session=None,
|
||||
multi_level_index=True):
|
||||
logger = utils.get_yf_logger()
|
||||
session = session or requests.Session(impersonate="chrome")
|
||||
session = session or new_session()
|
||||
|
||||
YfData(session=session)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import curl_cffi
|
||||
from yfinance._http import HTTPError
|
||||
import pandas as pd
|
||||
|
||||
from yfinance import utils
|
||||
@@ -187,7 +187,7 @@ class Analysis:
|
||||
params_dict = {"modules": modules, "corsDomain": "finance.yahoo.com", "formatted": "false", "symbol": self._symbol}
|
||||
try:
|
||||
result = self._data.get_raw_json(_QUOTE_SUMMARY_URL_ + f"/{self._symbol}", params=params_dict)
|
||||
except curl_cffi.requests.exceptions.HTTPError as e:
|
||||
except HTTPError as e:
|
||||
if not YfConfig.debug.hide_exceptions:
|
||||
raise
|
||||
utils.get_yf_logger().error(str(e) + e.response.text)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from curl_cffi import requests
|
||||
from yfinance._http import new_session
|
||||
from math import isclose
|
||||
import bisect
|
||||
import datetime as _datetime
|
||||
@@ -19,7 +19,7 @@ class PriceHistory:
|
||||
self._data = data
|
||||
self.ticker = ticker.upper()
|
||||
self.tz = tz
|
||||
self.session = session or requests.Session(impersonate="chrome")
|
||||
self.session = session or new_session()
|
||||
|
||||
self._history_cache = {}
|
||||
self._history_metadata = None
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import curl_cffi
|
||||
from yfinance._http import HTTPError
|
||||
import pandas as pd
|
||||
|
||||
from yfinance import utils
|
||||
@@ -71,7 +71,7 @@ class Holders:
|
||||
def _fetch_and_parse(self):
|
||||
try:
|
||||
result = self._fetch()
|
||||
except curl_cffi.requests.exceptions.HTTPError as e:
|
||||
except HTTPError as e:
|
||||
if not YfConfig.debug.hide_exceptions:
|
||||
raise
|
||||
utils.get_yf_logger().error(str(e) + e.response.text)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import curl_cffi
|
||||
from yfinance._http import HTTPError
|
||||
import datetime
|
||||
import json
|
||||
import numpy as _np
|
||||
@@ -594,7 +594,7 @@ class Quote:
|
||||
params_dict = {"modules": modules, "corsDomain": "finance.yahoo.com", "formatted": "false", "symbol": self._symbol, "lang": YfConfig.locale.lang, "region": YfConfig.locale.region}
|
||||
try:
|
||||
result = self._data.get_raw_json(_QUOTE_SUMMARY_URL_ + f"/{self._symbol}", params=params_dict)
|
||||
except curl_cffi.requests.exceptions.HTTPError as e:
|
||||
except HTTPError as e:
|
||||
if not YfConfig.debug.hide_exceptions:
|
||||
raise
|
||||
utils.get_yf_logger().error(str(e) + e.response.text)
|
||||
@@ -605,7 +605,7 @@ class Quote:
|
||||
params_dict = {"symbols": self._symbol, "formatted": "false", "lang": YfConfig.locale.lang, "region": YfConfig.locale.region}
|
||||
try:
|
||||
result = self._data.get_raw_json(f"{_QUERY1_URL_}/v7/finance/quote?", params=params_dict)
|
||||
except curl_cffi.requests.exceptions.HTTPError as e:
|
||||
except HTTPError as e:
|
||||
if not YfConfig.debug.hide_exceptions:
|
||||
raise
|
||||
utils.get_yf_logger().error(str(e) + e.response.text)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import curl_cffi
|
||||
from yfinance._http import HTTPError
|
||||
from typing import Union
|
||||
import warnings
|
||||
from json import dumps
|
||||
@@ -176,7 +176,7 @@ def screen(query: Union[str, EquityQuery, FundQuery, ETFQuery],
|
||||
resp = _data.get(url=_PREDEFINED_URL_, params=params_dict)
|
||||
try:
|
||||
resp.raise_for_status()
|
||||
except curl_cffi.requests.exceptions.HTTPError:
|
||||
except HTTPError:
|
||||
if query not in PREDEFINED_SCREENER_QUERIES:
|
||||
print(f"yfinance.screen: '{query}' is probably not a predefined query.")
|
||||
raise
|
||||
|
||||
@@ -43,6 +43,34 @@ from yfinance import const
|
||||
from yfinance.exceptions import YFException
|
||||
from yfinance.config import YfConfig
|
||||
|
||||
# Use the third-party ``frozendict`` package if installed; otherwise fall
|
||||
# back to a small pure-Python equivalent (PEP 814).
|
||||
try:
|
||||
from frozendict import frozendict # type: ignore[import-not-found]
|
||||
except ImportError:
|
||||
class frozendict(dict): # type: ignore[no-redef]
|
||||
"""Hashable, read-only ``dict`` used as an ``lru_cache`` key."""
|
||||
__slots__ = ()
|
||||
|
||||
def __hash__(self): # type: ignore[override]
|
||||
return hash(frozenset(self.items()))
|
||||
|
||||
def __setitem__(self, *args, **kwargs):
|
||||
raise TypeError(f"'{type(self).__name__}' object doesn't support item assignment")
|
||||
|
||||
def __delitem__(self, *args, **kwargs):
|
||||
raise TypeError(f"'{type(self).__name__}' object doesn't support item deletion")
|
||||
|
||||
def _readonly(self, *args, **kwargs):
|
||||
raise AttributeError(f"'{type(self).__name__}' object is read-only")
|
||||
|
||||
pop = _readonly # type: ignore[assignment]
|
||||
popitem = _readonly # type: ignore[assignment]
|
||||
clear = _readonly # type: ignore[assignment]
|
||||
update = _readonly # type: ignore[assignment]
|
||||
setdefault = _readonly # type: ignore[assignment]
|
||||
|
||||
|
||||
# From https://stackoverflow.com/a/59128615
|
||||
def attributes(obj):
|
||||
disallowed_names = {
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
version = "1.2.2"
|
||||
version = "1.3.0"
|
||||
|
||||
Reference in New Issue
Block a user