data-xexe/xexe/exchange_rates.py

130 lines
3.6 KiB
Python
Raw Normal View History

2024-06-11 20:33:35 +02:00
import datetime
from decimal import Decimal
from numbers import Number
from typing import Iterable, Set, Union
2024-06-11 20:33:35 +02:00
2024-06-12 00:44:30 +02:00
from money.currency import Currency, CurrencyHelper
2024-06-11 20:33:35 +02:00
from money.money import Money
class ExchangeRate:
def __init__(
self,
from_currency: Currency,
to_currency: Currency,
2024-06-12 00:44:30 +02:00
rate: Union[Money, Number, str],
2024-06-11 20:33:35 +02:00
rate_date: datetime.date,
) -> None:
self.from_currency = from_currency
self.to_currency = to_currency
if not isinstance(rate, Money):
rate = Money(rate, to_currency)
2024-06-11 20:33:35 +02:00
self.rate = rate
self.rate_date = rate_date
@property
def descriptor(self) -> str:
return (
str(self.from_currency.value)
+ str(self.to_currency.value)
2024-06-11 20:44:03 +02:00
+ str(self.rate_date.strftime("%Y-%m-%d"))
2024-06-11 20:33:35 +02:00
)
@property
def amount(self) -> Decimal:
return self.rate.amount
2024-06-11 20:33:35 +02:00
class ExchangeRates:
def __init__(self, rates: Union[Iterable[ExchangeRate], None] = None):
self._rate_index = {}
if rates is not None:
for rate in rates:
if not isinstance(rate, ExchangeRate):
raise TypeError("ExchangeRates can only hold Rates.")
self._rate_index[rate.descriptor] = rate
@property
def present_currencies(self) -> Set[Currency]:
present_currencies = set()
for rate in self:
present_currencies.add(rate.from_currency)
present_currencies.add(rate.to_currency)
return present_currencies
@property
def present_dates(self) -> Set[datetime.date]:
return {rate.rate_date for rate in self}
2024-06-11 20:33:35 +02:00
def add_rate(self, new_rate: ExchangeRate) -> None:
self._rate_index[new_rate.descriptor] = new_rate
def __iter__(self):
2024-06-12 00:44:30 +02:00
return iter(list(self._rate_index.values()))
def __len__(self):
return len(self._rate_index)
2024-06-12 00:44:30 +02:00
def __contains__(self, rate) -> bool:
if not isinstance(rate, ExchangeRate):
raise TypeError("ExchangeRates can only hold Rates.")
2024-06-12 00:44:30 +02:00
if rate.descriptor in self._rate_index:
return True
return False
def is_rate_present(self, rate: ExchangeRate) -> bool:
if rate.descriptor in self._rate_index:
return True
return False
def __getitem__(self, rate_descriptor) -> ExchangeRate:
return self._rate_index[rate_descriptor]
def add_equal_rates(rates: ExchangeRates, overwrite: bool = False) -> ExchangeRates:
present_currencies = rates.present_currencies
present_dates = rates.present_dates
for date in present_dates:
for currency in present_currencies:
2024-06-12 00:44:30 +02:00
new_rate = ExchangeRate(
from_currency=currency,
to_currency=currency,
rate=Money(1, currency),
rate_date=date,
)
2024-06-12 00:44:30 +02:00
if new_rate in rates and not overwrite:
continue
rates.add_rate(new_rate)
return rates
def add_inverse_rates(rates: ExchangeRates) -> ExchangeRates:
# Hey, I haven't thought properly what happens here if the inverse rate is
# *already* present in the rates set. It's probably going to be fucky. I
# would advise only running this against sets where you don't have inverse
# rates already present.
for rate in rates:
inverse_rate = ExchangeRate(
from_currency=rate.to_currency,
to_currency=rate.from_currency,
rate_date=rate.rate_date,
rate=f"{1 / rate.amount:.{CurrencyHelper.decimal_precision_for_currency(rate.from_currency)}f}",
)
rates.add_rate(inverse_rate)
return rates