Skip to content

Commit

Permalink
Fix broken currency API (#71)
Browse files Browse the repository at this point in the history
* Fix broken currency API

* Adds freecurrencyapi python package

* Fix more stuff
  • Loading branch information
michaelhball authored Apr 3, 2024
1 parent a5b705c commit 8c14a28
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 190 deletions.
41 changes: 27 additions & 14 deletions discogs_alert/util/currency.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,50 @@
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
"""

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:
Expand All @@ -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`)")
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
201 changes: 33 additions & 168 deletions tests/data/currency_rates.json
Original file line number Diff line number Diff line change
@@ -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
}
7 changes: 3 additions & 4 deletions tests/test_entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 2 additions & 4 deletions tests/util/test_currency.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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):
Expand Down

0 comments on commit 8c14a28

Please sign in to comment.