Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
25 changes: 19 additions & 6 deletions src/imf_reader/sdr/read_announcements.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def read_tsv(url: str) -> pd.DataFrame:
return pd.read_csv(url, delimiter="/t", engine="python")

except pd.errors.ParserError:
raise ValueError("SDR _data not available for this date")
raise ValueError("SDR data not available for this date")


def clean_df(df: pd.DataFrame) -> pd.DataFrame:
Expand Down Expand Up @@ -73,14 +73,16 @@ def get_holdings_and_allocations_data(


@lru_cache
def get_latest_allocations_holdings_date() -> tuple[int, int]:
def get_latest_allocations_holdings_date(log_info=True) -> tuple[int, int]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed, this should be called "fetch..." to be consistent with the other user facing functions

"""
Get the latest available SDR allocation holdings date.

Args:
log_info (bool, optional): If True, log info about the download. Defaults to True.
Returns:
tuple[int, int]: A tuple containing the year and month of the latest SDR data.
"""
logger.info("Fetching latest date")
if log_info:
logger.info("Fetching latest date")

response = make_request(MAIN_PAGE_URL)
soup = BeautifulSoup(response.content, "html.parser")
Expand All @@ -94,16 +96,27 @@ def get_latest_allocations_holdings_date() -> tuple[int, int]:


def fetch_allocations_holdings(date: tuple[int, int] | None = None) -> pd.DataFrame:
"""Fetch SDR holdings and allocations data for a given date
"""
Fetch SDR holdings and allocations data for a given date. If date is not specified or exceeds the latest available
date, it fetches data for the latest date

Args:
date: The year and month to get allocations and holdings data for. e.g. (2024, 11) for November 2024. If None, the latest announcements released are fetched
date: The year and month to get allocations and holdings data for. e.g. (2024, 11) for November 2024.
If None, the latest announcements released are fetched

returns:
A dataframe with the SDR allocations and holdings data
"""

if date is None:
date = get_latest_allocations_holdings_date()
else:
latest_date = get_latest_allocations_holdings_date(log_info=False)
date_obj = datetime(date[0], date[1], 1)
latest_date_obj = datetime(latest_date[0], latest_date[1], 1)
if date_obj > latest_date_obj:
raise ValueError(
f"SDR data unavailable for: ({date[0]}, {date[1]}).\nLatest available: ({latest_date[0]}, {latest_date[1]})"
)

return get_holdings_and_allocations_data(*date)
39 changes: 21 additions & 18 deletions tests/test_sdr/test_read_announcements.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from unittest.mock import patch, Mock
import pytest
import pandas as pd
import re
from imf_reader import sdr
from imf_reader.sdr.read_announcements import (
read_tsv,
Expand Down Expand Up @@ -50,7 +51,7 @@ def test_read_tsv_success(self, mock_read_csv):
def test_read_tsv_failure(self, mock_read_csv):
"""Test read_tsv raises ValueError on malformed data."""
mock_read_csv.side_effect = pd.errors.ParserError
with pytest.raises(ValueError, match="SDR _data not available for this date"):
with pytest.raises(ValueError, match="SDR data not available for this date"):
read_tsv("mock_url")

def test_clean_df_correct_format(self, input_df):
Expand Down Expand Up @@ -128,7 +129,7 @@ def test_get_holdings_and_allocations_data_success(
def test_get_holdings_and_allocations_data_failure(self, mock_read_tsv):
"""Test get_holdings_and_allocations_data raises ValueError when read_tsv fails."""
with pytest.raises(ValueError, match="Data not available"):
get_holdings_and_allocations_data(2024, 2)
get_holdings_and_allocations_data(1800, 1)

@patch("imf_reader.sdr.read_announcements.make_request")
def test_get_latest_date_success(self, mock_make_request):
Expand Down Expand Up @@ -192,7 +193,7 @@ def test_fetch_allocations_holdings_default_date(
):
"""Test fetch_allocations_holdings when no date is provided."""
# Mock get_latest_allocations_holdings_date to return a specific date
mock_get_latest_date.return_value = (2, 2024)
mock_get_latest_date.return_value = (2024, 2)

# Mock read_tsv
mock_read_tsv.return_value = input_df
Expand All @@ -217,24 +218,26 @@ def test_fetch_allocations_holdings_default_date(
# Assertions
mock_get_latest_date.assert_called_once() # Ensure get_latest_allocations_holdings_date was called
mock_get_holdings_and_allocations_data.assert_called_once_with(
2, 2024
2024, 2
) # Ensure the correct call
pd.testing.assert_frame_equal(
result, cleaned_df
) # Ensure the result matches the cleaned data

@patch("imf_reader.sdr.read_announcements.get_holdings_and_allocations_data")
@patch("imf_reader.sdr.read_announcements.get_latest_allocations_holdings_date")
def test_fetch_allocations_holdings_failure(
self, mock_get_latest_date, mock_get_holdings_data
):
"""Test fetch_allocations_holdings raises ValueError when data fetching fails."""
# Mock get_latest_allocations_holdings_date to return a date
mock_get_latest_date.return_value = (2, 2024)

# Simulate a failure in get_holdings_and_allocations_data
mock_get_holdings_data.side_effect = ValueError("Data not available")

# Assertions
with pytest.raises(ValueError, match="Data not available"):
fetch_allocations_holdings()
@patch("imf_reader.sdr.read_announcements.get_holdings_and_allocations_data")
@patch(
"imf_reader.sdr.read_announcements.get_latest_allocations_holdings_date",
return_value=(2024, 1),
)
def test_fetch_allocations_holdings_future_date(
mock_get_latest_date, mock_get_holdings_and_allocations_data
):
"""Test fetch_allocations_holdings when unavailable future date is provided."""
with pytest.raises(
ValueError,
match=re.escape(
"SDR data unavailable for: (2025, 1).\nLatest available: (2024, 1)"
),
):
fetch_allocations_holdings((2025, 1))