We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Bug during payment modification
I try to donate to this account: https://liberapay.com/lowtechlab/ after a previous promise on the twitter account.

lowtechlab created its account on the plateform but if I see that I donate to it, the actual number of mecene is still 0.

I tried to change the amount since I know that with the new system we need to do a payment in advance but I had a 500 error with the following error code: `fc677c3e6c54434ba77bcf1eac628a18`

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `liberapay/utils/currencies.py`
Content:
```
1 from __future__ import absolute_import, division, print_function, unicode_literals
2 
3 from collections import defaultdict, OrderedDict
4 from decimal import Decimal, ROUND_DOWN, ROUND_HALF_UP, ROUND_UP
5 from numbers import Number
6 import operator
7 
8 from mangopay.exceptions import CurrencyMismatch
9 from mangopay.utils import Money
10 import requests
11 import xmltodict
12 
13 from liberapay.constants import CURRENCIES, D_CENT, D_ZERO
14 from liberapay.website import website
15 
16 
17 def _convert(self, c, rounding=ROUND_HALF_UP):
18     if self.currency == c:
19         return self
20     if 'EUR' in (self.currency, c):
21         rate = website.currency_exchange_rates[(self.currency, c)]
22     else:
23         rate = (
24             website.currency_exchange_rates[(self.currency, 'EUR')] *
25             website.currency_exchange_rates[('EUR', c)]
26         )
27     amount = self.amount * rate
28     return Money(amount, c, rounding=rounding)
29 
30 def _sum(cls, amounts, currency):
31     a = Money.ZEROS[currency].amount
32     for m in amounts:
33         if m.currency != currency:
34             raise CurrencyMismatch(m.currency, currency, 'sum')
35         a += m.amount
36     return cls(a, currency)
37 
38 def _Money_init(self, amount=Decimal('0'), currency=None, rounding=None):
39     if not isinstance(amount, Decimal):
40         amount = Decimal(str(amount))
41         # Why `str(amount)`? Because:
42         # >>> Decimal(0.23)
43         # Decimal('0.2300000000000000099920072216264088638126850128173828125')
44         # >>> Decimal(str(0.23))
45         # Decimal('0.23')
46     if rounding is not None:
47         minimum = Money.MINIMUMS[currency].amount
48         amount = amount.quantize(minimum, rounding=rounding)
49     self.amount = amount
50     self.currency = currency
51 
52 def _Money_eq(self, other):
53     if isinstance(other, self.__class__):
54         return self.__dict__ == other.__dict__
55     if isinstance(other, (Decimal, Number)):
56         return self.amount == other
57     if isinstance(other, MoneyBasket):
58         return other.__eq__(self)
59     return False
60 
61 def _Money_round(self, rounding=ROUND_HALF_UP):
62     return Money(self.amount, self.currency, rounding=rounding)
63 
64 class _Minimums(defaultdict):
65     def __missing__(self, currency):
66         exponent = website.db.one("SELECT get_currency_exponent(%s)", (currency,))
67         minimum = Money((D_CENT if exponent == 2 else Decimal(10) ** (-exponent)), currency)
68         self[currency] = minimum
69         return minimum
70 
71 class _Zeros(defaultdict):
72     def __missing__(self, currency):
73         minimum = Money.MINIMUMS[currency].amount
74         zero = Money((D_ZERO if minimum is D_CENT else minimum - minimum), currency)
75         self[currency] = zero
76         return zero
77 
78 
79 Money.__init__ = _Money_init
80 Money.__nonzero__ = Money.__bool__
81 Money.__eq__ = _Money_eq
82 Money.__iter__ = lambda m: iter((m.amount, m.currency))
83 Money.__repr__ = lambda m: '<Money "%s">' % m
84 Money.__str__ = lambda m: '%(amount)s %(currency)s' % m.__dict__
85 Money.__unicode__ = Money.__str__
86 Money.convert = _convert
87 Money.minimum = lambda m: Money.MINIMUMS[m.currency]
88 Money.MINIMUMS = _Minimums()
89 Money.round = _Money_round
90 Money.round_down = lambda m: m.round(ROUND_DOWN)
91 Money.round_up = lambda m: m.round(ROUND_UP)
92 Money.sum = classmethod(_sum)
93 Money.zero = lambda m: Money.ZEROS[m.currency]
94 Money.ZEROS = _Zeros()
95 
96 
97 class MoneyBasket(object):
98 
99     def __init__(self, *args, **decimals):
100         self.amounts = OrderedDict(
101             (currency, decimals.get(currency, Money.ZEROS[currency].amount))
102             for currency in CURRENCIES
103         )
104         for arg in args:
105             if isinstance(arg, Money):
106                 self.amounts[arg.currency] += arg.amount
107             else:
108                 for m in arg:
109                     self.amounts[m.currency] += m.amount
110 
111     def __getitem__(self, currency):
112         return Money(self.amounts[currency], currency)
113 
114     def __iter__(self):
115         return (Money(amount, currency) for currency, amount in self.amounts.items())
116 
117     def __eq__(self, other):
118         if isinstance(other, self.__class__):
119             return self.amounts == other.amounts
120         elif isinstance(other, Money):
121             return self.amounts == MoneyBasket(other).amounts
122         elif other == 0:
123             return all(v == 0 for v in self.amounts.values())
124         return False
125 
126     def __ne__(self, other):
127         return not self.__eq__(other)
128 
129     def _compare(self, op, other):
130         if isinstance(other, self.__class__):
131             return all(op(a, b) for a, b in zip(self.amounts.values(), other.amounts.values()))
132         elif isinstance(other, Money):
133             return op(self.amounts[other.currency], other.amount)
134         elif other == 0:
135             return any(op(v, 0) for v in self.amounts.values())
136         else:
137             raise TypeError(
138                 "can't compare %r and %r" % (self.__class__, other.__class__)
139             )
140 
141     def __ge__(self, other):
142         return self._compare(operator.ge, other)
143 
144     def __gt__(self, other):
145         return self._compare(operator.gt, other)
146 
147     def __add__(self, other):
148         if other is 0:
149             return self
150         r = self.__class__(**self.amounts)
151         if isinstance(other, self.__class__):
152             for currency, amount in other.amounts.items():
153                 if currency in r.amounts:
154                     r.amounts[currency] += amount
155                 else:
156                     r.amounts[currency] = amount
157         elif isinstance(other, Money):
158             currency = other.currency
159             if currency in r.amounts:
160                 r.amounts[currency] += other.amount
161             else:
162                 r.amounts[currency] = other.amount
163         elif other == 0:
164             return r
165         else:
166             raise TypeError(other)
167         return r
168 
169     def __radd__(self, other):
170         return self.__add__(other)
171 
172     def __sub__(self, other):
173         if other is 0:
174             return self
175         r = self.__class__(**self.amounts)
176         if isinstance(other, self.__class__):
177             for currency, v in other.amounts.items():
178                 if currency in r.amounts:
179                     r.amounts[currency] -= v
180                 else:
181                     r.amounts[currency] = -v
182         elif isinstance(other, Money):
183             currency = other.currency
184             if currency in r.amounts:
185                 r.amounts[currency] -= other.amount
186             else:
187                 r.amounts[currency] = -other.amount
188         else:
189             raise TypeError(other)
190         return r
191 
192     def __repr__(self):
193         return '%s[%s]' % (
194             self.__class__.__name__,
195             ', '.join('%s %s' % (a, c) for c, a in self.amounts.items() if a)
196         )
197 
198     def __bool__(self):
199         return any(v for v in self.amounts.values())
200 
201     __nonzero__ = __bool__
202 
203     def __setstate__(self, state):
204         """Backward-compatible unpickling
205 
206         The original version of `MoneyBasket` stored `Money` objects in its
207         `__dict__`, whereas the current version stores `Decimal`s in the
208         `amounts` attribute.
209         """
210         if 'amounts' in state:
211             self.__dict__ = state
212         else:
213             self.amounts = {m.currency: m.amount for m in state.values()}
214 
215     @property
216     def currencies_present(self):
217         return [k for k, v in self.amounts.items() if v > 0]
218 
219     def fuzzy_sum(self, currency, rounding=ROUND_UP):
220         a = Money.ZEROS[currency].amount
221         fuzzy = False
222         for m in self:
223             if m.currency == currency:
224                 a += m.amount
225             elif m.amount:
226                 a += m.convert(currency, rounding=None).amount
227                 fuzzy = True
228         r = Money(a, currency, rounding=rounding)
229         r.fuzzy = fuzzy
230         return r
231 
232 
233 def fetch_currency_exchange_rates(db):
234     currencies = set(db.one("SELECT array_to_json(enum_range(NULL::currency))"))
235     r = requests.get('https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml')
236     rates = xmltodict.parse(r.text)['gesmes:Envelope']['Cube']['Cube']['Cube']
237     for fx in rates:
238         currency = fx['@currency']
239         if currency not in currencies:
240             continue
241         db.run("""
242             INSERT INTO currency_exchange_rates
243                         (source_currency, target_currency, rate)
244                  VALUES ('EUR', %(target)s, %(rate)s)
245                       , (%(target)s, 'EUR', 1 / %(rate)s)
246             ON CONFLICT (source_currency, target_currency) DO UPDATE
247                     SET rate = excluded.rate
248         """, dict(target=currency, rate=Decimal(fx['@rate'])))
249 
250 
251 def get_currency_exchange_rates(db):
252     r = {(r[0], r[1]): r[2] for r in db.all("SELECT * FROM currency_exchange_rates")}
253     if r:
254         return r
255     fetch_currency_exchange_rates(db)
256     return get_currency_exchange_rates(db)
257
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/liberapay/utils/currencies.py b/liberapay/utils/currencies.py
--- a/liberapay/utils/currencies.py
+++ b/liberapay/utils/currencies.py
@@ -51,7 +51,7 @@
 
 def _Money_eq(self, other):
     if isinstance(other, self.__class__):
-        return self.__dict__ == other.__dict__
+        return self.amount == other.amount and self.currency == other.currency
     if isinstance(other, (Decimal, Number)):
         return self.amount == other
     if isinstance(other, MoneyBasket):

{"golden_diff": "diff --git a/liberapay/utils/currencies.py b/liberapay/utils/currencies.py\n--- a/liberapay/utils/currencies.py\n+++ b/liberapay/utils/currencies.py\n@@ -51,7 +51,7 @@\n \n def _Money_eq(self, other):\n     if isinstance(other, self.__class__):\n-        return self.__dict__ == other.__dict__\n+        return self.amount == other.amount and self.currency == other.currency\n     if isinstance(other, (Decimal, Number)):\n         return self.amount == other\n     if isinstance(other, MoneyBasket):\n", "issue": "Bug during payment modification\nI try to donate to this account: https://liberapay.com/lowtechlab/ after a previous promise on the twitter account.\r\n\r\nlowtechlab created its account on the plateform but if I see that I donate to it, the actual number of mecene is still 0.\r\n\r\nI tried to change the amount since I know that with the new system we need to do a payment in advance but I had a 500 error with the following error code: `fc677c3e6c54434ba77bcf1eac628a18`\n", "before_files": [{"content": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom collections import defaultdict, OrderedDict\nfrom decimal import Decimal, ROUND_DOWN, ROUND_HALF_UP, ROUND_UP\nfrom numbers import Number\nimport operator\n\nfrom mangopay.exceptions import CurrencyMismatch\nfrom mangopay.utils import Money\nimport requests\nimport xmltodict\n\nfrom liberapay.constants import CURRENCIES, D_CENT, D_ZERO\nfrom liberapay.website import website\n\n\ndef _convert(self, c, rounding=ROUND_HALF_UP):\n    if self.currency == c:\n        return self\n    if 'EUR' in (self.currency, c):\n        rate = website.currency_exchange_rates[(self.currency, c)]\n    else:\n        rate = (\n            website.currency_exchange_rates[(self.currency, 'EUR')] *\n            website.currency_exchange_rates[('EUR', c)]\n        )\n    amount = self.amount * rate\n    return Money(amount, c, rounding=rounding)\n\ndef _sum(cls, amounts, currency):\n    a = Money.ZEROS[currency].amount\n    for m in amounts:\n        if m.currency != currency:\n            raise CurrencyMismatch(m.currency, currency, 'sum')\n        a += m.amount\n    return cls(a, currency)\n\ndef _Money_init(self, amount=Decimal('0'), currency=None, rounding=None):\n    if not isinstance(amount, Decimal):\n        amount = Decimal(str(amount))\n        # Why `str(amount)`? Because:\n        # >>> Decimal(0.23)\n        # Decimal('0.2300000000000000099920072216264088638126850128173828125')\n        # >>> Decimal(str(0.23))\n        # Decimal('0.23')\n    if rounding is not None:\n        minimum = Money.MINIMUMS[currency].amount\n        amount = amount.quantize(minimum, rounding=rounding)\n    self.amount = amount\n    self.currency = currency\n\ndef _Money_eq(self, other):\n    if isinstance(other, self.__class__):\n        return self.__dict__ == other.__dict__\n    if isinstance(other, (Decimal, Number)):\n        return self.amount == other\n    if isinstance(other, MoneyBasket):\n        return other.__eq__(self)\n    return False\n\ndef _Money_round(self, rounding=ROUND_HALF_UP):\n    return Money(self.amount, self.currency, rounding=rounding)\n\nclass _Minimums(defaultdict):\n    def __missing__(self, currency):\n        exponent = website.db.one(\"SELECT get_currency_exponent(%s)\", (currency,))\n        minimum = Money((D_CENT if exponent == 2 else Decimal(10) ** (-exponent)), currency)\n        self[currency] = minimum\n        return minimum\n\nclass _Zeros(defaultdict):\n    def __missing__(self, currency):\n        minimum = Money.MINIMUMS[currency].amount\n        zero = Money((D_ZERO if minimum is D_CENT else minimum - minimum), currency)\n        self[currency] = zero\n        return zero\n\n\nMoney.__init__ = _Money_init\nMoney.__nonzero__ = Money.__bool__\nMoney.__eq__ = _Money_eq\nMoney.__iter__ = lambda m: iter((m.amount, m.currency))\nMoney.__repr__ = lambda m: '<Money \"%s\">' % m\nMoney.__str__ = lambda m: '%(amount)s %(currency)s' % m.__dict__\nMoney.__unicode__ = Money.__str__\nMoney.convert = _convert\nMoney.minimum = lambda m: Money.MINIMUMS[m.currency]\nMoney.MINIMUMS = _Minimums()\nMoney.round = _Money_round\nMoney.round_down = lambda m: m.round(ROUND_DOWN)\nMoney.round_up = lambda m: m.round(ROUND_UP)\nMoney.sum = classmethod(_sum)\nMoney.zero = lambda m: Money.ZEROS[m.currency]\nMoney.ZEROS = _Zeros()\n\n\nclass MoneyBasket(object):\n\n    def __init__(self, *args, **decimals):\n        self.amounts = OrderedDict(\n            (currency, decimals.get(currency, Money.ZEROS[currency].amount))\n            for currency in CURRENCIES\n        )\n        for arg in args:\n            if isinstance(arg, Money):\n                self.amounts[arg.currency] += arg.amount\n            else:\n                for m in arg:\n                    self.amounts[m.currency] += m.amount\n\n    def __getitem__(self, currency):\n        return Money(self.amounts[currency], currency)\n\n    def __iter__(self):\n        return (Money(amount, currency) for currency, amount in self.amounts.items())\n\n    def __eq__(self, other):\n        if isinstance(other, self.__class__):\n            return self.amounts == other.amounts\n        elif isinstance(other, Money):\n            return self.amounts == MoneyBasket(other).amounts\n        elif other == 0:\n            return all(v == 0 for v in self.amounts.values())\n        return False\n\n    def __ne__(self, other):\n        return not self.__eq__(other)\n\n    def _compare(self, op, other):\n        if isinstance(other, self.__class__):\n            return all(op(a, b) for a, b in zip(self.amounts.values(), other.amounts.values()))\n        elif isinstance(other, Money):\n            return op(self.amounts[other.currency], other.amount)\n        elif other == 0:\n            return any(op(v, 0) for v in self.amounts.values())\n        else:\n            raise TypeError(\n                \"can't compare %r and %r\" % (self.__class__, other.__class__)\n            )\n\n    def __ge__(self, other):\n        return self._compare(operator.ge, other)\n\n    def __gt__(self, other):\n        return self._compare(operator.gt, other)\n\n    def __add__(self, other):\n        if other is 0:\n            return self\n        r = self.__class__(**self.amounts)\n        if isinstance(other, self.__class__):\n            for currency, amount in other.amounts.items():\n                if currency in r.amounts:\n                    r.amounts[currency] += amount\n                else:\n                    r.amounts[currency] = amount\n        elif isinstance(other, Money):\n            currency = other.currency\n            if currency in r.amounts:\n                r.amounts[currency] += other.amount\n            else:\n                r.amounts[currency] = other.amount\n        elif other == 0:\n            return r\n        else:\n            raise TypeError(other)\n        return r\n\n    def __radd__(self, other):\n        return self.__add__(other)\n\n    def __sub__(self, other):\n        if other is 0:\n            return self\n        r = self.__class__(**self.amounts)\n        if isinstance(other, self.__class__):\n            for currency, v in other.amounts.items():\n                if currency in r.amounts:\n                    r.amounts[currency] -= v\n                else:\n                    r.amounts[currency] = -v\n        elif isinstance(other, Money):\n            currency = other.currency\n            if currency in r.amounts:\n                r.amounts[currency] -= other.amount\n            else:\n                r.amounts[currency] = -other.amount\n        else:\n            raise TypeError(other)\n        return r\n\n    def __repr__(self):\n        return '%s[%s]' % (\n            self.__class__.__name__,\n            ', '.join('%s %s' % (a, c) for c, a in self.amounts.items() if a)\n        )\n\n    def __bool__(self):\n        return any(v for v in self.amounts.values())\n\n    __nonzero__ = __bool__\n\n    def __setstate__(self, state):\n        \"\"\"Backward-compatible unpickling\n\n        The original version of `MoneyBasket` stored `Money` objects in its\n        `__dict__`, whereas the current version stores `Decimal`s in the\n        `amounts` attribute.\n        \"\"\"\n        if 'amounts' in state:\n            self.__dict__ = state\n        else:\n            self.amounts = {m.currency: m.amount for m in state.values()}\n\n    @property\n    def currencies_present(self):\n        return [k for k, v in self.amounts.items() if v > 0]\n\n    def fuzzy_sum(self, currency, rounding=ROUND_UP):\n        a = Money.ZEROS[currency].amount\n        fuzzy = False\n        for m in self:\n            if m.currency == currency:\n                a += m.amount\n            elif m.amount:\n                a += m.convert(currency, rounding=None).amount\n                fuzzy = True\n        r = Money(a, currency, rounding=rounding)\n        r.fuzzy = fuzzy\n        return r\n\n\ndef fetch_currency_exchange_rates(db):\n    currencies = set(db.one(\"SELECT array_to_json(enum_range(NULL::currency))\"))\n    r = requests.get('https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml')\n    rates = xmltodict.parse(r.text)['gesmes:Envelope']['Cube']['Cube']['Cube']\n    for fx in rates:\n        currency = fx['@currency']\n        if currency not in currencies:\n            continue\n        db.run(\"\"\"\n            INSERT INTO currency_exchange_rates\n                        (source_currency, target_currency, rate)\n                 VALUES ('EUR', %(target)s, %(rate)s)\n                      , (%(target)s, 'EUR', 1 / %(rate)s)\n            ON CONFLICT (source_currency, target_currency) DO UPDATE\n                    SET rate = excluded.rate\n        \"\"\", dict(target=currency, rate=Decimal(fx['@rate'])))\n\n\ndef get_currency_exchange_rates(db):\n    r = {(r[0], r[1]): r[2] for r in db.all(\"SELECT * FROM currency_exchange_rates\")}\n    if r:\n        return r\n    fetch_currency_exchange_rates(db)\n    return get_currency_exchange_rates(db)\n", "path": "liberapay/utils/currencies.py"}], "after_files": [{"content": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom collections import defaultdict, OrderedDict\nfrom decimal import Decimal, ROUND_DOWN, ROUND_HALF_UP, ROUND_UP\nfrom numbers import Number\nimport operator\n\nfrom mangopay.exceptions import CurrencyMismatch\nfrom mangopay.utils import Money\nimport requests\nimport xmltodict\n\nfrom liberapay.constants import CURRENCIES, D_CENT, D_ZERO\nfrom liberapay.website import website\n\n\ndef _convert(self, c, rounding=ROUND_HALF_UP):\n    if self.currency == c:\n        return self\n    if 'EUR' in (self.currency, c):\n        rate = website.currency_exchange_rates[(self.currency, c)]\n    else:\n        rate = (\n            website.currency_exchange_rates[(self.currency, 'EUR')] *\n            website.currency_exchange_rates[('EUR', c)]\n        )\n    amount = self.amount * rate\n    return Money(amount, c, rounding=rounding)\n\ndef _sum(cls, amounts, currency):\n    a = Money.ZEROS[currency].amount\n    for m in amounts:\n        if m.currency != currency:\n            raise CurrencyMismatch(m.currency, currency, 'sum')\n        a += m.amount\n    return cls(a, currency)\n\ndef _Money_init(self, amount=Decimal('0'), currency=None, rounding=None):\n    if not isinstance(amount, Decimal):\n        amount = Decimal(str(amount))\n        # Why `str(amount)`? Because:\n        # >>> Decimal(0.23)\n        # Decimal('0.2300000000000000099920072216264088638126850128173828125')\n        # >>> Decimal(str(0.23))\n        # Decimal('0.23')\n    if rounding is not None:\n        minimum = Money.MINIMUMS[currency].amount\n        amount = amount.quantize(minimum, rounding=rounding)\n    self.amount = amount\n    self.currency = currency\n\ndef _Money_eq(self, other):\n    if isinstance(other, self.__class__):\n        return self.amount == other.amount and self.currency == other.currency\n    if isinstance(other, (Decimal, Number)):\n        return self.amount == other\n    if isinstance(other, MoneyBasket):\n        return other.__eq__(self)\n    return False\n\ndef _Money_round(self, rounding=ROUND_HALF_UP):\n    return Money(self.amount, self.currency, rounding=rounding)\n\nclass _Minimums(defaultdict):\n    def __missing__(self, currency):\n        exponent = website.db.one(\"SELECT get_currency_exponent(%s)\", (currency,))\n        minimum = Money((D_CENT if exponent == 2 else Decimal(10) ** (-exponent)), currency)\n        self[currency] = minimum\n        return minimum\n\nclass _Zeros(defaultdict):\n    def __missing__(self, currency):\n        minimum = Money.MINIMUMS[currency].amount\n        zero = Money((D_ZERO if minimum is D_CENT else minimum - minimum), currency)\n        self[currency] = zero\n        return zero\n\n\nMoney.__init__ = _Money_init\nMoney.__nonzero__ = Money.__bool__\nMoney.__eq__ = _Money_eq\nMoney.__iter__ = lambda m: iter((m.amount, m.currency))\nMoney.__repr__ = lambda m: '<Money \"%s\">' % m\nMoney.__str__ = lambda m: '%(amount)s %(currency)s' % m.__dict__\nMoney.__unicode__ = Money.__str__\nMoney.convert = _convert\nMoney.minimum = lambda m: Money.MINIMUMS[m.currency]\nMoney.MINIMUMS = _Minimums()\nMoney.round = _Money_round\nMoney.round_down = lambda m: m.round(ROUND_DOWN)\nMoney.round_up = lambda m: m.round(ROUND_UP)\nMoney.sum = classmethod(_sum)\nMoney.zero = lambda m: Money.ZEROS[m.currency]\nMoney.ZEROS = _Zeros()\n\n\nclass MoneyBasket(object):\n\n    def __init__(self, *args, **decimals):\n        self.amounts = OrderedDict(\n            (currency, decimals.get(currency, Money.ZEROS[currency].amount))\n            for currency in CURRENCIES\n        )\n        for arg in args:\n            if isinstance(arg, Money):\n                self.amounts[arg.currency] += arg.amount\n            else:\n                for m in arg:\n                    self.amounts[m.currency] += m.amount\n\n    def __getitem__(self, currency):\n        return Money(self.amounts[currency], currency)\n\n    def __iter__(self):\n        return (Money(amount, currency) for currency, amount in self.amounts.items())\n\n    def __eq__(self, other):\n        if isinstance(other, self.__class__):\n            return self.amounts == other.amounts\n        elif isinstance(other, Money):\n            return self.amounts == MoneyBasket(other).amounts\n        elif other == 0:\n            return all(v == 0 for v in self.amounts.values())\n        return False\n\n    def __ne__(self, other):\n        return not self.__eq__(other)\n\n    def _compare(self, op, other):\n        if isinstance(other, self.__class__):\n            return all(op(a, b) for a, b in zip(self.amounts.values(), other.amounts.values()))\n        elif isinstance(other, Money):\n            return op(self.amounts[other.currency], other.amount)\n        elif other == 0:\n            return any(op(v, 0) for v in self.amounts.values())\n        else:\n            raise TypeError(\n                \"can't compare %r and %r\" % (self.__class__, other.__class__)\n            )\n\n    def __ge__(self, other):\n        return self._compare(operator.ge, other)\n\n    def __gt__(self, other):\n        return self._compare(operator.gt, other)\n\n    def __add__(self, other):\n        if other is 0:\n            return self\n        r = self.__class__(**self.amounts)\n        if isinstance(other, self.__class__):\n            for currency, amount in other.amounts.items():\n                if currency in r.amounts:\n                    r.amounts[currency] += amount\n                else:\n                    r.amounts[currency] = amount\n        elif isinstance(other, Money):\n            currency = other.currency\n            if currency in r.amounts:\n                r.amounts[currency] += other.amount\n            else:\n                r.amounts[currency] = other.amount\n        elif other == 0:\n            return r\n        else:\n            raise TypeError(other)\n        return r\n\n    def __radd__(self, other):\n        return self.__add__(other)\n\n    def __sub__(self, other):\n        if other is 0:\n            return self\n        r = self.__class__(**self.amounts)\n        if isinstance(other, self.__class__):\n            for currency, v in other.amounts.items():\n                if currency in r.amounts:\n                    r.amounts[currency] -= v\n                else:\n                    r.amounts[currency] = -v\n        elif isinstance(other, Money):\n            currency = other.currency\n            if currency in r.amounts:\n                r.amounts[currency] -= other.amount\n            else:\n                r.amounts[currency] = -other.amount\n        else:\n            raise TypeError(other)\n        return r\n\n    def __repr__(self):\n        return '%s[%s]' % (\n            self.__class__.__name__,\n            ', '.join('%s %s' % (a, c) for c, a in self.amounts.items() if a)\n        )\n\n    def __bool__(self):\n        return any(v for v in self.amounts.values())\n\n    __nonzero__ = __bool__\n\n    def __setstate__(self, state):\n        \"\"\"Backward-compatible unpickling\n\n        The original version of `MoneyBasket` stored `Money` objects in its\n        `__dict__`, whereas the current version stores `Decimal`s in the\n        `amounts` attribute.\n        \"\"\"\n        if 'amounts' in state:\n            self.__dict__ = state\n        else:\n            self.amounts = {m.currency: m.amount for m in state.values()}\n\n    @property\n    def currencies_present(self):\n        return [k for k, v in self.amounts.items() if v > 0]\n\n    def fuzzy_sum(self, currency, rounding=ROUND_UP):\n        a = Money.ZEROS[currency].amount\n        fuzzy = False\n        for m in self:\n            if m.currency == currency:\n                a += m.amount\n            elif m.amount:\n                a += m.convert(currency, rounding=None).amount\n                fuzzy = True\n        r = Money(a, currency, rounding=rounding)\n        r.fuzzy = fuzzy\n        return r\n\n\ndef fetch_currency_exchange_rates(db):\n    currencies = set(db.one(\"SELECT array_to_json(enum_range(NULL::currency))\"))\n    r = requests.get('https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml')\n    rates = xmltodict.parse(r.text)['gesmes:Envelope']['Cube']['Cube']['Cube']\n    for fx in rates:\n        currency = fx['@currency']\n        if currency not in currencies:\n            continue\n        db.run(\"\"\"\n            INSERT INTO currency_exchange_rates\n                        (source_currency, target_currency, rate)\n                 VALUES ('EUR', %(target)s, %(rate)s)\n                      , (%(target)s, 'EUR', 1 / %(rate)s)\n            ON CONFLICT (source_currency, target_currency) DO UPDATE\n                    SET rate = excluded.rate\n        \"\"\", dict(target=currency, rate=Decimal(fx['@rate'])))\n\n\ndef get_currency_exchange_rates(db):\n    r = {(r[0], r[1]): r[2] for r in db.all(\"SELECT * FROM currency_exchange_rates\")}\n    if r:\n        return r\n    fetch_currency_exchange_rates(db)\n    return get_currency_exchange_rates(db)\n", "path": "liberapay/utils/currencies.py"}]}