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
56 changes: 56 additions & 0 deletions yahooquery/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,59 @@ def _construct_data(self, json, response_field, **kwargs):
except TypeError:
data = json
return data

def _normalize_quote_summary_response(self, data, module_name="quoteSummary"):
"""
Normalize quoteSummary response to ensure consistent structure across all modules.

Many quoteSummary endpoints can return either:
1. A dictionary with nested module data (normal case)
2. A string error message when no data is found (error case)

This method ensures that error cases return a consistent structure
instead of a plain string, making it easier for consumers to handle
the response predictably across all quoteSummary-based properties.

Parameters
----------
data : dict
Raw response from _quote_summary
module_name : str, optional
Name of the module being normalized (for logging/debugging)

Returns
-------
dict
Normalized response where each symbol maps to either:
- A dict with module data (success case)
- A dict with error information (error case):
{
"error": {
"code": 404,
"type": "NotFoundError",
"message": "No fundamentals data found for symbol: EAI",
"symbol": "EAI"
}
}
"""
if not isinstance(data, dict):
return data

normalized_data = {}
for symbol, module_data in data.items():

if isinstance(module_data, str):
# Convert string error messages to a consistent error structure
normalized_data[symbol] = {
"error": {
"code": 404,
"type": "NotFoundError",
"message": module_data,
"symbol": symbol
}
}
else:
# Keep successful responses as-is
normalized_data[symbol] = module_data

return normalized_data
10 changes: 5 additions & 5 deletions yahooquery/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,15 +587,15 @@
"UnrealizedGainLossOnInvestmentSecurities",
],
"valuation": [
"MarketCap",
"EnterpriseValue",
"PeRatio",
"ForwardPeRatio",
"PegRatio",
"PsRatio",
"PbRatio",
"EnterprisesValueEBITDARatio",
"EnterprisesValueRevenueRatio",
"PeRatio",
"MarketCap",
"EnterpriseValue",
"PegRatio",
"EnterprisesValueEBITDARatio",
],
}

Expand Down
106 changes: 82 additions & 24 deletions yahooquery/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ def get_modules(self, modules):
One of {} is not a valid value. Valid values are {}.
""".format(", ".join(modules), ", ".join(all_modules))
)
return self._quote_summary(modules)
data = self._quote_summary(modules)
return self._normalize_quote_summary_response(data, "quoteSummary")

@property
def asset_profile(self):
Expand All @@ -208,7 +209,8 @@ def asset_profile(self):
dict
assetProfile module data
"""
return self._quote_summary(["assetProfile"])
data = self._quote_summary(["assetProfile"])
return self._normalize_quote_summary_response(data, "asset_profile")

@property
def calendar_events(self):
Expand All @@ -222,7 +224,8 @@ def calendar_events(self):
dict
calendarEvents module data
"""
return self._quote_summary(["calendarEvents"])
data = self._quote_summary(["calendarEvents"])
return self._normalize_quote_summary_response(data, "calendar_events")

@property
def earnings(self):
Expand All @@ -235,7 +238,8 @@ def earnings(self):
dict
earnings module data
"""
return self._quote_summary(["earnings"])
data = self._quote_summary(["earnings"])
return self._normalize_quote_summary_response(data, "earnings")

@property
def earnings_trend(self):
Expand All @@ -249,7 +253,8 @@ def earnings_trend(self):
dict
earningsTrend module data
"""
return self._quote_summary(["earningsTrend"])
data = self._quote_summary(["earningsTrend"])
return self._normalize_quote_summary_response(data, "earnings_trend")

@property
def esg_scores(self):
Expand All @@ -263,7 +268,8 @@ def esg_scores(self):
dict
esgScores module data
"""
return self._quote_summary(["esgScores"])
data = self._quote_summary(["esgScores"])
return self._normalize_quote_summary_response(data, "esg_scores")

@property
def financial_data(self):
Expand All @@ -276,7 +282,8 @@ def financial_data(self):
dict
financialData module data
"""
return self._quote_summary(["financialData"])
data = self._quote_summary(["financialData"])
return self._normalize_quote_summary_response(data, "financial_data")

def news(self, count=25, start=None):
"""News articles related to given symbol(s)
Expand Down Expand Up @@ -320,7 +327,8 @@ def index_trend(self):
dict
indexTrend module data
"""
return self._quote_summary(["indexTrend"])
data = self._quote_summary(["indexTrend"])
return self._normalize_quote_summary_response(data, "index_trend")

@property
def industry_trend(self):
Expand All @@ -333,7 +341,8 @@ def industry_trend(self):
dict
industryTrend module data
"""
return self._quote_summary(["industryTrend"])
data = self._quote_summary(["industryTrend"])
return self._normalize_quote_summary_response(data, "industry_trend")

