Skip to content

Commit

Permalink
Change get_latest_price to use None instead of current datetime, add …
Browse files Browse the repository at this point in the history
…unit test
  • Loading branch information
mrlimacz committed Jan 4, 2025
1 parent ba6b5be commit 7530b97
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 3 deletions.
7 changes: 4 additions & 3 deletions beanprice/sources/ecbrates.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ def _get_rate_EUR_to_CCY(currency, date):
# Call API
symbol = f"D.{currency}.EUR.SP00.A"
params = {
"endPeriod": date,
"format": "csvdata",
"detail": "full",
"lastNObservations": 1
}
if date is not None:
params["endPeriod"] = date
url = f"https://data-api.ecb.europa.eu/service/data/EXR/{symbol}"
response = requests.get(url, params=params)
if response.status_code != requests.codes.ok:
Expand Down Expand Up @@ -98,7 +99,7 @@ def _get_quote(ticker, date):

# Calculate base -> symbol
if EUR_to_symbol is None or EUR_to_base is None:
return None
raise ECBRatesError(f"At least one of the subrates returned None: (EUR{symbol}: {EUR_to_symbol}, EUR{base}: {EUR_to_base})")
else:
# Derive precision from sunrates (must be at least 5)
minimal_precision = 5
Expand All @@ -111,7 +112,7 @@ def _get_quote(ticker, date):
class Source(source.Source):

def get_latest_price(self, ticker):
return _get_quote(ticker, now().date().isoformat())
return _get_quote(ticker, None)

def get_historical_price(self, ticker, time):
return _get_quote(ticker, time.date().isoformat())
66 changes: 66 additions & 0 deletions beanprice/sources/ecbrates_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import unittest
from unittest import mock
from beanprice import source
from beanprice.sources import ecbrates
import requests
from decimal import Decimal
from datetime import datetime
from dateutil import tz


ECB_CSV = """KEY,FREQ,CURRENCY,CURRENCY_DENOM,EXR_TYPE,EXR_SUFFIX,TIME_PERIOD,OBS_VALUE,OBS_STATUS,OBS_CONF,OBS_PRE_BREAK,OBS_COM,TIME_FORMAT,BREAKS,COLLECTION,COMPILING_ORG,DISS_ORG,DOM_SER_IDS,PUBL_ECB,PUBL_MU,PUBL_PUBLIC,UNIT_INDEX_BASE,COMPILATION,COVERAGE,DECIMALS,NAT_TITLE,SOURCE_AGENCY,SOURCE_PUB,TITLE,TITLE_COMPL,UNIT,UNIT_MULT
EXR.D.SEK.EUR.SP00.A,D,SEK,EUR,SP00,A,2024-12-24,11.5335,A,F,,,P1D,,A,,,,,,,,,,4,,4F0,,Euro/Swedish krona,"ECB reference exchange rate, Euro/Swedish krona, 2:15 pm (C.E.T.)",SEK,0
"""

ECB_CSV_HIST = """KEY,FREQ,CURRENCY,CURRENCY_DENOM,EXR_TYPE,EXR_SUFFIX,TIME_PERIOD,OBS_VALUE,OBS_STATUS,OBS_CONF,OBS_PRE_BREAK,OBS_COM,TIME_FORMAT,BREAKS,COLLECTION,COMPILING_ORG,DISS_ORG,DOM_SER_IDS,PUBL_ECB,PUBL_MU,PUBL_PUBLIC,UNIT_INDEX_BASE,COMPILATION,COVERAGE,DECIMALS,NAT_TITLE,SOURCE_AGENCY,SOURCE_PUB,TITLE,TITLE_COMPL,UNIT,UNIT_MULT
EXR.D.SEK.EUR.SP00.A,D,SEK,EUR,SP00,A,2024-12-06,11.523,A,F,,,P1D,,A,,,,,,,,,,4,,4F0,,Euro/Swedish krona,"ECB reference exchange rate, Euro/Swedish krona, 2:15 pm (C.E.T.)",SEK,0
"""


def response(contents, status_code=requests.codes.ok):
"""Return a context manager to patch a CSV response."""
response = mock.Mock()
response.status_code = status_code
response.text = contents
return mock.patch('requests.get', return_value=response)


class ECBRatesErrorFetcher(unittest.TestCase):
def test_error_invalid_ticker(self):
with self.assertRaises(ValueError) as exc:
ecbrates.Source().get_latest_price('INVALID')

def test_error_network(self):
with response('Foobar', 404):
with self.assertRaises(ValueError) as exc:
ecbrates.Source().get_latest_price('EUR-SEK')

def test_empty_response(self):
with response('', 200):
with self.assertRaises(ecbrates.ECBRatesError) as exc:
ecbrates.Source().get_latest_price('EUR-SEK')

def test_valid_response(self):
contents = ECB_CSV
with response(contents):
srcprice = ecbrates.Source().get_latest_price('EUR-SEK')
self.assertIsInstance(srcprice, source.SourcePrice)
self.assertEqual(Decimal('11.5335'), srcprice.price)
self.assertEqual('SEK', srcprice.quote_currency)
self.assertIsInstance(srcprice.time, datetime)
self.assertEqual(datetime(2024, 12, 24, 0, 0, 0, tzinfo=tz.tzutc()), srcprice.time)


def test_historical_price(self):
time = datetime(2024, 12, 6, 16, 0, 0, tzinfo=tz.tzlocal()).astimezone(tz.tzutc())
contents = ECB_CSV_HIST
with response(contents):
srcprice = ecbrates.Source().get_historical_price('EUR-SEK', time)
self.assertIsInstance(srcprice, source.SourcePrice)
self.assertEqual(Decimal('11.523'), srcprice.price)
self.assertEqual('SEK', srcprice.quote_currency)
self.assertIsInstance(srcprice.time, datetime)
self.assertEqual(datetime(2024, 12, 6, 0, 0, 0, tzinfo=tz.tzutc()), srcprice.time)

if __name__ == '__main__':
unittest.main()

0 comments on commit 7530b97

Please sign in to comment.