We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
SMTP exceptions aren't caught
- https://sentry.io/share/issue/3132343535362e313939323633313931/
- https://sentry.io/share/issue/3132343535362e313939323533393935/
- https://sentry.io/share/issue/3132343535362e313939323533383839/

```
SMTPServerDisconnected: please run connect() first
  File "site-packages/algorithm.py", line 321, in loop
    new_state = function(**deps.as_kwargs)
  File "liberapay/security/authentication.py", line 136, in authenticate_user_if_possible
    p = sign_in_with_form_data(body, state)
  File "liberapay/security/authentication.py", line 88, in sign_in_with_form_data
    p.add_email(email, cursor=c)
  File "liberapay/models/participant.py", line 618, in add_email
    r = self.send_email('verification', email=email, link=link.format(**locals()))
  File "liberapay/models/participant.py", line 733, in send_email
    n = website.mailer.send(**message)
  File "mailshake/mailers/base.py", line 41, in send
    return self.send_messages(EmailMessage(*args, **kwargs))
  File "mailshake/mailers/smtp.py", line 105, in send_messages
    sent = self._send(message)
  File "mailshake/mailers/smtp.py", line 126, in _send
    from_email, recipients, email_message.as_bytes()
  File "python2.7/smtplib.py", line 733, in sendmail
    (code, resp) = self.mail(from_addr, esmtp_opts)
  File "python2.7/smtplib.py", line 480, in mail
    self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist))
  File "python2.7/smtplib.py", line 341, in putcmd
    self.send(str)
  File "python2.7/smtplib.py", line 333, in send
    raise SMTPServerDisconnected('please run connect() first')
```

```
SMTPRecipientsRefused: {u"~3921 <winter@example.com|ping-n21127.0.0.1||`ping-c21127.0.0.1`#'|ping-n21127.0.0.1||`ping-c21127.0.0.1`#\\>": (501, 'Syntax error')}
  File "site-packages/algorithm.py", line 321, in loop
    new_state = function(**deps.as_kwargs)
  File "liberapay/security/authentication.py", line 136, in authenticate_user_if_possible
    p = sign_in_with_form_data(body, state)
  File "liberapay/security/authentication.py", line 88, in sign_in_with_form_data
    p.add_email(email, cursor=c)
  File "liberapay/models/participant.py", line 618, in add_email
    r = self.send_email('verification', email=email, link=link.format(**locals()))
  File "liberapay/models/participant.py", line 733, in send_email
    n = website.mailer.send(**message)
  File "mailshake/mailers/base.py", line 41, in send
    return self.send_messages(EmailMessage(*args, **kwargs))
  File "mailshake/mailers/smtp.py", line 105, in send_messages
    sent = self._send(message)
  File "mailshake/mailers/smtp.py", line 126, in _send
    from_email, recipients, email_message.as_bytes()
  File "python2.7/smtplib.py", line 747, in sendmail
    raise SMTPRecipientsRefused(senderrs)
```

```
SMTPDataError: (554, 'Recipient format is invalid: \'["winter@example.com\'and3405=3406--"]\'')
  File "site-packages/algorithm.py", line 321, in loop
    new_state = function(**deps.as_kwargs)
  File "liberapay/security/authentication.py", line 136, in authenticate_user_if_possible
    p = sign_in_with_form_data(body, state)
  File "liberapay/security/authentication.py", line 88, in sign_in_with_form_data
    p.add_email(email, cursor=c)
  File "liberapay/models/participant.py", line 618, in add_email
    r = self.send_email('verification', email=email, link=link.format(**locals()))
  File "liberapay/models/participant.py", line 733, in send_email
    n = website.mailer.send(**message)
  File "mailshake/mailers/base.py", line 41, in send
    return self.send_messages(EmailMessage(*args, **kwargs))
  File "mailshake/mailers/smtp.py", line 105, in send_messages
    sent = self._send(message)
  File "mailshake/mailers/smtp.py", line 126, in _send
    from_email, recipients, email_message.as_bytes()
  File "python2.7/smtplib.py", line 751, in sendmail
    raise SMTPDataError(code, resp)
```