@property
def key_stats(self):
Expand All @@ -346,7 +355,8 @@ def key_stats(self):
dict
defaultKeyStatistics module data
"""
return self._quote_summary(["defaultKeyStatistics"])
data = self._quote_summary(["defaultKeyStatistics"])
return self._normalize_quote_summary_response(data, "key_stats")

@property
def major_holders(self):
Expand All @@ -360,7 +370,8 @@ def major_holders(self):
dict
majorHoldersBreakdown module data
"""
return self._quote_summary(["majorHoldersBreakdown"])
data = self._quote_summary(["majorHoldersBreakdown"])
return self._normalize_quote_summary_response(data, "major_holders")

@property
def page_views(self):
Expand All @@ -373,7 +384,8 @@ def page_views(self):
dict
pageViews module data
"""
return self._quote_summary(["pageViews"])
data = self._quote_summary(["pageViews"])
return self._normalize_quote_summary_response(data, "page_views")

@property
def price(self):
Expand All @@ -387,7 +399,8 @@ def price(self):
dict
price module data
"""
return self._quote_summary(["price"])
data = self._quote_summary(["price"])
return self._normalize_quote_summary_response(data, "price")

@property
def quote_type(self):
Expand Down Expand Up @@ -446,7 +459,8 @@ def share_purchase_activity(self):
dict
netSharePurchaseActivity module data
"""
return self._quote_summary(["netSharePurchaseActivity"])
data = self._quote_summary(["netSharePurchaseActivity"])
return self._normalize_quote_summary_response(data, "share_purchase_activity")

@property
def summary_detail(self):
Expand All @@ -459,7 +473,8 @@ def summary_detail(self):
dict
summaryDetail module data
"""
return self._quote_summary(["summaryDetail"])
data = self._quote_summary(["summaryDetail"])
return self._normalize_quote_summary_response(data, "summary_detail")

@property
def summary_profile(self):
Expand All @@ -472,7 +487,8 @@ def summary_profile(self):
dict
summaryProfile module data
"""
return self._quote_summary(["summaryProfile"])
data = self._quote_summary(["summaryProfile"])
return self._normalize_quote_summary_response(data, "summary_profile")

@property
def technical_insights(self):
Expand Down Expand Up @@ -557,7 +573,7 @@ def _financials_dataframes(self, data, period_type):
df = pd.DataFrame.from_records(data[data_type])
if period_type:
df["reportedValue"] = df["reportedValue"].apply(
lambda x: x.get("raw") if isinstance(x, dict) else x
lambda x: self._extract_reported_value(x)
)
df["dataType"] = data_type
df["symbol"] = symbol
Expand All @@ -571,6 +587,22 @@ def _financials_dataframes(self, data, period_type):
# No data is available for that type
pass

def _extract_reported_value(self, reported_value):
"""Extract the raw value from reportedValue, handling nested structures"""
if isinstance(reported_value, dict):
raw_value = reported_value.get("raw")
if isinstance(raw_value, dict):
# Handle nested raw structure (e.g., for MarketCap, EnterpriseValue)
raw_value = raw_value.get("parsedValue", raw_value.get("source"))
if isinstance(raw_value, str):
try:
return float(raw_value)
except (ValueError, TypeError):
pass
return raw_value
return reported_value


def all_financial_data(self, frequency="a"):
"""
Retrieve all financial data, including income statement,
Expand Down Expand Up @@ -635,17 +667,29 @@ def corporate_guidance(self):
trailing=False,
)

@property
def valuation_measures(self):
def valuation_measures(self, frequency="q", trailing=True):
"""Valuation Measures
Retrieves valuation measures for most recent four quarters as well
as the most recent date

Retrieves valuation measures for most recent quarters or years

Notes
-----
Only quarterly data is available for non-premium subscribers

Parameters
----------
frequency: str, default 'q', optional
Specify either annual or quarterly. Value should be 'a' or 'q'.
trailing: bool, default True, optional
Specify whether or not you'd like trailing twelve month (TTM)
data returned

Returns
-------
pandas.DataFrame
"""
return self._financials("valuation", "q")
return self._financials("valuation", frequency, trailing=trailing)


def balance_sheet(self, frequency="a"):
"""Balance Sheet
Expand Down Expand Up @@ -1170,12 +1214,26 @@ def p_ideas(self, idea_id):
def p_technical_events(self):
return self._get_data("technical_events")

def p_valuation_measures(self, frequency="q"):
def p_valuation_measures(self, frequency="q", trailing=True):
"""Valuation Measures

Retrieves valuation measures for all available dates for given
symbol(s)

Parameters
----------
frequency: str, default 'q', optional
Specify either annual or quarterly. Value should be 'a' or 'q'.
trailing: bool, default True, optional
Specify whether or not you'd like trailing twelve month (TTM)
data returned

Returns
-------
pandas.DataFrame
"""
return self._financials("valuation", frequency, premium=True)
return self._financials("valuation", frequency, premium=True, trailing=trailing)


@property
def p_value_analyzer(self):
Expand Down