Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion yfinance/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from .domain.industry import Industry
from .domain.market import Market
from .data import YfData
from .config import YfConfig

from .screener.query import EquityQuery, FundQuery
from .screener.screener import screen, PREDEFINED_SCREENER_QUERIES
Expand All @@ -48,7 +49,9 @@

# Config stuff:
_NOTSET=object()
def set_config(proxy=_NOTSET):
def set_config(proxy=_NOTSET, hide_exceptions=_NOTSET):
if proxy is not _NOTSET:
YfData(proxy=proxy)
if hide_exceptions is not _NOTSET:
YfConfig(hide_exceptions=hide_exceptions)
__all__ += ["set_config"]
19 changes: 15 additions & 4 deletions yfinance/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@

from . import utils, cache
from .data import YfData
from .exceptions import YFEarningsDateMissing, YFRateLimitError
from .config import YfConfig
from .exceptions import YFDataException, YFEarningsDateMissing, YFRateLimitError
from .live import WebSocket
from .scrapers.analysis import Analysis
from .scrapers.fundamentals import Fundamentals
Expand Down Expand Up @@ -157,6 +158,8 @@ def _fetch_ticker_tz(self, timeout):
# Must propagate this
raise
except Exception as e:
if not YfConfig().hide_exceptions:
raise
logger.error(f"Failed to get ticker '{self.ticker}' reason: {e}")
return None
else:
Expand All @@ -168,6 +171,8 @@ def _fetch_ticker_tz(self, timeout):
try:
return data["chart"]["result"][0]["meta"]["exchangeTimezoneName"]
except Exception as err:
if not YfConfig().hide_exceptions:
raise
logger.error(f"Could not get exchangeTimezoneName for ticker '{self.ticker}' reason: {err}")
logger.debug("Got response: ")
logger.debug("-------------")
Expand Down Expand Up @@ -599,13 +604,17 @@ def get_shares_full(self, start=None, end=None, proxy=_SENTINEL_):
json_data = self._data.cache_get(url=shares_url)
json_data = json_data.json()
except (_json.JSONDecodeError, requests.exceptions.RequestException):
if not YfConfig().hide_exceptions:
raise
logger.error(f"{self.ticker}: Yahoo web request for share count failed")
return None
try:
fail = json_data["finance"]["error"]["code"] == "Bad Request"
except KeyError:
fail = False
if fail:
if not YfConfig().hide_exceptions:
raise requests.exceptions.HTTPError("Yahoo web request for share count returned 'Bad Request'")
logger.error(f"{self.ticker}: Yahoo web request for share count failed")
return None

Expand All @@ -615,6 +624,8 @@ def get_shares_full(self, start=None, end=None, proxy=_SENTINEL_):
try:
df = pd.Series(shares_data[0]["shares_out"], index=pd.to_datetime(shares_data[0]["timestamp"], unit="s"))
except Exception as e:
if not YfConfig().hide_exceptions:
raise
logger.error(f"{self.ticker}: Failed to parse shares count data: {e}")
return None

Expand Down Expand Up @@ -693,12 +704,12 @@ def get_news(self, count=10, tab="news", proxy=_SENTINEL_) -> list:

data = self._data.post(url, body=payload)
if data is None or "Will be right back" in data.text:
raise RuntimeError("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***\n"
"Our engineers are working quickly to resolve "
"the issue. Thank you for your patience.")
raise YFDataException("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***")
try:
data = data.json()
except _json.JSONDecodeError:
if not YfConfig().hide_exceptions:
raise
logger.error(f"{self.ticker}: Failed to retrieve the news and received faulty response instead.")
data = {}

Expand Down
10 changes: 6 additions & 4 deletions yfinance/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from . import utils, cache
import threading

from .exceptions import YFRateLimitError, YFDataException
from .exceptions import YFException, YFDataException, YFRateLimitError

cache_maxsize = 64