--- 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/constants.py`
Content:
```
1 # coding: utf8
2 from __future__ import print_function, unicode_literals
3 
4 from collections import namedtuple, OrderedDict
5 from datetime import date, datetime, timedelta
6 from decimal import Decimal, ROUND_UP
7 import re
8 
9 from jinja2 import StrictUndefined
10 from pando.utils import utc
11 
12 
13 class CustomUndefined(StrictUndefined):
14     __bool__ = __nonzero__ = lambda self: False
15 
16     def __str__(self):
17         try:
18             self._fail_with_undefined_error()
19         except Exception as e:
20             self._tell_sentry(e, {})
21         return ''
22 
23     __unicode__ = __str__
24 
25 
26 def check_bits(bits):
27     assert len(set(bits)) == len(bits)  # no duplicates
28     assert not [b for b in bits if '{0:b}'.format(b).count('1') != 1]  # single bit
29 
30 
31 Event = namedtuple('Event', 'name bit title')
32 
33 Fees = namedtuple('Fees', ('var', 'fix'))
34 
35 
36 _ = lambda a: a
37 
38 ASCII_ALLOWED_IN_USERNAME = set("0123456789"
39                                 "abcdefghijklmnopqrstuvwxyz"
40                                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
41                                 "-_")
42 
43 AVATAR_QUERY = '?s=160&default=retro'
44 AVATAR_SOURCES = 'libravatar bitbucket facebook github google twitter'.split()
45 
46 BIRTHDAY = date(2015, 5, 22)
47 
48 D_CENT = Decimal('0.01')
49 D_INF = Decimal('inf')
50 D_UNIT = Decimal('1.00')
51 D_ZERO = Decimal('0.00')
52 
53 DONATION_LIMITS_WEEKLY = (Decimal('0.01'), Decimal('100.00'))
54 DONATION_LIMITS = {
55     'weekly': DONATION_LIMITS_WEEKLY,
56     'monthly': tuple((x * Decimal(52) / Decimal(12)).quantize(D_CENT, rounding=ROUND_UP)
57                      for x in DONATION_LIMITS_WEEKLY),
58     'yearly': tuple((x * Decimal(52)).quantize(D_CENT)
59                     for x in DONATION_LIMITS_WEEKLY),
60 }
61 DONATION_WEEKLY_MIN, DONATION_WEEKLY_MAX = DONATION_LIMITS_WEEKLY
62 
63 ELSEWHERE_ACTIONS = {'connect', 'lock', 'unlock'}
64 
65 EMAIL_VERIFICATION_TIMEOUT = timedelta(hours=24)
66 EMAIL_RE = re.compile(r'^[^@]+@[^@]+\.[^@]+$')
67 
68 EPOCH = datetime(1970, 1, 1, 0, 0, 0, 0, utc)
69 
70 EVENTS = [
71     Event('income', 1, _("When I receive money")),
72     Event('low_balance', 2, _("When there isn't enough money in my wallet to cover my donations")),
73     Event('withdrawal_created', 4, _("When a transfer to my bank account is initiated")),
74     Event('withdrawal_failed', 8, _("When a transfer to my bank account fails")),
75     Event('pledgee_joined', 16, _("When someone I pledge to joins Liberapay")),
76     Event('team_invite', 32, _("When someone invites me to join a team")),
77     Event('payin_bankwire_failed', 64, _("When a bank wire transfer to my Liberapay wallet fails")),
78     Event('payin_bankwire_succeeded', 128, _("When a bank wire transfer to my Liberapay wallet succeeds")),
79 ]
80 check_bits([e.bit for e in EVENTS])
81 EVENTS = OrderedDict((e.name, e) for e in EVENTS)
82 EVENTS_S = ' '.join(EVENTS.keys())
83 
84 # https://www.mangopay.com/pricing/
85 FEE_PAYIN_BANK_WIRE = Fees(Decimal('0.005'), Decimal(0))  # 0.5%
86 FEE_PAYIN_CARD = Fees(Decimal('0.018'), Decimal('0.18'))  # 1.8% + €0.18
87 FEE_PAYOUT = Fees(Decimal(0), Decimal(0))
88 FEE_PAYOUT_OUTSIDE_SEPA = Fees(Decimal(0), Decimal('2.5'))
89 FEE_PAYOUT_WARN = Decimal('0.03')  # warn user when fee exceeds 3%
90 FEE_VAT = Decimal('0.17')  # 17% (Luxembourg rate)
91 
92 JINJA_ENV_COMMON = dict(
93     trim_blocks=True, lstrip_blocks=True,
94     line_statement_prefix='%',
95     # undefined=CustomUndefined,
96 )
97 
98 # https://docs.mangopay.com/api-references/kyc-rules/
99 KYC_PAYIN_YEARLY_THRESHOLD = Decimal('2500')
100 KYC_PAYOUT_YEARLY_THRESHOLD = Decimal('1000')
101 
102 LAUNCH_TIME = datetime(2016, 2, 3, 12, 50, 0, 0, utc)
103 
104 PASSWORD_MIN_SIZE = 8
105 PASSWORD_MAX_SIZE = 150
106 
107 PAYIN_BANK_WIRE_MIN = Decimal('2.00')
108 PAYIN_CARD_MIN = Decimal("15.00")  # fee ≈ 3.5%
109 PAYIN_CARD_TARGET = Decimal("92.00")  # fee ≈ 2.33%
110 
111 PERIOD_CONVERSION_RATES = {
112     'weekly': Decimal(1),
113     'monthly': Decimal(12) / Decimal(52),
114     'yearly': Decimal(1) / Decimal(52),
115 }
116 
117 POSTAL_ADDRESS_KEYS = (
118     'AddressLine1', 'AddressLine2', 'City', 'Region', 'PostalCode', 'Country'
119 )
120 
121 PRIVACY_FIELDS = OrderedDict([
122     ('hide_giving', _("Hide total giving from others.")),
123     ('hide_receiving', _("Hide total receiving from others.")),
124     ('hide_from_search', _("Hide myself from search results on Liberapay.")),
125     ('profile_noindex', _("Tell web search engines not to index my profile.")),
126     ('hide_from_lists', _("Prevent my profile from being listed on Liberapay.")),
127 ])
128 PRIVACY_FIELDS_S = ' '.join(PRIVACY_FIELDS.keys())
129 
130 PRIVILEGES = dict(admin=1, run_payday=2)
131 check_bits(list(PRIVILEGES.values()))
132 
133 QUARANTINE = timedelta(weeks=4)
134 
135 SEPA_ZONE = set("""
136     AT BE BG CH CY CZ DE DK EE ES ES FI FR GB GI GR HR HU IE IS IT LI LT LU LV
137     MC MT NL NO PL PT RO SE SI SK
138 """.split())
139 
140 SESSION = str('session')  # bytes in python2, unicode in python3
141 SESSION_REFRESH = timedelta(hours=1)
142 SESSION_TIMEOUT = timedelta(hours=6)
143 
144 STANDARD_TIPS = (
145     (_("Symbolic ({0} per week)"), Decimal('0.01')),
146     (_("Small ({0} per week)"), Decimal('0.25')),
147     (_("Medium ({0} per week)"), Decimal('1.00')),
148     (_("Large ({0} per week)"), Decimal('5.00')),
149     (_("Maximum ({0} per week)"), DONATION_WEEKLY_MAX),
150 )
151 
152 USERNAME_MAX_SIZE = 32
153 
154 del _
155
```
--- 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/constants.py b/liberapay/constants.py
--- a/liberapay/constants.py
+++ b/liberapay/constants.py
@@ -63,7 +63,16 @@
 ELSEWHERE_ACTIONS = {'connect', 'lock', 'unlock'}
 
 EMAIL_VERIFICATION_TIMEOUT = timedelta(hours=24)
-EMAIL_RE = re.compile(r'^[^@]+@[^@]+\.[^@]+$')
+EMAIL_RE = re.compile(r'''
+    # This is the regexp used by MangoPay (as of February 2017).
+    # It rejects some valid but exotic addresses.
+    # https://en.wikipedia.org/wiki/Email_address
+    ^
+    [a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*
+    @
+    ([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?
+    $
+''', re.VERBOSE)
 
 EPOCH = datetime(1970, 1, 1, 0, 0, 0, 0, utc)

{"golden_diff": "diff --git a/liberapay/constants.py b/liberapay/constants.py\n--- a/liberapay/constants.py\n+++ b/liberapay/constants.py\n@@ -63,7 +63,16 @@\n ELSEWHERE_ACTIONS = {'connect', 'lock', 'unlock'}\n \n EMAIL_VERIFICATION_TIMEOUT = timedelta(hours=24)\n-EMAIL_RE = re.compile(r'^[^@]+@[^@]+\\.[^@]+$')\n+EMAIL_RE = re.compile(r'''\n+    # This is the regexp used by MangoPay (as of February 2017).\n+    # It rejects some valid but exotic addresses.\n+    # https://en.wikipedia.org/wiki/Email_address\n+    ^\n+    [a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*\n+    @\n+    ([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\n+    $\n+''', re.VERBOSE)\n \n EPOCH = datetime(1970, 1, 1, 0, 0, 0, 0, utc)\n", "issue": "SMTP exceptions aren't caught\n- https://sentry.io/share/issue/3132343535362e313939323633313931/\r\n- https://sentry.io/share/issue/3132343535362e313939323533393935/\r\n- https://sentry.io/share/issue/3132343535362e313939323533383839/\r\n\r\n```\r\nSMTPServerDisconnected: please run connect() first\r\n  File \"site-packages/algorithm.py\", line 321, in loop\r\n    new_state = function(**deps.as_kwargs)\r\n  File \"liberapay/security/authentication.py\", line 136, in authenticate_user_if_possible\r\n    p = sign_in_with_form_data(body, state)\r\n  File \"liberapay/security/authentication.py\", line 88, in sign_in_with_form_data\r\n    p.add_email(email, cursor=c)\r\n  File \"liberapay/models/participant.py\", line 618, in add_email\r\n    r = self.send_email('verification', email=email, link=link.format(**locals()))\r\n  File \"liberapay/models/participant.py\", line 733, in send_email\r\n    n = website.mailer.send(**message)\r\n  File \"mailshake/mailers/base.py\", line 41, in send\r\n    return self.send_messages(EmailMessage(*args, **kwargs))\r\n  File \"mailshake/mailers/smtp.py\", line 105, in send_messages\r\n    sent = self._send(message)\r\n  File \"mailshake/mailers/smtp.py\", line 126, in _send\r\n    from_email, recipients, email_message.as_bytes()\r\n  File \"python2.7/smtplib.py\", line 733, in sendmail\r\n    (code, resp) = self.mail(from_addr, esmtp_opts)\r\n  File \"python2.7/smtplib.py\", line 480, in mail\r\n    self.putcmd(\"mail\", \"FROM:%s%s\" % (quoteaddr(sender), optionlist))\r\n  File \"python2.7/smtplib.py\", line 341, in putcmd\r\n    self.send(str)\r\n  File \"python2.7/smtplib.py\", line 333, in send\r\n    raise SMTPServerDisconnected('please run connect() first')\r\n```\r\n\r\n```\r\nSMTPRecipientsRefused: {u\"~3921 <winter@example.com|ping-n21127.0.0.1||`ping-c21127.0.0.1`#'|ping-n21127.0.0.1||`ping-c21127.0.0.1`#\\\\>\": (501, 'Syntax error')}\r\n  File \"site-packages/algorithm.py\", line 321, in loop\r\n    new_state = function(**deps.as_kwargs)\r\n  File \"liberapay/security/authentication.py\", line 136, in authenticate_user_if_possible\r\n    p = sign_in_with_form_data(body, state)\r\n  File \"liberapay/security/authentication.py\", line 88, in sign_in_with_form_data\r\n    p.add_email(email, cursor=c)\r\n  File \"liberapay/models/participant.py\", line 618, in add_email\r\n    r = self.send_email('verification', email=email, link=link.format(**locals()))\r\n  File \"liberapay/models/participant.py\", line 733, in send_email\r\n    n = website.mailer.send(**message)\r\n  File \"mailshake/mailers/base.py\", line 41, in send\r\n    return self.send_messages(EmailMessage(*args, **kwargs))\r\n  File \"mailshake/mailers/smtp.py\", line 105, in send_messages\r\n    sent = self._send(message)\r\n  File \"mailshake/mailers/smtp.py\", line 126, in _send\r\n    from_email, recipients, email_message.as_bytes()\r\n  File \"python2.7/smtplib.py\", line 747, in sendmail\r\n    raise SMTPRecipientsRefused(senderrs)\r\n```\r\n\r\n```\r\nSMTPDataError: (554, 'Recipient format is invalid: \\'[\"winter@example.com\\'and3405=3406--\"]\\'')\r\n  File \"site-packages/algorithm.py\", line 321, in loop\r\n    new_state = function(**deps.as_kwargs)\r\n  File \"liberapay/security/authentication.py\", line 136, in authenticate_user_if_possible\r\n    p = sign_in_with_form_data(body, state)\r\n  File \"liberapay/security/authentication.py\", line 88, in sign_in_with_form_data\r\n    p.add_email(email, cursor=c)\r\n  File \"liberapay/models/participant.py\", line 618, in add_email\r\n    r = self.send_email('verification', email=email, link=link.format(**locals()))\r\n  File \"liberapay/models/participant.py\", line 733, in send_email\r\n    n = website.mailer.send(**message)\r\n  File \"mailshake/mailers/base.py\", line 41, in send\r\n    return self.send_messages(EmailMessage(*args, **kwargs))\r\n  File \"mailshake/mailers/smtp.py\", line 105, in send_messages\r\n    sent = self._send(message)\r\n  File \"mailshake/mailers/smtp.py\", line 126, in _send\r\n    from_email, recipients, email_message.as_bytes()\r\n  File \"python2.7/smtplib.py\", line 751, in sendmail\r\n    raise SMTPDataError(code, resp)\r\n```\n", "before_files": [{"content": "# coding: utf8\nfrom __future__ import print_function, unicode_literals\n\nfrom collections import namedtuple, OrderedDict\nfrom datetime import date, datetime, timedelta\nfrom decimal import Decimal, ROUND_UP\nimport re\n\nfrom jinja2 import StrictUndefined\nfrom pando.utils import utc\n\n\nclass CustomUndefined(StrictUndefined):\n    __bool__ = __nonzero__ = lambda self: False\n\n    def __str__(self):\n        try:\n            self._fail_with_undefined_error()\n        except Exception as e:\n            self._tell_sentry(e, {})\n        return ''\n\n    __unicode__ = __str__\n\n\ndef check_bits(bits):\n    assert len(set(bits)) == len(bits)  # no duplicates\n    assert not [b for b in bits if '{0:b}'.format(b).count('1') != 1]  # single bit\n\n\nEvent = namedtuple('Event', 'name bit title')\n\nFees = namedtuple('Fees', ('var', 'fix'))\n\n\n_ = lambda a: a\n\nASCII_ALLOWED_IN_USERNAME = set(\"0123456789\"\n                                \"abcdefghijklmnopqrstuvwxyz\"\n                                \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n                                \"-_\")\n\nAVATAR_QUERY = '?s=160&default=retro'\nAVATAR_SOURCES = 'libravatar bitbucket facebook github google twitter'.split()\n\nBIRTHDAY = date(2015, 5, 22)\n\nD_CENT = Decimal('0.01')\nD_INF = Decimal('inf')\nD_UNIT = Decimal('1.00')\nD_ZERO = Decimal('0.00')\n\nDONATION_LIMITS_WEEKLY = (Decimal('0.01'), Decimal('100.00'))\nDONATION_LIMITS = {\n    'weekly': DONATION_LIMITS_WEEKLY,\n    'monthly': tuple((x * Decimal(52) / Decimal(12)).quantize(D_CENT, rounding=ROUND_UP)\n                     for x in DONATION_LIMITS_WEEKLY),\n    'yearly': tuple((x * Decimal(52)).quantize(D_CENT)\n                    for x in DONATION_LIMITS_WEEKLY),\n}\nDONATION_WEEKLY_MIN, DONATION_WEEKLY_MAX = DONATION_LIMITS_WEEKLY\n\nELSEWHERE_ACTIONS = {'connect', 'lock', 'unlock'}\n\nEMAIL_VERIFICATION_TIMEOUT = timedelta(hours=24)\nEMAIL_RE = re.compile(r'^[^@]+@[^@]+\\.[^@]+$')\n\nEPOCH = datetime(1970, 1, 1, 0, 0, 0, 0, utc)\n\nEVENTS = [\n    Event('income', 1, _(\"When I receive money\")),\n    Event('low_balance', 2, _(\"When there isn't enough money in my wallet to cover my donations\")),\n    Event('withdrawal_created', 4, _(\"When a transfer to my bank account is initiated\")),\n    Event('withdrawal_failed', 8, _(\"When a transfer to my bank account fails\")),\n    Event('pledgee_joined', 16, _(\"When someone I pledge to joins Liberapay\")),\n    Event('team_invite', 32, _(\"When someone invites me to join a team\")),\n    Event('payin_bankwire_failed', 64, _(\"When a bank wire transfer to my Liberapay wallet fails\")),\n    Event('payin_bankwire_succeeded', 128, _(\"When a bank wire transfer to my Liberapay wallet succeeds\")),\n]\ncheck_bits([e.bit for e in EVENTS])\nEVENTS = OrderedDict((e.name, e) for e in EVENTS)\nEVENTS_S = ' '.join(EVENTS.keys())\n\n# https://www.mangopay.com/pricing/\nFEE_PAYIN_BANK_WIRE = Fees(Decimal('0.005'), Decimal(0))  # 0.5%\nFEE_PAYIN_CARD = Fees(Decimal('0.018'), Decimal('0.18'))  # 1.8% + \u20ac0.18\nFEE_PAYOUT = Fees(Decimal(0), Decimal(0))\nFEE_PAYOUT_OUTSIDE_SEPA = Fees(Decimal(0), Decimal('2.5'))\nFEE_PAYOUT_WARN = Decimal('0.03')  # warn user when fee exceeds 3%\nFEE_VAT = Decimal('0.17')  # 17% (Luxembourg rate)\n\nJINJA_ENV_COMMON = dict(\n    trim_blocks=True, lstrip_blocks=True,\n    line_statement_prefix='%',\n    # undefined=CustomUndefined,\n)\n\n# https://docs.mangopay.com/api-references/kyc-rules/\nKYC_PAYIN_YEARLY_THRESHOLD = Decimal('2500')\nKYC_PAYOUT_YEARLY_THRESHOLD = Decimal('1000')\n\nLAUNCH_TIME = datetime(2016, 2, 3, 12, 50, 0, 0, utc)\n\nPASSWORD_MIN_SIZE = 8\nPASSWORD_MAX_SIZE = 150\n\nPAYIN_BANK_WIRE_MIN = Decimal('2.00')\nPAYIN_CARD_MIN = Decimal(\"15.00\")  # fee \u2248 3.5%\nPAYIN_CARD_TARGET = Decimal(\"92.00\")  # fee \u2248 2.33%\n\nPERIOD_CONVERSION_RATES = {\n    'weekly': Decimal(1),\n    'monthly': Decimal(12) / Decimal(52),\n    'yearly': Decimal(1) / Decimal(52),\n}\n\nPOSTAL_ADDRESS_KEYS = (\n    'AddressLine1', 'AddressLine2', 'City', 'Region', 'PostalCode', 'Country'\n)\n\nPRIVACY_FIELDS = OrderedDict([\n    ('hide_giving', _(\"Hide total giving from others.\")),\n    ('hide_receiving', _(\"Hide total receiving from others.\")),\n    ('hide_from_search', _(\"Hide myself from search results on Liberapay.\")),\n    ('profile_noindex', _(\"Tell web search engines not to index my profile.\")),\n    ('hide_from_lists', _(\"Prevent my profile from being listed on Liberapay.\")),\n])\nPRIVACY_FIELDS_S = ' '.join(PRIVACY_FIELDS.keys())\n\nPRIVILEGES = dict(admin=1, run_payday=2)\ncheck_bits(list(PRIVILEGES.values()))\n\nQUARANTINE = timedelta(weeks=4)\n\nSEPA_ZONE = set(\"\"\"\n    AT BE BG CH CY CZ DE DK EE ES ES FI FR GB GI GR HR HU IE IS IT LI LT LU LV\n    MC MT NL NO PL PT RO SE SI SK\n\"\"\".split())\n\nSESSION = str('session')  # bytes in python2, unicode in python3\nSESSION_REFRESH = timedelta(hours=1)\nSESSION_TIMEOUT = timedelta(hours=6)\n\nSTANDARD_TIPS = (\n    (_(\"Symbolic ({0} per week)\"), Decimal('0.01')),\n    (_(\"Small ({0} per week)\"), Decimal('0.25')),\n    (_(\"Medium ({0} per week)\"), Decimal('1.00')),\n    (_(\"Large ({0} per week)\"), Decimal('5.00')),\n    (_(\"Maximum ({0} per week)\"), DONATION_WEEKLY_MAX),\n)\n\nUSERNAME_MAX_SIZE = 32\n\ndel _\n", "path": "liberapay/constants.py"}], "after_files": [{"content": "# coding: utf8\nfrom __future__ import print_function, unicode_literals\n\nfrom collections import namedtuple, OrderedDict\nfrom datetime import date, datetime, timedelta\nfrom decimal import Decimal, ROUND_UP\nimport re\n\nfrom jinja2 import StrictUndefined\nfrom pando.utils import utc\n\n\nclass CustomUndefined(StrictUndefined):\n    __bool__ = __nonzero__ = lambda self: False\n\n    def __str__(self):\n        try:\n            self._fail_with_undefined_error()\n        except Exception as e:\n            self._tell_sentry(e, {})\n        return ''\n\n    __unicode__ = __str__\n\n\ndef check_bits(bits):\n    assert len(set(bits)) == len(bits)  # no duplicates\n    assert not [b for b in bits if '{0:b}'.format(b).count('1') != 1]  # single bit\n\n\nEvent = namedtuple('Event', 'name bit title')\n\nFees = namedtuple('Fees', ('var', 'fix'))\n\n\n_ = lambda a: a\n\nASCII_ALLOWED_IN_USERNAME = set(\"0123456789\"\n                                \"abcdefghijklmnopqrstuvwxyz\"\n                                \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n                                \"-_\")\n\nAVATAR_QUERY = '?s=160&default=retro'\nAVATAR_SOURCES = 'libravatar bitbucket facebook github google twitter'.split()\n\nBIRTHDAY = date(2015, 5, 22)\n\nD_CENT = Decimal('0.01')\nD_INF = Decimal('inf')\nD_UNIT = Decimal('1.00')\nD_ZERO = Decimal('0.00')\n\nDONATION_LIMITS_WEEKLY = (Decimal('0.01'), Decimal('100.00'))\nDONATION_LIMITS = {\n    'weekly': DONATION_LIMITS_WEEKLY,\n    'monthly': tuple((x * Decimal(52) / Decimal(12)).quantize(D_CENT, rounding=ROUND_UP)\n                     for x in DONATION_LIMITS_WEEKLY),\n    'yearly': tuple((x * Decimal(52)).quantize(D_CENT)\n                    for x in DONATION_LIMITS_WEEKLY),\n}\nDONATION_WEEKLY_MIN, DONATION_WEEKLY_MAX = DONATION_LIMITS_WEEKLY\n\nELSEWHERE_ACTIONS = {'connect', 'lock', 'unlock'}\n\nEMAIL_VERIFICATION_TIMEOUT = timedelta(hours=24)\nEMAIL_RE = re.compile(r'''\n    # This is the regexp used by MangoPay (as of February 2017).\n    # It rejects some valid but exotic addresses.\n    # https://en.wikipedia.org/wiki/Email_address\n    ^\n    [a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*\n    @\n    ([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\n    $\n''', re.VERBOSE)\n\nEPOCH = datetime(1970, 1, 1, 0, 0, 0, 0, utc)\n\nEVENTS = [\n    Event('income', 1, _(\"When I receive money\")),\n    Event('low_balance', 2, _(\"When there isn't enough money in my wallet to cover my donations\")),\n    Event('withdrawal_created', 4, _(\"When a transfer to my bank account is initiated\")),\n    Event('withdrawal_failed', 8, _(\"When a transfer to my bank account fails\")),\n    Event('pledgee_joined', 16, _(\"When someone I pledge to joins Liberapay\")),\n    Event('team_invite', 32, _(\"When someone invites me to join a team\")),\n    Event('payin_bankwire_failed', 64, _(\"When a bank wire transfer to my Liberapay wallet fails\")),\n    Event('payin_bankwire_succeeded', 128, _(\"When a bank wire transfer to my Liberapay wallet succeeds\")),\n]\ncheck_bits([e.bit for e in EVENTS])\nEVENTS = OrderedDict((e.name, e) for e in EVENTS)\nEVENTS_S = ' '.join(EVENTS.keys())\n\n# https://www.mangopay.com/pricing/\nFEE_PAYIN_BANK_WIRE = Fees(Decimal('0.005'), Decimal(0))  # 0.5%\nFEE_PAYIN_CARD = Fees(Decimal('0.018'), Decimal('0.18'))  # 1.8% + \u20ac0.18\nFEE_PAYOUT = Fees(Decimal(0), Decimal(0))\nFEE_PAYOUT_OUTSIDE_SEPA = Fees(Decimal(0), Decimal('2.5'))\nFEE_PAYOUT_WARN = Decimal('0.03')  # warn user when fee exceeds 3%\nFEE_VAT = Decimal('0.17')  # 17% (Luxembourg rate)\n\nJINJA_ENV_COMMON = dict(\n    trim_blocks=True, lstrip_blocks=True,\n    line_statement_prefix='%',\n    # undefined=CustomUndefined,\n)\n\n# https://docs.mangopay.com/api-references/kyc-rules/\nKYC_PAYIN_YEARLY_THRESHOLD = Decimal('2500')\nKYC_PAYOUT_YEARLY_THRESHOLD = Decimal('1000')\n\nLAUNCH_TIME = datetime(2016, 2, 3, 12, 50, 0, 0, utc)\n\nPASSWORD_MIN_SIZE = 8\nPASSWORD_MAX_SIZE = 150\n\nPAYIN_BANK_WIRE_MIN = Decimal('2.00')\nPAYIN_CARD_MIN = Decimal(\"15.00\")  # fee \u2248 3.5%\nPAYIN_CARD_TARGET = Decimal(\"92.00\")  # fee \u2248 2.33%\n\nPERIOD_CONVERSION_RATES = {\n    'weekly': Decimal(1),\n    'monthly': Decimal(12) / Decimal(52),\n    'yearly': Decimal(1) / Decimal(52),\n}\n\nPOSTAL_ADDRESS_KEYS = (\n    'AddressLine1', 'AddressLine2', 'City', 'Region', 'PostalCode', 'Country'\n)\n\nPRIVACY_FIELDS = OrderedDict([\n    ('hide_giving', _(\"Hide total giving from others.\")),\n    ('hide_receiving', _(\"Hide total receiving from others.\")),\n    ('hide_from_search', _(\"Hide myself from search results on Liberapay.\")),\n    ('profile_noindex', _(\"Tell web search engines not to index my profile.\")),\n    ('hide_from_lists', _(\"Prevent my profile from being listed on Liberapay.\")),\n])\nPRIVACY_FIELDS_S = ' '.join(PRIVACY_FIELDS.keys())\n\nPRIVILEGES = dict(admin=1, run_payday=2)\ncheck_bits(list(PRIVILEGES.values()))\n\nQUARANTINE = timedelta(weeks=4)\n\nSEPA_ZONE = set(\"\"\"\n    AT BE BG CH CY CZ DE DK EE ES ES FI FR GB GI GR HR HU IE IS IT LI LT LU LV\n    MC MT NL NO PL PT RO SE SI SK\n\"\"\".split())\n\nSESSION = str('session')  # bytes in python2, unicode in python3\nSESSION_REFRESH = timedelta(hours=1)\nSESSION_TIMEOUT = timedelta(hours=6)\n\nSTANDARD_TIPS = (\n    (_(\"Symbolic ({0} per week)\"), Decimal('0.01')),\n    (_(\"Small ({0} per week)\"), Decimal('0.25')),\n    (_(\"Medium ({0} per week)\"), Decimal('1.00')),\n    (_(\"Large ({0} per week)\"), Decimal('5.00')),\n    (_(\"Maximum ({0} per week)\"), DONATION_WEEKLY_MAX),\n)\n\nUSERNAME_MAX_SIZE = 32\n\ndel _\n", "path": "liberapay/constants.py"}]}