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
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Change Log
==========

2.4.2
-----
## Add
- Added a new function and minor improvements (see PR/commit for details)

## Fix
- Minor bug and doc updates related to the new function

2.4.1
-----
## Fix
Expand Down
14 changes: 14 additions & 0 deletions docs/docs/guide/ticker/financials.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@
| aapl | 2020-07-29 00:00:00 | TTM | 1.6632e+12 | nan | nan | 25.1256 | 1.64774e+12 | 21.0104 | 29.7232 | 2.0905 | 6.37702 |
| aapl | 2020-07-30 00:00:00 | TTM | nan | 20.2772 | 6.2064 | nan | nan | nan | nan | nan | nan |

### **current_valuation_measures**

=== "Details"

- *Description*: Retrieves a JSON-serializable mapping of symbol to the most recent TTM valuation metrics.
- *Return*: `dict`

=== "Example"

```python
aapl = Ticker('aapl')
aapl.current_valuation_measures()
```


## Multiple

Expand Down
8 changes: 8 additions & 0 deletions docs/docs/release_notes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Release Notes

2.4.2
-----
## Add
- Added a new function and minor improvements (see PR/commit for details)

## Fix
- Minor bug and doc updates related to the new function

2.4.0
-----
## Update
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "yahooquery"
version = "2.4.1"
version = "2.4.2"
description = "Python wrapper for an unofficial Yahoo Finance API"
authors = [
{name = "Doug Guthrie", email = "[email protected]"}
Expand Down
53 changes: 53 additions & 0 deletions tests/test_ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"all_financial_data",
"p_all_financial_data",
"p_valuation_measures",
"current_valuation_measures",
]

SEPERATE_ENDPOINTS = [
Expand Down Expand Up @@ -185,9 +186,61 @@ def test_history_bad_args(ticker, period, interval):


def test_adj_ohlc(ticker):

def test_current_valuation_measures(ticker):
res = ticker.current_valuation_measures()
assert res is not None
if isinstance(res, str):
assert "unavailable" in res.lower()
else:
assert isinstance(res, dict)
for sym, metrics in res.items():
# Ensure there is an asOfDate for the current selected entry
assert "asOfDate" in metrics
assert ticker.history(period="max", adj_ohlc=True) is not None


def test_current_valuation_measures_batch_ttm(monkeypatch):
"""Ensure batch current_valuation_measures returns most recent TTM per symbol"""
# Create a Ticker with two symbols
ticker = Ticker("AAPL MSFT")
# Craft a MultiIndex DataFrame to mimic the API response
symbols = ["AAPL", "MSFT"]
dates = pd.to_datetime(["2020-01-01", "2020-02-01", "2020-03-01"])
idx = pd.MultiIndex.from_product([symbols, dates], names=["symbol", "date"])
df = pd.DataFrame(index=idx)
# Make TTM present for the middle date for each symbol
df["periodType"] = "TTM"
df["asOfDate"] = df.index.get_level_values(1)
df["value"] = range(len(df))

# Monkeypatch valuation_measures to return our DataFrame
monkeypatch.setattr(ticker, "valuation_measures", lambda frequency, trailing=True: df)
res = ticker.current_valuation_measures()
# Expect keys to be the two symbols and values have asOfDate equal to most recent TTM per symbol
assert set(res.keys()) == set(symbols)
for symbol in symbols:
assert res[symbol]["asOfDate"] == pd.Timestamp("2020-03-01").isoformat()


def test_current_valuation_measures_batch_no_ttm(monkeypatch):
"""Ensure fallback to most recent asOfDate per symbol when TTM is absent"""
ticker = Ticker("AAPL MSFT")
symbols = ["AAPL", "MSFT"]
dates = pd.to_datetime(["2020-01-01", "2020-02-01", "2020-03-01"])
idx = pd.MultiIndex.from_product([symbols, dates], names=["symbol", "date"])
df = pd.DataFrame(index=idx)
# No TTM rows
df["periodType"] = "MRQ"
df["asOfDate"] = df.index.get_level_values(1)
df["value"] = range(len(df))
monkeypatch.setattr(ticker, "valuation_measures", lambda frequency, trailing=True: df)
res = ticker.current_valuation_measures()
assert set(res.keys()) == set(symbols)
for symbol in symbols:
assert res[symbol]["asOfDate"] == pd.Timestamp("2020-03-01").isoformat()


class TestHistoryDataframe:
"""Tests for `utils.history_dataframe` and dependencies."""

Expand Down
2 changes: 1 addition & 1 deletion yahooquery/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Python interface to unofficial Yahoo Finance API endpoints"""

name = "yahooquery"
__version__ = "2.4.1"
__version__ = "2.4.2"


from .misc import ( # noqa
Expand Down
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
27 changes: 12 additions & 15 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 Expand Up @@ -714,17 +714,14 @@
},
},
"news": {
"path": "https://query2.finance.yahoo.com/v2/finance/news",
"response_field": "Content",
"path": "https://finance.yahoo.com/xhr/ncp",
"response_field": "data",
"method": "post",
"query": {
"start": {"required": False, "default": None},
"count": {"required": False, "default": None},
"symbols": {"required": True, "default": None},
"sizeLabels": {"required": False, "default": None},
"widths": {"required": False, "default": None},
"tags": {"required": False, "default": None},
"filterOldVideos": {"required": False, "default": None},
"category": {"required": False, "default": None},
"symbol": {"required": True, "default": None},
"location": {"required": False, "default": "US"},
"queryRef": {"required": False, "default": "newsAll"},
"serviceKey": {"required": False, "default": "ncp_fin"},
},
},
"quoteSummary": {
Expand Down
Loading