Expand Down Expand Up @@ -197,8 +197,10 @@ def _get_cookie_basic(self, timeout=30):
url='https://fc.yahoo.com',
timeout=timeout,
allow_redirects=True)
except requests.exceptions.DNSError:
# Possible because url on some privacy/ad blocklists
except requests.exceptions.DNSError as e:
# Possible because url on some privacy/ad blocklists.
# Can ignore because have second strategy.
utils.get_yf_logger().debug("Handling DNS error on cookie fetch: " + str(e))
return False
self._save_cookie_curlCffi()
return True
Expand Down Expand Up @@ -386,7 +388,7 @@ def _make_request(self, url, request_method, body=None, params=None, timeout=30)
if params is None:
params = {}
if 'crumb' in params:
raise Exception("Don't manually add 'crumb' to params dict, let data.py handle it")
raise YFException("Don't manually add 'crumb' to params dict, let data.py handle it")

crumb, strategy = self._get_cookie_and_crumb()
if crumb is not None:
Expand Down
3 changes: 3 additions & 0 deletions yfinance/domain/industry.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import warnings

from .. import utils
from ..config import YfConfig
from ..const import _SENTINEL_
from ..data import YfData

Expand Down Expand Up @@ -147,6 +148,8 @@ def _fetch_and_parse(self) -> None:

return result
except Exception as e:
if not YfConfig().hide_exceptions:
raise
logger = utils.get_yf_logger()
logger.error(f"Failed to get industry data for '{self._key}' reason: {e}")
logger.debug("Got response: ")
Expand Down
32 changes: 20 additions & 12 deletions yfinance/domain/market.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
import json as _json
import warnings

from ..config import YfConfig
from ..const import _QUERY1_URL_, _SENTINEL_
from ..data import utils, YfData
from ..exceptions import YFDataException

class Market:
def __init__(self, market:'str', session=None, proxy=_SENTINEL_, timeout=30):
Expand All @@ -24,12 +26,12 @@ def __init__(self, market:'str', session=None, proxy=_SENTINEL_, timeout=30):
def _fetch_json(self, url, params):
data = self._data.cache_get(url=url, params=params, timeout=self.timeout)
if data is None or "Will be right back" in data.text:
raise RuntimeError("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***\n"
"Our engineers are working quickly to resolve "
"the issue. Thank you for your patience.")
raise YFDataException("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***")
try:
return data.json()
except _json.JSONDecodeError:
if not YfConfig().hide_exceptions:
raise
self._logger.error(f"{self.market}: Failed to retrieve market data and recieved faulty data.")
return {}

Expand Down Expand Up @@ -66,6 +68,8 @@ def _parse_data(self):
self._summary = self._summary['marketSummaryResponse']['result']
self._summary = {x['exchange']:x for x in self._summary}
except Exception as e:
if not YfConfig().hide_exceptions:
raise
self._logger.error(f"{self.market}: Failed to parse market summary")
self._logger.debug(f"{type(e)}: {e}")

Expand All @@ -75,18 +79,22 @@ def _parse_data(self):
self._status = self._status['finance']['marketTimes'][0]['marketTime'][0]
self._status['timezone'] = self._status['timezone'][0]
del self._status['time'] # redundant
try:
self._status.update({
"open": dt.datetime.fromisoformat(self._status["open"]),
"close": dt.datetime.fromisoformat(self._status["close"]),
"tz": dt.timezone(dt.timedelta(hours=int(self._status["timezone"]["gmtoffset"]))/1000, self._status["timezone"]["short"])
})
except Exception as e:
self._logger.error(f"{self.market}: Failed to update market status")
self._logger.debug(f"{type(e)}: {e}")
except Exception as e:
if not YfConfig().hide_exceptions:
raise
self._logger.error(f"{self.market}: Failed to parse market status")
self._logger.debug(f"{type(e)}: {e}")
try:
self._status.update({
"open": dt.datetime.fromisoformat(self._status["open"]),
"close": dt.datetime.fromisoformat(self._status["close"]),
"tz": dt.timezone(dt.timedelta(hours=int(self._status["timezone"]["gmtoffset"]))/1000, self._status["timezone"]["short"])
})
except Exception as e:
if not YfConfig().hide_exceptions:
raise
self._logger.error(f"{self.market}: Failed to update market status")
self._logger.debug(f"{type(e)}: {e}")



