We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
b64decode_s with unicode crashes.
Was working on https://github.com/gratipay/gratipay.com/pull/3965 when I ran into this.
According to http://stackoverflow.com/a/2230623/2643846

> b64decode quite explicitly can only take bytes (a string), not unicode.

Steps to reproduce - 
1. `b64encode_s('TheEnter?prise')` => `VGhlRW50ZXI_cHJpc2U~`
2. `b64decode_s('VGhlRW50ZXI_cHJpc2U~')` => `TypeError: character mapping must return integer, None or unicode` (deep down in the stack trace)

b64decode_s with unicode crashes.
Was working on https://github.com/gratipay/gratipay.com/pull/3965 when I ran into this.
According to http://stackoverflow.com/a/2230623/2643846

> b64decode quite explicitly can only take bytes (a string), not unicode.

Steps to reproduce - 
1. `b64encode_s('TheEnter?prise')` => `VGhlRW50ZXI_cHJpc2U~`
2. `b64decode_s('VGhlRW50ZXI_cHJpc2U~')` => `TypeError: character mapping must return integer, None or unicode` (deep down in the stack trace)


--- 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/__init__.py`
Content:
```
1 # encoding: utf8
2 
3 from __future__ import absolute_import, division, print_function, unicode_literals
4 
5 from base64 import b64decode, b64encode
6 from binascii import hexlify
7 from datetime import date, datetime, timedelta
8 import fnmatch
9 import os
10 import pickle
11 import re
12 
13 from six.moves.urllib.parse import quote as urlquote
14 
15 from aspen import Response, json
16 from aspen.utils import to_rfc822, utcnow
17 from markupsafe import Markup
18 from postgres.cursors import SimpleCursorBase
19 
20 from liberapay.exceptions import AuthRequired
21 from liberapay.models.community import Community
22 from liberapay.utils.i18n import Money
23 from liberapay.website import website
24 
25 
26 BEGINNING_OF_EPOCH = to_rfc822(datetime(1970, 1, 1)).encode('ascii')
27 
28 
29 def get_participant(state, restrict=True, redirect_stub=True, allow_member=False):
30     """Given a Request, raise Response or return Participant.
31 
32     If restrict is True then we'll restrict access to owners and admins.
33 
34     """
35     request = state['request']
36     user = state['user']
37     slug = request.line.uri.path['username']
38     _ = state['_']
39 
40     if restrict and user.ANON:
41         raise AuthRequired
42 
43     if slug.startswith('~'):
44         thing = 'id'
45         value = slug[1:]
46         participant = user if user and str(user.id) == value else None
47     else:
48         thing = 'lower(username)'
49         value = slug.lower()
50         participant = user if user and user.username.lower() == value else None
51 
52     if participant is None:
53         from liberapay.models.participant import Participant  # avoid circular import
54         participant = Participant._from_thing(thing, value) if value else None
55         if participant is None or participant.kind == 'community':
56             raise Response(404)
57 
58     if request.method in ('GET', 'HEAD'):
59         if slug != participant.username:
60             canon = '/' + participant.username + request.line.uri[len(slug)+1:]
61             raise Response(302, headers={'Location': canon})
62 
63     status = participant.status
64     if status == 'closed':
65         if user.is_admin:
66             return participant
67         raise Response(410)
68     elif status == 'stub':
69         if redirect_stub:
70             to = participant.resolve_stub()
71             assert to
72             raise Response(302, headers={'Location': to})
73 
74     if restrict:
75         if participant != user:
76             if allow_member and participant.kind == 'group' and user.member_of(participant):
77                 pass
78             elif not user.is_admin:
79                 raise Response(403, _("You are not authorized to access this page."))
80 
81     return participant
82 
83 
84 def get_community(state, restrict=False):
85     request, response = state['request'], state['response']
86     user = state['user']
87     name = request.path['name']
88 
89     c = Community.from_name(name)
90     if request.method in ('GET', 'HEAD'):
91         if not c:
92             response.redirect('/for/new?name=' + urlquote(name))
93         if c.name != name:
94             response.redirect('/for/' + c.name + request.line.uri[5+len(name):])
95     elif not c:
96         raise Response(404)
97     elif user.ANON:
98         raise AuthRequired
99 
100     if restrict:
101         if user.ANON:
102             raise AuthRequired
103         if user.id != c.creator and not user.is_admin:
104             _ = state['_']
105             raise Response(403, _("You are not authorized to access this page."))
106 
107     return c
108 
109 
110 def b64decode_s(s, **kw):
111     udecode = lambda a: a.decode('utf8')
112     if s[:1] == b'.':
113         udecode = lambda a: a
114         s = s[1:]
115     s = s.replace(b'~', b'=')
116     try:
117         return udecode(b64decode(s, '-_'))
118     except Exception:
119         try:
120             # For retrocompatibility
121             return udecode(b64decode(s))
122         except Exception:
123             pass
124         if 'default' in kw:
125             return kw['default']
126         raise Response(400, "invalid base64 input")
127 
128 
129 def b64encode_s(s):
130     prefix = b''
131     if not isinstance(s, bytes):
132         s = s.encode('utf8')
133     else:
134         # Check whether the string is binary or already utf8
135         try:
136             s.decode('utf8')
137         except UnicodeError:
138             prefix = b'.'
139     return prefix + b64encode(s, b'-_').replace(b'=', b'~')
140 
141 
142 def update_global_stats(website):
143     website.gnusers = website.db.one("""
144         SELECT count(*)
145           FROM participants
146          WHERE status = 'active'
147            AND kind <> 'community';
148     """)
149     transfer_volume = website.db.one("""
150         SELECT coalesce(sum(amount), 0)
151           FROM current_tips
152          WHERE is_funded
153     """)
154     website.gmonthly_volume = Money(transfer_volume * 52 / 12, 'EUR')
155 
156 
157 def _execute(this, sql, params=[]):
158     print(sql.strip(), params)
159     super(SimpleCursorBase, this).execute(sql, params)
160 
161 def log_cursor(f):
162     "Prints sql and params to stdout. Works globaly so watch for threaded use."
163     def wrapper(*a, **kw):
164         try:
165             SimpleCursorBase.execute = _execute
166             ret = f(*a, **kw)
167         finally:
168             del SimpleCursorBase.execute
169         return ret
170     return wrapper
171 
172 
173 def excerpt_intro(text, length=175, append='…'):
174     if not text:
175         return ''
176     if len(text) > length:
177         return text[:length] + append
178     return text
179 
180 
181 def is_card_expired(exp_year, exp_month):
182     today = date.today()
183     cur_year, cur_month = today.year, today.month
184     return exp_year < cur_year or exp_year == cur_year and exp_month < cur_month
185 
186 
187 def set_cookie(cookies, key, value, expires=None, httponly=True, path=b'/'):
188     cookies[key] = value
189     cookie = cookies[key]
190     if expires:
191         if isinstance(expires, timedelta):
192             expires += utcnow()
193         if isinstance(expires, datetime):
194             expires = to_rfc822(expires).encode('ascii')
195         cookie[b'expires'] = expires
196     if httponly:
197         cookie[b'httponly'] = True
198     if path:
199         cookie[b'path'] = path
200     if website.canonical_domain:
201         cookie[b'domain'] = website.canonical_domain
202     if website.canonical_scheme == 'https':
203         cookie[b'secure'] = True
204 
205 
206 def erase_cookie(cookies, key, **kw):
207     set_cookie(cookies, key, '', BEGINNING_OF_EPOCH, **kw)
208 
209 
210 def to_javascript(obj):
211     """For when you want to inject an object into a <script> tag.
212     """
213     return json.dumps(obj).replace('</', '<\\/')
214 
215 
216 svg_attrs_re = re.compile(r'\s+(?:height|width|x|y|xmlns)=(["\']).*?\1')
217 
218 def include_svg(svg, height, width, x=None, y=None):
219     """For when you want to include an SVG in an HTML page or in another SVG.
220     """
221     assert svg.startswith('<svg')
222     i = svg.find('>')
223     assert i != -1
224     d = locals()
225     attrs = svg_attrs_re.sub('', svg[4:i])
226     for a in ('height', 'width', 'x', 'y'):
227         v = d[a]
228         if v is None:
229             continue
230         attrs += ' %s="%s"' % (a, v)
231     return Markup(svg[:4] + attrs + svg[i:])
232 
233 
234 def group_by(iterable, key):
235     r = {}
236     for obj in iterable:
237         try:
238             k = obj[key]
239         except KeyError:
240             continue
241         r.setdefault(k, []).append(obj)
242     return r
243 
244 
245 def find_files(directory, pattern):
246     for root, dirs, files in os.walk(directory):
247         for filename in fnmatch.filter(files, pattern):
248             yield os.path.join(root, filename)
249 
250 
251 def serialize(context):
252     for k, v in context.items():
253         if str(type(v)) == "<class 'psycopg2.extras.Record'>":
254             context[k] = v._asdict()
255     return b'\\x' + hexlify(pickle.dumps(context, 2))
256
```
--- 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/__init__.py b/liberapay/utils/__init__.py
--- a/liberapay/utils/__init__.py
+++ b/liberapay/utils/__init__.py
@@ -108,6 +108,16 @@
 
 
 def b64decode_s(s, **kw):
+    def error():
+        if 'default' in kw:
+            return kw['default']
+        raise Response(400, "invalid base64 input")
+
+    try:
+        s = s.encode('ascii')
+    except UnicodeError:
+        return error()
+
     udecode = lambda a: a.decode('utf8')
     if s[:1] == b'.':
         udecode = lambda a: a
@@ -121,9 +131,7 @@
             return udecode(b64decode(s))
         except Exception:
             pass
-        if 'default' in kw:
-            return kw['default']
-        raise Response(400, "invalid base64 input")
+        return error()
 
 
 def b64encode_s(s):

{"golden_diff": "diff --git a/liberapay/utils/__init__.py b/liberapay/utils/__init__.py\n--- a/liberapay/utils/__init__.py\n+++ b/liberapay/utils/__init__.py\n@@ -108,6 +108,16 @@\n \n \n def b64decode_s(s, **kw):\n+    def error():\n+        if 'default' in kw:\n+            return kw['default']\n+        raise Response(400, \"invalid base64 input\")\n+\n+    try:\n+        s = s.encode('ascii')\n+    except UnicodeError:\n+        return error()\n+\n     udecode = lambda a: a.decode('utf8')\n     if s[:1] == b'.':\n         udecode = lambda a: a\n@@ -121,9 +131,7 @@\n             return udecode(b64decode(s))\n         except Exception:\n             pass\n-        if 'default' in kw:\n-            return kw['default']\n-        raise Response(400, \"invalid base64 input\")\n+        return error()\n \n \n def b64encode_s(s):\n", "issue": "b64decode_s with unicode crashes.\nWas working on https://github.com/gratipay/gratipay.com/pull/3965 when I ran into this.\nAccording to http://stackoverflow.com/a/2230623/2643846\n\n> b64decode quite explicitly can only take bytes (a string), not unicode.\n\nSteps to reproduce - \n1. `b64encode_s('TheEnter?prise')` => `VGhlRW50ZXI_cHJpc2U~`\n2. `b64decode_s('VGhlRW50ZXI_cHJpc2U~')` => `TypeError: character mapping must return integer, None or unicode` (deep down in the stack trace)\n\nb64decode_s with unicode crashes.\nWas working on https://github.com/gratipay/gratipay.com/pull/3965 when I ran into this.\nAccording to http://stackoverflow.com/a/2230623/2643846\n\n> b64decode quite explicitly can only take bytes (a string), not unicode.\n\nSteps to reproduce - \n1. `b64encode_s('TheEnter?prise')` => `VGhlRW50ZXI_cHJpc2U~`\n2. `b64decode_s('VGhlRW50ZXI_cHJpc2U~')` => `TypeError: character mapping must return integer, None or unicode` (deep down in the stack trace)\n\n", "before_files": [{"content": "# encoding: utf8\n\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom base64 import b64decode, b64encode\nfrom binascii import hexlify\nfrom datetime import date, datetime, timedelta\nimport fnmatch\nimport os\nimport pickle\nimport re\n\nfrom six.moves.urllib.parse import quote as urlquote\n\nfrom aspen import Response, json\nfrom aspen.utils import to_rfc822, utcnow\nfrom markupsafe import Markup\nfrom postgres.cursors import SimpleCursorBase\n\nfrom liberapay.exceptions import AuthRequired\nfrom liberapay.models.community import Community\nfrom liberapay.utils.i18n import Money\nfrom liberapay.website import website\n\n\nBEGINNING_OF_EPOCH = to_rfc822(datetime(1970, 1, 1)).encode('ascii')\n\n\ndef get_participant(state, restrict=True, redirect_stub=True, allow_member=False):\n    \"\"\"Given a Request, raise Response or return Participant.\n\n    If restrict is True then we'll restrict access to owners and admins.\n\n    \"\"\"\n    request = state['request']\n    user = state['user']\n    slug = request.line.uri.path['username']\n    _ = state['_']\n\n    if restrict and user.ANON:\n        raise AuthRequired\n\n    if slug.startswith('~'):\n        thing = 'id'\n        value = slug[1:]\n        participant = user if user and str(user.id) == value else None\n    else:\n        thing = 'lower(username)'\n        value = slug.lower()\n        participant = user if user and user.username.lower() == value else None\n\n    if participant is None:\n        from liberapay.models.participant import Participant  # avoid circular import\n        participant = Participant._from_thing(thing, value) if value else None\n        if participant is None or participant.kind == 'community':\n            raise Response(404)\n\n    if request.method in ('GET', 'HEAD'):\n        if slug != participant.username:\n            canon = '/' + participant.username + request.line.uri[len(slug)+1:]\n            raise Response(302, headers={'Location': canon})\n\n    status = participant.status\n    if status == 'closed':\n        if user.is_admin:\n            return participant\n        raise Response(410)\n    elif status == 'stub':\n        if redirect_stub:\n            to = participant.resolve_stub()\n            assert to\n            raise Response(302, headers={'Location': to})\n\n    if restrict:\n        if participant != user:\n            if allow_member and participant.kind == 'group' and user.member_of(participant):\n                pass\n            elif not user.is_admin:\n                raise Response(403, _(\"You are not authorized to access this page.\"))\n\n    return participant\n\n\ndef get_community(state, restrict=False):\n    request, response = state['request'], state['response']\n    user = state['user']\n    name = request.path['name']\n\n    c = Community.from_name(name)\n    if request.method in ('GET', 'HEAD'):\n        if not c:\n            response.redirect('/for/new?name=' + urlquote(name))\n        if c.name != name:\n            response.redirect('/for/' + c.name + request.line.uri[5+len(name):])\n    elif not c:\n        raise Response(404)\n    elif user.ANON:\n        raise AuthRequired\n\n    if restrict:\n        if user.ANON:\n            raise AuthRequired\n        if user.id != c.creator and not user.is_admin:\n            _ = state['_']\n            raise Response(403, _(\"You are not authorized to access this page.\"))\n\n    return c\n\n\ndef b64decode_s(s, **kw):\n    udecode = lambda a: a.decode('utf8')\n    if s[:1] == b'.':\n        udecode = lambda a: a\n        s = s[1:]\n    s = s.replace(b'~', b'=')\n    try:\n        return udecode(b64decode(s, '-_'))\n    except Exception:\n        try:\n            # For retrocompatibility\n            return udecode(b64decode(s))\n        except Exception:\n            pass\n        if 'default' in kw:\n            return kw['default']\n        raise Response(400, \"invalid base64 input\")\n\n\ndef b64encode_s(s):\n    prefix = b''\n    if not isinstance(s, bytes):\n        s = s.encode('utf8')\n    else:\n        # Check whether the string is binary or already utf8\n        try:\n            s.decode('utf8')\n        except UnicodeError:\n            prefix = b'.'\n    return prefix + b64encode(s, b'-_').replace(b'=', b'~')\n\n\ndef update_global_stats(website):\n    website.gnusers = website.db.one(\"\"\"\n        SELECT count(*)\n          FROM participants\n         WHERE status = 'active'\n           AND kind <> 'community';\n    \"\"\")\n    transfer_volume = website.db.one(\"\"\"\n        SELECT coalesce(sum(amount), 0)\n          FROM current_tips\n         WHERE is_funded\n    \"\"\")\n    website.gmonthly_volume = Money(transfer_volume * 52 / 12, 'EUR')\n\n\ndef _execute(this, sql, params=[]):\n    print(sql.strip(), params)\n    super(SimpleCursorBase, this).execute(sql, params)\n\ndef log_cursor(f):\n    \"Prints sql and params to stdout. Works globaly so watch for threaded use.\"\n    def wrapper(*a, **kw):\n        try:\n            SimpleCursorBase.execute = _execute\n            ret = f(*a, **kw)\n        finally:\n            del SimpleCursorBase.execute\n        return ret\n    return wrapper\n\n\ndef excerpt_intro(text, length=175, append='\u2026'):\n    if not text:\n        return ''\n    if len(text) > length:\n        return text[:length] + append\n    return text\n\n\ndef is_card_expired(exp_year, exp_month):\n    today = date.today()\n    cur_year, cur_month = today.year, today.month\n    return exp_year < cur_year or exp_year == cur_year and exp_month < cur_month\n\n\ndef set_cookie(cookies, key, value, expires=None, httponly=True, path=b'/'):\n    cookies[key] = value\n    cookie = cookies[key]\n    if expires:\n        if isinstance(expires, timedelta):\n            expires += utcnow()\n        if isinstance(expires, datetime):\n            expires = to_rfc822(expires).encode('ascii')\n        cookie[b'expires'] = expires\n    if httponly:\n        cookie[b'httponly'] = True\n    if path:\n        cookie[b'path'] = path\n    if website.canonical_domain:\n        cookie[b'domain'] = website.canonical_domain\n    if website.canonical_scheme == 'https':\n        cookie[b'secure'] = True\n\n\ndef erase_cookie(cookies, key, **kw):\n    set_cookie(cookies, key, '', BEGINNING_OF_EPOCH, **kw)\n\n\ndef to_javascript(obj):\n    \"\"\"For when you want to inject an object into a <script> tag.\n    \"\"\"\n    return json.dumps(obj).replace('</', '<\\\\/')\n\n\nsvg_attrs_re = re.compile(r'\\s+(?:height|width|x|y|xmlns)=([\"\\']).*?\\1')\n\ndef include_svg(svg, height, width, x=None, y=None):\n    \"\"\"For when you want to include an SVG in an HTML page or in another SVG.\n    \"\"\"\n    assert svg.startswith('<svg')\n    i = svg.find('>')\n    assert i != -1\n    d = locals()\n    attrs = svg_attrs_re.sub('', svg[4:i])\n    for a in ('height', 'width', 'x', 'y'):\n        v = d[a]\n        if v is None:\n            continue\n        attrs += ' %s=\"%s\"' % (a, v)\n    return Markup(svg[:4] + attrs + svg[i:])\n\n\ndef group_by(iterable, key):\n    r = {}\n    for obj in iterable:\n        try:\n            k = obj[key]\n        except KeyError:\n            continue\n        r.setdefault(k, []).append(obj)\n    return r\n\n\ndef find_files(directory, pattern):\n    for root, dirs, files in os.walk(directory):\n        for filename in fnmatch.filter(files, pattern):\n            yield os.path.join(root, filename)\n\n\ndef serialize(context):\n    for k, v in context.items():\n        if str(type(v)) == \"<class 'psycopg2.extras.Record'>\":\n            context[k] = v._asdict()\n    return b'\\\\x' + hexlify(pickle.dumps(context, 2))\n", "path": "liberapay/utils/__init__.py"}], "after_files": [{"content": "# encoding: utf8\n\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom base64 import b64decode, b64encode\nfrom binascii import hexlify\nfrom datetime import date, datetime, timedelta\nimport fnmatch\nimport os\nimport pickle\nimport re\n\nfrom six.moves.urllib.parse import quote as urlquote\n\nfrom aspen import Response, json\nfrom aspen.utils import to_rfc822, utcnow\nfrom markupsafe import Markup\nfrom postgres.cursors import SimpleCursorBase\n\nfrom liberapay.exceptions import AuthRequired\nfrom liberapay.models.community import Community\nfrom liberapay.utils.i18n import Money\nfrom liberapay.website import website\n\n\nBEGINNING_OF_EPOCH = to_rfc822(datetime(1970, 1, 1)).encode('ascii')\n\n\ndef get_participant(state, restrict=True, redirect_stub=True, allow_member=False):\n    \"\"\"Given a Request, raise Response or return Participant.\n\n    If restrict is True then we'll restrict access to owners and admins.\n\n    \"\"\"\n    request = state['request']\n    user = state['user']\n    slug = request.line.uri.path['username']\n    _ = state['_']\n\n    if restrict and user.ANON:\n        raise AuthRequired\n\n    if slug.startswith('~'):\n        thing = 'id'\n        value = slug[1:]\n        participant = user if user and str(user.id) == value else None\n    else:\n        thing = 'lower(username)'\n        value = slug.lower()\n        participant = user if user and user.username.lower() == value else None\n\n    if participant is None:\n        from liberapay.models.participant import Participant  # avoid circular import\n        participant = Participant._from_thing(thing, value) if value else None\n        if participant is None or participant.kind == 'community':\n            raise Response(404)\n\n    if request.method in ('GET', 'HEAD'):\n        if slug != participant.username:\n            canon = '/' + participant.username + request.line.uri[len(slug)+1:]\n            raise Response(302, headers={'Location': canon})\n\n    status = participant.status\n    if status == 'closed':\n        if user.is_admin:\n            return participant\n        raise Response(410)\n    elif status == 'stub':\n        if redirect_stub:\n            to = participant.resolve_stub()\n            assert to\n            raise Response(302, headers={'Location': to})\n\n    if restrict:\n        if participant != user:\n            if allow_member and participant.kind == 'group' and user.member_of(participant):\n                pass\n            elif not user.is_admin:\n                raise Response(403, _(\"You are not authorized to access this page.\"))\n\n    return participant\n\n\ndef get_community(state, restrict=False):\n    request, response = state['request'], state['response']\n    user = state['user']\n    name = request.path['name']\n\n    c = Community.from_name(name)\n    if request.method in ('GET', 'HEAD'):\n        if not c:\n            response.redirect('/for/new?name=' + urlquote(name))\n        if c.name != name:\n            response.redirect('/for/' + c.name + request.line.uri[5+len(name):])\n    elif not c:\n        raise Response(404)\n    elif user.ANON:\n        raise AuthRequired\n\n    if restrict:\n        if user.ANON:\n            raise AuthRequired\n        if user.id != c.creator and not user.is_admin:\n            _ = state['_']\n            raise Response(403, _(\"You are not authorized to access this page.\"))\n\n    return c\n\n\ndef b64decode_s(s, **kw):\n    def error():\n        if 'default' in kw:\n            return kw['default']\n        raise Response(400, \"invalid base64 input\")\n\n    try:\n        s = s.encode('ascii')\n    except UnicodeError:\n        return error()\n\n    udecode = lambda a: a.decode('utf8')\n    if s[:1] == b'.':\n        udecode = lambda a: a\n        s = s[1:]\n    s = s.replace(b'~', b'=')\n    try:\n        return udecode(b64decode(s, '-_'))\n    except Exception:\n        try:\n            # For retrocompatibility\n            return udecode(b64decode(s))\n        except Exception:\n            pass\n        return error()\n\n\ndef b64encode_s(s):\n    prefix = b''\n    if not isinstance(s, bytes):\n        s = s.encode('utf8')\n    else:\n        # Check whether the string is binary or already utf8\n        try:\n            s.decode('utf8')\n        except UnicodeError:\n            prefix = b'.'\n    return prefix + b64encode(s, b'-_').replace(b'=', b'~')\n\n\ndef update_global_stats(website):\n    website.gnusers = website.db.one(\"\"\"\n        SELECT count(*)\n          FROM participants\n         WHERE status = 'active'\n           AND kind <> 'community';\n    \"\"\")\n    transfer_volume = website.db.one(\"\"\"\n        SELECT coalesce(sum(amount), 0)\n          FROM current_tips\n         WHERE is_funded\n    \"\"\")\n    website.gmonthly_volume = Money(transfer_volume * 52 / 12, 'EUR')\n\n\ndef _execute(this, sql, params=[]):\n    print(sql.strip(), params)\n    super(SimpleCursorBase, this).execute(sql, params)\n\ndef log_cursor(f):\n    \"Prints sql and params to stdout. Works globaly so watch for threaded use.\"\n    def wrapper(*a, **kw):\n        try:\n            SimpleCursorBase.execute = _execute\n            ret = f(*a, **kw)\n        finally:\n            del SimpleCursorBase.execute\n        return ret\n    return wrapper\n\n\ndef excerpt_intro(text, length=175, append='\u2026'):\n    if not text:\n        return ''\n    if len(text) > length:\n        return text[:length] + append\n    return text\n\n\ndef is_card_expired(exp_year, exp_month):\n    today = date.today()\n    cur_year, cur_month = today.year, today.month\n    return exp_year < cur_year or exp_year == cur_year and exp_month < cur_month\n\n\ndef set_cookie(cookies, key, value, expires=None, httponly=True, path=b'/'):\n    cookies[key] = value\n    cookie = cookies[key]\n    if expires:\n        if isinstance(expires, timedelta):\n            expires += utcnow()\n        if isinstance(expires, datetime):\n            expires = to_rfc822(expires).encode('ascii')\n        cookie[b'expires'] = expires\n    if httponly:\n        cookie[b'httponly'] = True\n    if path:\n        cookie[b'path'] = path\n    if website.canonical_domain:\n        cookie[b'domain'] = website.canonical_domain\n    if website.canonical_scheme == 'https':\n        cookie[b'secure'] = True\n\n\ndef erase_cookie(cookies, key, **kw):\n    set_cookie(cookies, key, '', BEGINNING_OF_EPOCH, **kw)\n\n\ndef to_javascript(obj):\n    \"\"\"For when you want to inject an object into a <script> tag.\n    \"\"\"\n    return json.dumps(obj).replace('</', '<\\\\/')\n\n\nsvg_attrs_re = re.compile(r'\\s+(?:height|width|x|y|xmlns)=([\"\\']).*?\\1')\n\ndef include_svg(svg, height, width, x=None, y=None):\n    \"\"\"For when you want to include an SVG in an HTML page or in another SVG.\n    \"\"\"\n    assert svg.startswith('<svg')\n    i = svg.find('>')\n    assert i != -1\n    d = locals()\n    attrs = svg_attrs_re.sub('', svg[4:i])\n    for a in ('height', 'width', 'x', 'y'):\n        v = d[a]\n        if v is None:\n            continue\n        attrs += ' %s=\"%s\"' % (a, v)\n    return Markup(svg[:4] + attrs + svg[i:])\n\n\ndef group_by(iterable, key):\n    r = {}\n    for obj in iterable:\n        try:\n            k = obj[key]\n        except KeyError:\n            continue\n        r.setdefault(k, []).append(obj)\n    return r\n\n\ndef find_files(directory, pattern):\n    for root, dirs, files in os.walk(directory):\n        for filename in fnmatch.filter(files, pattern):\n            yield os.path.join(root, filename)\n\n\ndef serialize(context):\n    for k, v in context.items():\n        if str(type(v)) == \"<class 'psycopg2.extras.Record'>\":\n            context[k] = v._asdict()\n    return b'\\\\x' + hexlify(pickle.dumps(context, 2))\n", "path": "liberapay/utils/__init__.py"}]}