Since this thing is on the innerwebs I suppose it should have a
[python_utils.git] / type / money.py
index 290c2c86f91a7a6da652b21e670d2579c51ba6c0..99637d1f23bd607c8856f4fada204b1859c51f3d 100644 (file)
@@ -1,13 +1,14 @@
 #!/usr/bin/env python3
 
-from decimal import Decimal
-import re
-from typing import Optional, TypeVar, Tuple
+# © Copyright 2021-2022, Scott Gasch
 
-import math_utils
+"""A class to represent money.  See also centcount.py"""
 
+import re
+from decimal import Decimal
+from typing import Optional, Tuple
 
-T = TypeVar('T', bound='Money')
+import math_utils
 
 
 class Money(object):
@@ -15,12 +16,12 @@ class Money(object):
     different currencies.
     """
 
-    def __init__ (
-            self,
-            amount: Decimal = Decimal("0"),
-            currency: str = 'USD',
-            *,
-            strict_mode = False
+    def __init__(
+        self,
+        amount: Decimal = Decimal("0"),
+        currency: str = 'USD',
+        *,
+        strict_mode=False,
     ):
         self.strict_mode = strict_mode
         if isinstance(amount, str):
@@ -35,16 +36,16 @@ class Money(object):
         if not currency:
             self.currency: Optional[str] = None
         else:
-            self.currency: Optional[str] = currency
+            self.currency = currency
 
     def __repr__(self):
         a = float(self.amount)
         a = round(a, 2)
         s = f'{a:,.2f}'
         if self.currency is not None:
-            return '%s %s' % (s, self.currency)
+            return f'{s} {self.currency}'
         else:
-            return '$%s' % s
+            return f'${s}'
 
     def __pos__(self):
         return Money(amount=self.amount, currency=self.currency)
@@ -55,10 +56,7 @@ class Money(object):
     def __add__(self, other):
         if isinstance(other, Money):
             if self.currency == other.currency:
-                return Money(
-                    amount = self.amount + other.amount,
-                    currency = self.currency
-                )
+                return Money(amount=self.amount + other.amount, currency=self.currency)
             else:
                 raise TypeError('Incompatible currencies in add expression')
         else:
@@ -66,17 +64,14 @@ class Money(object):
                 raise TypeError('In strict_mode only two moneys can be added')
             else:
                 return Money(
-                    amount = self.amount + Decimal(float(other)),
-                    currency = self.currency
+                    amount=self.amount + Decimal(float(other)),
+                    currency=self.currency,
                 )
 
     def __sub__(self, other):
         if isinstance(other, Money):
             if self.currency == other.currency:
-                return Money(
-                    amount = self.amount - other.amount,
-                    currency = self.currency
-                )
+                return Money(amount=self.amount - other.amount, currency=self.currency)
             else:
                 raise TypeError('Incompatible currencies in add expression')
         else:
@@ -84,8 +79,8 @@ class Money(object):
                 raise TypeError('In strict_mode only two moneys can be added')
             else:
                 return Money(
-                    amount = self.amount - Decimal(float(other)),
-                    currency = self.currency
+                    amount=self.amount - Decimal(float(other)),
+                    currency=self.currency,
                 )
 
     def __mul__(self, other):
@@ -93,8 +88,8 @@ class Money(object):
             raise TypeError('can not multiply monetary quantities')
         else:
             return Money(
-                amount = self.amount * Decimal(float(other)),
-                currency = self.currency
+                amount=self.amount * Decimal(float(other)),
+                currency=self.currency,
             )
 
     def __truediv__(self, other):
@@ -102,8 +97,8 @@ class Money(object):
             raise TypeError('can not divide monetary quantities')
         else:
             return Money(
-                amount = self.amount / Decimal(float(other)),
-                currency = self.currency
+                amount=self.amount / Decimal(float(other)),
+                currency=self.currency,
             )
 
     def __float__(self):
@@ -124,10 +119,7 @@ class Money(object):
     def __rsub__(self, other):
         if isinstance(other, Money):
             if self.currency == other.currency:
-                return Money(
-                    amount = other.amount - self.amount,
-                    currency = self.currency
-                )
+                return Money(amount=other.amount - self.amount, currency=self.currency)
             else:
                 raise TypeError('Incompatible currencies in sub expression')
         else:
@@ -135,8 +127,8 @@ class Money(object):
                 raise TypeError('In strict_mode only two moneys can be added')
             else:
                 return Money(
-                    amount = Decimal(float(other)) - self.amount,
-                    currency = self.currency
+                    amount=Decimal(float(other)) - self.amount,
+                    currency=self.currency,
                 )
 
     __rmul__ = __mul__
@@ -148,10 +140,7 @@ class Money(object):
         if other is None:
             return False
         if isinstance(other, Money):
-            return (
-                self.amount == other.amount and
-                self.currency == other.currency
-            )
+            return self.amount == other.amount and self.currency == other.currency
         if self.strict_mode:
             raise TypeError("In strict mode only two Moneys can be compared")
         else:
@@ -193,11 +182,11 @@ class Money(object):
     def __ge__(self, other):
         return self > other or self == other
 
-    def __hash__(self):
-        return self.__repr__
+    def __hash__(self) -> int:
+        return hash(self.__repr__)
 
-    AMOUNT_RE = re.compile("^([+|-]?)(\d+)(\.\d+)$")
-    CURRENCY_RE = re.compile("^[A-Z][A-Z][A-Z]$")
+    AMOUNT_RE = re.compile(r"^([+|-]?)(\d+)(\.\d+)$")
+    CURRENCY_RE = re.compile(r"^[A-Z][A-Z][A-Z]$")
 
     @classmethod
     def _parse(cls, s: str) -> Optional[Tuple[Decimal, str]]:
@@ -220,7 +209,7 @@ class Money(object):
         return None
 
     @classmethod
-    def parse(cls, s: str) -> T:
+    def parse(cls, s: str) -> 'Money':
         chunks = Money._parse(s)
         if chunks is not None:
             return Money(chunks[0], chunks[1])