Expand Down
3 changes: 3 additions & 0 deletions yfinance/domain/sector.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Dict, Optional
import warnings

from ..config import YfConfig
from ..const import SECTOR_INDUSTY_MAPPING, _SENTINEL_
from ..data import YfData
from ..utils import dynamic_docstring, generate_list_table_from_dict, get_yf_logger
Expand Down Expand Up @@ -148,6 +149,8 @@ def _fetch_and_parse(self) -> None:
self._industries = self._parse_industries(data.get('industries', {}))

except Exception as e:
if not YfConfig().hide_exceptions:
raise
logger = get_yf_logger()
logger.error(f"Failed to get sector data for '{self._key}' reason: {e}")
logger.debug("Got response: ")
Expand Down
15 changes: 15 additions & 0 deletions yfinance/live.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from websockets.asyncio.client import connect as async_connect

from yfinance import utils
from yfinance.config import YfConfig
from yfinance.pricing_pb2 import PricingData
from google.protobuf.json_format import MessageToDict

Expand All @@ -27,6 +28,8 @@ def _decode_message(self, base64_message: str) -> dict:
pricing_data.ParseFromString(decoded_bytes)
return MessageToDict(pricing_data, preserving_proto_field_name=True)
except Exception as e:
if not YfConfig().hide_exceptions:
raise
self.logger.error("Failed to decode message: %s", e, exc_info=True)
if self.verbose:
print("Failed to decode message: %s", e)
Expand Down Expand Up @@ -61,6 +64,8 @@ async def _connect(self):
if self.verbose:
print("Connected to WebSocket.")
except Exception as e:
if not YfConfig().hide_exceptions:
raise
self.logger.error("Failed to connect to WebSocket: %s", e, exc_info=True)
if self.verbose:
print(f"Failed to connect to WebSocket: {e}")
Expand All @@ -79,6 +84,8 @@ async def _periodic_subscribe(self):
if self.verbose:
print(f"Heartbeat subscription sent for symbols: {self._subscriptions}")
except Exception as e:
if not YfConfig().hide_exceptions:
raise
self.logger.error("Error in heartbeat subscription: %s", e, exc_info=True)
if self.verbose:
print(f"Error in heartbeat subscription: {e}")
Expand Down Expand Up @@ -162,6 +169,8 @@ async def listen(self, message_handler=None):
else:
self._message_handler(decoded_message)
except Exception as handler_exception:
if not YfConfig().hide_exceptions:
raise
self.logger.error("Error in message handler: %s", handler_exception, exc_info=True)
if self.verbose:
print("Error in message handler:", handler_exception)
Expand All @@ -176,6 +185,8 @@ async def listen(self, message_handler=None):
break

except Exception as e:
if not YfConfig().hide_exceptions:
raise
self.logger.error("Error while listening to messages: %s", e, exc_info=True)
if self.verbose:
print("Error while listening to messages: %s", e)
Expand Down Expand Up @@ -301,6 +312,8 @@ def listen(self, message_handler: Optional[Callable[[dict], None]] = None):
try:
message_handler(decoded_message)
except Exception as handler_exception:
if not YfConfig().hide_exceptions:
raise
self.logger.error("Error in message handler: %s", handler_exception, exc_info=True)
if self.verbose:
print("Error in message handler:", handler_exception)
Expand All @@ -314,6 +327,8 @@ def listen(self, message_handler: Optional[Callable[[dict], None]] = None):
break

