Source code for hippo.price

import mcol

import mrich

CURRENCIES = {
    "USD": "$",
    "EUR": "€",
    "GBP": "£",
}


[docs] class Price: """Class to represent a certain amount of currency. Supported currencies: :: CURRENCIES = { 'USD':'$', 'EUR':'€', 'GBP':'£', } """ def __init__(self, amount, currency): if currency not in CURRENCIES: assert currency is None, f"Unrecognised {currency=}" assert not amount, f"Null Price can't have {amount=}" amount = None if amount is not None: amount = float(amount) self._amount = amount self._currency = currency ### FACTORIES
[docs] @classmethod def null(cls) -> "Price": """Zero in any currency""" self = cls.__new__(cls) self.__init__(None, None) return self
[docs] @classmethod def from_dict( cls, d: dict, ) -> "Price": """Create a :class:`.Price` object from a dictionary: :: dict(amount: float, currency: str) :param d: dictionary in the above format: """ self = cls.__new__(cls) self.__init__(d["amount"], d["currency"]) return self
### PROPERTIES @property def symbol(self) -> str: """Currency symbol""" return CURRENCIES[self.currency] @property def currency(self) -> str: """Currency string""" return self._currency @property def amount(self) -> float: """Amount""" return self._amount @property def is_null(self) -> bool: """Is this :meth:`.Price.null` or zero?""" return self.amount is None ### METHODS
[docs] def get_dict(self) -> dict: """Dictionary in the format: :: dict(amount: float, currency: str) """ return dict(amount=self.amount, currency=self.currency)
[docs] def copy(self) -> "Price": """Return a copy of this :class:`.Price`""" return Price(amount=self.amount, currency=self.currency)
### DUNDERS
[docs] def __str__(self) -> str: """Unformatted string representation""" if self.currency is None: return "Null Price" return f"{self.symbol}{self.amount:.2f} {self.currency}"
[docs] def __repr__(self) -> str: """ANSI Formatted string representation""" return f"{mcol.bold}{mcol.underline}{self}{mcol.unbold}{mcol.ununderline}"
def __rich__(self) -> str: """Rich Formatted string representation""" return f"[bold underline]{self}"
[docs] def __add__(self, other: "Price") -> "Price": """Add two :class:`.Price` objects :param other: :class:`.Price` object :returns: :class:`.Price` object """ if other is None: return self if other.is_null: return self if self.is_null: return other if self.currency != other.currency: raise NotImplementedError( f"Adding two different currencies: {self.currency} != {other.currency}" ) return Price(self.amount + other.amount, self.currency)
[docs] def __truediv__(self, other: "Price | float | int") -> "Price | float": """Divide this :class:`.Price` by another object :param other: :class:`.Price` or float or int :returns: :class:`.Price` object or float """ if isinstance(other, int) or isinstance(other, float): if self.is_null: return self return Price(amount=self.amount / other, currency=self.currency) elif isinstance(other, Price): assert self.currency == other.currency assert not other.is_null return self.amount / other.amount raise TypeError(f"Division not supported between Price and {type(other)}")
[docs] def __mul__(self, other: "Price | float | int") -> "Price | float": """Multiply this :class:`.Price` by another object :param other: :class:`.Price` or float or int :returns: :class:`.Price` object or float """ if isinstance(other, int) or isinstance(other, float): if self.is_null: return self return Price(amount=self.amount * other, currency=self.currency) raise TypeError(f"Multiplication not supported between Price and {type(other)}")
[docs] def __eq__(self, other: "Price") -> bool: """Compare two :class:`.Price` objects""" if isinstance(other, int) or isinstance(other, float): if self.is_null: return other == 0 return self.amount == other if self.is_null and other.is_null: return True if self.is_null and not other.is_null: return False if not self.is_null and other.is_null: return False assert ( self.currency == other.currency ), f"Comparing different currencies: {self.currency} != {other.currency}" return self.amount == other.amount
[docs] def __lt__(self, other: "Price") -> bool: """Compare two :class:`.Price` objects""" if isinstance(other, int) or isinstance(other, float): if self.is_null: return False return self.amount > other if self.is_null and other.is_null: return False if self.is_null and not other.is_null: return True if not self.is_null and other.is_null: return False assert ( self.currency == other.currency ), f"Comparing different currencies: {self.currency} != {other.currency}" return self.amount < other.amount
[docs] def __gt__(self, other: "Price") -> bool: """Compare two :class:`.Price` objects""" if isinstance(other, int) or isinstance(other, float): if self.is_null: return False return self.amount < other if self.is_null and other.is_null: return False if self.is_null and not other.is_null: return False if not self.is_null and other.is_null: return True assert ( self.currency == other.currency ), f"Comparing different currencies: {self.currency} != {other.currency}" return self.amount > other.amount