2020-07-07 02:28:34 +04:00
|
|
|
"""Currency class and a set of exceptions.
|
|
|
|
|
|
|
|
Face values are stored as decimals*.
|
|
|
|
|
|
|
|
* Decimal is used to avoid problems of rounding float numbers. So first two
|
|
|
|
digits to the right are used to store a fraction part. You can simply divide
|
|
|
|
this number by 100 to get a floating-point number.
|
|
|
|
|
|
|
|
Currency string has following format:
|
|
|
|
<ISO>;<CURRENCY NAME>;<COINS COUNT>;{}<FRACTION SYMBOL>,{}<CURRENCY SYMBOL>
|
|
|
|
;<FACE VALUES>
|
|
|
|
|
|
|
|
{} with currency symbol used as a placeholder for where to put digits.
|
|
|
|
|
|
|
|
Example:
|
2020-07-08 02:10:35 +04:00
|
|
|
RUB;Russian ruble;8;{} коп.,{}₽;1,5,10,50,100,200,500,1000
|
2020-07-07 02:28:34 +04:00
|
|
|
USD;US Dollar;6;{}¢,${};1,5,10,25,50,100
|
|
|
|
"""
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
from operator import mul
|
|
|
|
from typing import Dict, List, TypedDict
|
|
|
|
|
|
|
|
__all__ = [ "Currency", "BaseCurrencyError", "CurrencyIsNotSupportedError",
|
|
|
|
"CurrenciesCoinCountMismatchError", "CurrencyMismatchError"]
|
|
|
|
|
|
|
|
|
|
|
|
class BaseCurrencyError(Exception):
|
|
|
|
"""Base class for all currency exeptions."""
|
|
|
|
def __init__(self, message=None, *args, **kwargs) -> None:
|
|
|
|
if message is None:
|
|
|
|
message = self.__doc__
|
|
|
|
super().__init__(message, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
class CurrencyIsNotSupportedError(BaseCurrencyError):
|
|
|
|
"""Currency is not supported."""
|
|
|
|
|
|
|
|
|
|
|
|
class CurrencyMismatchError(BaseCurrencyError):
|
|
|
|
"""Currencies doesn't match, but they must do so."""
|
|
|
|
def __init__(self, extra=None):
|
|
|
|
if not extra is None:
|
|
|
|
super().__init__(f"{self.__doc__} {extra}")
|
|
|
|
else:
|
|
|
|
super().__init__(self.__doc__)
|
|
|
|
|
|
|
|
|
|
|
|
class CurrenciesCoinCountMismatchError(BaseCurrencyError):
|
|
|
|
"""Length of list supplied doesn't corresponds coins count."""
|
|
|
|
|
|
|
|
|
|
|
|
class Currency:
|
|
|
|
"""Currency's representation and methods to work with it.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
iso_code -- ISO code of a currency;
|
|
|
|
name -- full name of a currency;
|
|
|
|
coins_count -- number of face values of coins;
|
|
|
|
fraction_symbol -- a symbol or word for a fraction of currency (e.g. c, cent);
|
|
|
|
currency_symbol -- a symbol or word for a currency (e.g. $);
|
2020-07-08 02:10:35 +04:00
|
|
|
face_values -- list of values for each face value in decimal;
|
2020-07-07 02:28:34 +04:00
|
|
|
"""
|
|
|
|
def __init__(self, iso_code: str, name: str, coins_count: int,
|
|
|
|
fraction_symbol: str, currency_symbol: str,
|
|
|
|
face_values: List[int]):
|
|
|
|
self._iso = iso_code.upper()
|
|
|
|
self._name = name
|
|
|
|
self._coins_count = coins_count
|
|
|
|
self._fraction_symbol = fraction_symbol
|
|
|
|
self._currency_symbol = currency_symbol
|
|
|
|
self._face_values = face_values
|
|
|
|
|
|
|
|
@property
|
|
|
|
def iso(self) -> str:
|
|
|
|
"""ISO code of a currency."""
|
|
|
|
return self._iso
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self) -> str:
|
|
|
|
"""Full name of a currency."""
|
|
|
|
return self._name
|
|
|
|
|
|
|
|
@property
|
|
|
|
def count(self):
|
|
|
|
"""Count of coins of a currency."""
|
|
|
|
return self._coins_count
|
|
|
|
|
|
|
|
@property
|
|
|
|
def fraction_symbol(self) -> str:
|
|
|
|
"""Returns a symbol or word for a fraction of a currency."""
|
|
|
|
return self._fraction_symbol
|
|
|
|
|
|
|
|
@property
|
|
|
|
def currency_symbol(self) -> str:
|
|
|
|
"""Returns a symbol or word for a currency."""
|
|
|
|
return self._currency_symbol
|
|
|
|
|
|
|
|
@property
|
|
|
|
def face_values(self) -> List[int]:
|
|
|
|
"""Face values of coins. From least to most."""
|
|
|
|
return self._face_values
|
|
|
|
|
|
|
|
def multiply(self, lst: List[int]) -> List[float]:
|
|
|
|
"""Multiplies a list by face values. Length of list must match
|
|
|
|
currency's coins count."""
|
|
|
|
if len(lst) != len(self):
|
|
|
|
raise CurrenciesCoinCountMismatchError
|
|
|
|
return list(map(mul, lst, self.face_values))
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def from_string(currency: str) -> Currency:
|
|
|
|
"""Creates a Currency object from its string representation."""
|
2020-07-07 02:30:21 +04:00
|
|
|
iso, name, count, syms, fv = currency.split(';')
|
|
|
|
frac_sym, cur_sym = syms.split(',')
|
2020-07-07 02:28:34 +04:00
|
|
|
return Currency(iso, name, int(count), frac_sym, cur_sym,
|
2020-07-07 02:30:21 +04:00
|
|
|
list(map(int, fv.split(','))))
|
2020-07-07 02:28:34 +04:00
|
|
|
|
|
|
|
def __mul__(self, lst: List[int]) -> List[float]:
|
|
|
|
return self.multiply(lst)
|
|
|
|
|
|
|
|
def __len__(self) -> int:
|
|
|
|
return self.count
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return f"{self.iso};{self.name};{self.count};" \
|
|
|
|
f"{self.fraction_symbol},{self.currency_symbol};" \
|
|
|
|
f"{','.join(list(map(str, self.face_values)))}"
|