except Exception as e:
if not YfConfig().hide_exceptions:
raise
self.logger.error("Error while listening to messages: %s", e, exc_info=True)
if self.verbose:
print("Error while listening to messages: %s", e)
Expand Down
14 changes: 8 additions & 6 deletions yfinance/lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@
import warnings

from . import utils
from .config import YfConfig
from .const import _QUERY1_URL_, _SENTINEL_
from .data import YfData
from .exceptions import YFException
from .exceptions import YFDataException

LOOKUP_TYPES = ["all", "equity", "mutualfund", "etf", "index", "future", "currency", "cryptocurrency"]

Expand Down Expand Up @@ -81,18 +82,19 @@ def _fetch_lookup(self, lookup_type="all", count=25) -> dict:

data = self._data.get(url=url, params=params, timeout=self.timeout)
if data is None or "Will be right back" in data.text:
raise RuntimeError("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***\n"
"Our engineers are working quickly to resolve "
"the issue. Thank you for your patience.")
raise YFDataException("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***")
try:
data = data.json()
except _json.JSONDecodeError:
self._logger.error(f"{self.query}: Failed to retrieve lookup results and received faulty response instead.")
if not YfConfig().hide_exceptions:
raise
self._logger.error(f"{self.ticker}: 'lookup' fetch received faulty data")
data = {}

# Error returned
if data.get("finance", {}).get("error", {}):
raise YFException(data.get("finance", {}).get("error", {}))
error = data.get("finance", {}).get("error", {})
raise YFDataException(f"{self.ticker}: 'lookup' fetch returned error: {error}")

self._cache[cache_key] = data
return data
Expand Down
11 changes: 11 additions & 0 deletions yfinance/scrapers/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import warnings

from yfinance import utils
from yfinance.config import YfConfig
from yfinance.const import quote_summary_valid_modules, _SENTINEL_
from yfinance.data import YfData
from yfinance.exceptions import YFException
Expand Down Expand Up @@ -84,6 +85,8 @@ def analyst_price_targets(self) -> dict:
data = self._fetch(['financialData'])
data = data['quoteSummary']['result'][0]['financialData']
except (TypeError, KeyError):
if not YfConfig().hide_exceptions:
raise
self._analyst_price_targets = {}
return self._analyst_price_targets

Expand All @@ -107,6 +110,8 @@ def earnings_history(self) -> pd.DataFrame:
data = self._fetch(['earningsHistory'])
data = data['quoteSummary']['result'][0]['earningsHistory']['history']
except (TypeError, KeyError):
if not YfConfig().hide_exceptions:
raise
self._earnings_history = pd.DataFrame()
return self._earnings_history

Expand Down Expand Up @@ -143,6 +148,8 @@ def growth_estimates(self) -> pd.DataFrame:
trends = self._fetch(['industryTrend', 'sectorTrend', 'indexTrend'])
trends = trends['quoteSummary']['result'][0]
except (TypeError, KeyError):
if not YfConfig().hide_exceptions:
raise
self._growth_estimates = pd.DataFrame()
return self._growth_estimates

Expand Down Expand Up @@ -180,6 +187,8 @@ def _fetch(self, modules: list):
try:
result = self._data.get_raw_json(_QUOTE_SUMMARY_URL_ + f"/{self._symbol}", params=params_dict)
except curl_cffi.requests.exceptions.HTTPError as e:
if not YfConfig().hide_exceptions:
raise
utils.get_yf_logger().error(str(e))
return None
return result
Expand All @@ -189,4 +198,6 @@ def _fetch_earnings_trend(self) -> None:
data = self._fetch(['earningsTrend'])
self._earnings_trend = data['quoteSummary']['result'][0]['earningsTrend']['trend']
except (TypeError, KeyError):
if not YfConfig().hide_exceptions:
raise
self._earnings_trend = []
Loading
Loading