From 8c14a28f9c42817a9f0baecfb9023cb8d7bc9630 Mon Sep 17 00:00:00 2001 From: mhsb Date: Wed, 3 Apr 2024 20:01:42 +0200 Subject: [PATCH] Fix broken currency API (#71) * Fix broken currency API * Adds freecurrencyapi python package * Fix more stuff --- discogs_alert/util/currency.py | 41 ++++--- pyproject.toml | 1 + tests/data/currency_rates.json | 201 ++++++--------------------------- tests/test_entities.py | 7 +- tests/util/test_currency.py | 6 +- 5 files changed, 66 insertions(+), 190 deletions(-) diff --git a/discogs_alert/util/currency.py b/discogs_alert/util/currency.py index 36901a5..81720f8 100644 --- a/discogs_alert/util/currency.py +++ b/discogs_alert/util/currency.py @@ -1,24 +1,31 @@ +import json import os -from typing import Dict, Union +import pathlib +from datetime import datetime +from typing import Dict -import requests +import freecurrencyapi from discogs_alert.util.constants import CURRENCY_CHOICES -from discogs_alert.util.system import time_cache -CurrencyRates = Dict[str, Union[int, float]] +# Dict containing currency conversions for all currencies with respect to a given base currency +CurrencyRates = Dict[str, float] + +# Directory in which to store weekly CurrencyRates JSON caches +CACHE_DIR = pathlib.Path(__file__).parent.parent.parent.resolve() / ".currency_cache" class InvalidCurrencyException(Exception): ... -@time_cache(seconds=86400) def get_currency_rates(base_currency: str) -> CurrencyRates: - """Get live currency exchange rates (from one base currency). Cached for one day at a time, per currency. + """ + Get live currency exchange rates (from one base currency). Cached for one week at a time, per currency (to avoid + API limits, and because small fluctuations in currency rates are really not important). Args: - base_currency: one of the 3-character currency identifiers from above. + base_currency: one of the valid 3-character currency identifiers. Returns: a dict containing exchange rates _to_ all major currencies _from_ the given base currency """ @@ -26,12 +33,18 @@ def get_currency_rates(base_currency: str) -> CurrencyRates: if base_currency not in CURRENCY_CHOICES: raise InvalidCurrencyException(f"{base_currency} is not a supported currency (see `discogs_alert/types.py`).") - access_key = os.getenv("DA_CURRENCY_TOKEN") - return ( - requests.get(f"http://api.exchangerate.host/live?access_key={access_key}&source={base_currency}") - .json() - .get("quotes") - ) + # See whether we've already cached currency rates for the current week + now = datetime.now().isocalendar() + cache_file = f"{CACHE_DIR}/{now.year}-{now.week}-{base_currency}" + if os.path.exists(cache_file): + return json.load(pathlib.Path(cache_file).open("r")) + + # Else query & cache them before returning + client = freecurrencyapi.Client(os.getenv("DA_CURRENCY_TOKEN")) + currency_rates = client.latest(base_currency="EUR")["data"] + json.dump(currency_rates, pathlib.Path(cache_file).open("w")) + + return currency_rates def convert_currency(value: float, old_currency: str, new_currency: str) -> float: @@ -46,6 +59,6 @@ def convert_currency(value: float, old_currency: str, new_currency: str) -> floa """ try: - return float(value) / get_currency_rates(new_currency)[f"{new_currency}{old_currency}"] + return float(value) / get_currency_rates(new_currency)[old_currency] except KeyError: raise InvalidCurrencyException(f"{old_currency} is not a supported currency (see `discogs_alert/types.py`)") diff --git a/pyproject.toml b/pyproject.toml index cb1c9b0..1807d61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ beautifulsoup4 = "^4.11.2" click = "^8.1.3" dacite = "^1.8.0" fake-useragent = "^1.1.1" +freecurrencyapi = "^0.1.0" pre-commit = "^2.20.0" requests = "^2.28.2" ruff = "^0.0.253" diff --git a/tests/data/currency_rates.json b/tests/data/currency_rates.json index a1e73fb..88ef778 100644 --- a/tests/data/currency_rates.json +++ b/tests/data/currency_rates.json @@ -1,170 +1,35 @@ { - "EURAED": 4.00965, - "EURAFN": 75.018901, - "EURALL": 103.920772, - "EURAMD": 437.476958, - "EURANG": 1.957849, - "EURAOA": 903.570398, - "EURARS": 384.006996, - "EURAUD": 1.676099, - "EURAWG": 1.964957, - "EURAZN": 1.860127, - "EURBAM": 1.95525, - "EURBBD": 2.193383, - "EURBDT": 120.306163, - "EURBGN": 1.95964, - "EURBHD": 0.409685, - "EURBIF": 3087.531618, - "EURBMD": 1.091643, - "EURBND": 1.460289, - "EURBOB": 7.506889, - "EURBRL": 5.355496, - "EURBSD": 1.086294, - "EURBTC": 2.9743319e-05, - "EURBTN": 90.447761, - "EURBWP": 14.571902, - "EURBYN": 3.578494, - "EURBYR": 21396.202226, - "EURBZD": 2.189684, - "EURCAD": 1.497898, - "EURCDF": 2866.654836, - "EURCHF": 0.966941, - "EURCLF": 0.034535, - "EURCLP": 952.921987, - "EURCNY": 7.871514, - "EURCOP": 4439.651329, - "EURCRC": 577.337621, - "EURCUC": 1.091643, - "EURCUP": 28.928539, - "EURCVE": 110.233996, - "EURCZK": 24.489537, - "EURDJF": 193.415601, - "EURDKK": 7.458764, - "EURDOP": 61.692649, - "EURDZD": 146.66446, - "EUREGP": 33.594451, - "EURERN": 16.374645, - "EURETB": 60.756612, - "EURFJD": 2.498666, - "EURFKP": 0.878435, - "EURGBP": 0.876329, - "EURGEL": 2.942022, - "EURGGP": 0.878435, - "EURGHS": 12.989347, - "EURGIP": 0.878435, - "EURGMD": 73.413415, - "EURGNF": 9334.374667, - "EURGTQ": 8.505608, - "EURGYD": 227.276078, - "EURHKD": 8.511814, - "EURHNL": 26.831454, - "EURHRK": 7.68688, - "EURHTG": 144.149457, - "EURHUF": 379.215358, - "EURIDR": 16837.828673, - "EURILS": 4.05755, - "EURIMP": 0.878435, - "EURINR": 90.924854, - "EURIQD": 1423.099747, - "EURIRR": 46135.565218, - "EURISK": 152.895936, - "EURJEP": 0.878435, - "EURJMD": 169.282389, - "EURJOD": 0.774307, - "EURJPY": 163.238875, - "EURKES": 165.443468, - "EURKGS": 97.293557, - "EURKHR": 4475.741179, - "EURKMF": 495.196586, - "EURKPW": 982.491758, - "EURKRW": 1415.184533, - "EURKWD": 0.336543, - "EURKYD": 0.905245, - "EURKZT": 504.048234, - "EURLAK": 22526.664279, - "EURLBP": 16327.407846, - "EURLKR": 356.339778, - "EURLRD": 205.065549, - "EURLSL": 20.053898, - "EURLTL": 3.223338, - "EURLVL": 0.660324, - "EURLYD": 5.26352, - "EURMAD": 11.017801, - "EURMDL": 19.374551, - "EURMGA": 4920.616056, - "EURMKD": 61.597675, - "EURMMK": 2281.258386, - "EURMNT": 3763.112256, - "EURMOP": 8.726346, - "EURMRO": 389.716353, - "EURMUR": 48.207367, - "EURMVR": 16.866297, - "EURMWK": 1828.685674, - "EURMXN": 18.807484, - "EURMYR": 5.100706, - "EURMZN": 69.046364, - "EURNAD": 20.053893, - "EURNGN": 917.024173, - "EURNIO": 39.758818, - "EURNOK": 11.783075, - "EURNPR": 144.716298, - "EURNZD": 1.820619, - "EUROMR": 0.420217, - "EURPAB": 1.086294, - "EURPEN": 4.201418, - "EURPGK": 4.103846, - "EURPHP": 60.474296, - "EURPKR": 311.230565, - "EURPLN": 4.386266, - "EURPYG": 8072.72951, - "EURQAR": 3.974714, - "EURRON": 4.977678, - "EURRSD": 117.207035, - "EURRUB": 97.538707, - "EURRWF": 1339.823169, - "EURSAR": 4.094218, - "EURSBD": 9.191265, - "EURSCR": 14.320772, - "EURSDG": 656.077796, - "EURSEK": 11.564833, - "EURSGD": 1.467391, - "EURSHP": 1.328257, - "EURSLE": 24.490293, - "EURSLL": 21559.949041, - "EURSOS": 623.328506, - "EURSRD": 41.640763, - "EURSTD": 22594.805475, - "EURSYP": 14193.347758, - "EURSZL": 19.885407, - "EURTHB": 38.639253, - "EURTJS": 11.884357, - "EURTMT": 3.82075, - "EURTND": 3.399926, - "EURTOP": 2.600335, - "EURTRY": 31.307888, - "EURTTD": 7.376925, - "EURTWD": 34.659341, - "EURTZS": 2713.636778, - "EURUAH": 39.236265, - "EURUGX": 4105.845213, - "EURUSD": 1.091643, - "EURUYU": 43.147864, - "EURUZS": 13340.247998, - "EURVEF": 3861416.594396, - "EURVES": 38.607661, - "EURVND": 26488.716684, - "EURVUV": 132.260957, - "EURWST": 3.025362, - "EURXAF": 655.772561, - "EURXAG": 0.04601, - "EURXAU": 0.000551, - "EURXCD": 2.95022, - "EURXDR": 0.820169, - "EURXOF": 655.772561, - "EURXPF": 120.572363, - "EURYER": 273.351325, - "EURZAR": 20.024193, - "EURZMK": 9826.100628, - "EURZMW": 25.229904, - "EURZWL": 351.508591 + "AUD": 1.6531610671, + "BGN": 1.9534088731, + "BRL": 5.4463221665, + "CAD": 1.4607323679, + "CHF": 0.9777059811, + "CNY": 7.7831235548, + "CZK": 25.2860762526, + "DKK": 7.4581583276, + "EUR": 1, + "GBP": 0.8562951521, + "HKD": 8.4285193519, + "HRK": 7.2489608558, + "HUF": 394.6315722182, + "IDR": 17104.5927655074, + "ILS": 4.0040174185, + "INR": 89.707252332, + "ISK": 150.2225196915, + "JPY": 163.1774310922, + "KRW": 1453.1252724105, + "MXN": 17.8313310647, + "MYR": 5.1151212505, + "NOK": 11.6666884565, + "NZD": 1.8037695252, + "PHP": 60.6137415547, + "PLN": 4.2891976903, + "RON": 4.9694668363, + "RUB": 99.5478026609, + "SEK": 11.5633284985, + "SGD": 1.4553042291, + "THB": 39.440724367, + "TRY": 34.4129784754, + "USD": 1.0770057964, + "ZAR": 20.2104575408 } \ No newline at end of file diff --git a/tests/test_entities.py b/tests/test_entities.py index f18c92b..3a89e7f 100644 --- a/tests/test_entities.py +++ b/tests/test_entities.py @@ -9,23 +9,22 @@ def test_conditions_satisfied(): def test_convert_listing_price_currency(mock_currency_rates, rates: da_currency.CurrencyRates): - # full listing price lp1 = da_entities.ListingPrice("GBP", 10, shipping=da_entities.ShippingPrice("GBP", 5)) lp1_target = da_entities.ListingPrice( - "EUR", 10 / rates["EURGBP"], shipping=da_entities.ShippingPrice("EUR", 5 / rates["EURGBP"]) + "EUR", 10 / rates["GBP"], shipping=da_entities.ShippingPrice("EUR", 5 / rates["GBP"]) ) assert lp1.convert_currency("EUR") == lp1_target # no shipping lp2 = da_entities.ListingPrice("GBP", 10, shipping=None) - lp2_target = da_entities.ListingPrice("EUR", 10 / rates["EURGBP"], shipping=None) + lp2_target = da_entities.ListingPrice("EUR", 10 / rates["GBP"], shipping=None) assert lp2.convert_currency("EUR") == lp2_target # different currencies lp3 = da_entities.ListingPrice("GBP", 10, shipping=da_entities.ShippingPrice("AUD", 5)) lp3_target = da_entities.ListingPrice( - "EUR", 10 / rates["EURGBP"], shipping=da_entities.ShippingPrice("EUR", 5 / rates["EURAUD"]) + "EUR", 10 / rates["GBP"], shipping=da_entities.ShippingPrice("EUR", 5 / rates["AUD"]) ) assert lp3.convert_currency("EUR") == lp3_target diff --git a/tests/util/test_currency.py b/tests/util/test_currency.py index 80d66b5..8cc38ab 100644 --- a/tests/util/test_currency.py +++ b/tests/util/test_currency.py @@ -5,7 +5,6 @@ @pytest.mark.online def test_get_currency_rates(): - # test that the URL works for all supported currencies (requires internet connection) for currency in dac.CURRENCY_CHOICES: rates = da_currency.get_currency_rates(currency) @@ -22,13 +21,12 @@ def test_get_currency_rates(): def test_convert_currency(mock_currency_rates, rates: da_currency.CurrencyRates): - # make sure our monkeypatch worked assert da_currency.get_currency_rates("SGD") == da_currency.get_currency_rates("EUR") # make sure the currency conversion is working correctly - assert da_currency.convert_currency(1, "GBP", "EUR") == 1 / rates.get("EURGBP") - assert da_currency.convert_currency(1, "CHF", "EUR") == 1 / rates.get("EURCHF") + assert da_currency.convert_currency(1, "GBP", "EUR") == 1 / rates.get("GBP") + assert da_currency.convert_currency(1, "CHF", "EUR") == 1 / rates.get("CHF") # make sure invalid currencies are handled with pytest.raises(da_currency.InvalidCurrencyException):