We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Books with non-ascii titles erroring when clicked from search
### Evidence / Screenshot (if possible)

### Relevant url?
<!-- `https://openlibrary.org/...` -->

### Steps to Reproduce
<!-- What steps caused you to find the bug? -->
1. Go to https://openlibrary.org/search?q=h%C3%A9las&mode=everything
2. Click on any book with a non-ascii character in the title

<!-- What actually happened after these steps? What did you expect to happen? -->
* Actual: 500 internal error on e.g. https://openlibrary.org/works/OL11565520W?edition=
* Expected: See the book

### Details

- **Logged in (Y/N)?** Y
- **Browser type/version?** FF82
- **Operating system?** Win10
- **Environment (prod/dev/local)?** prod
<!-- If not sure, put prod -->

### Proposal & Constraints
<!-- What is the proposed solution / implementation? Is there a precedent of this approach succeeding elsewhere? -->

### Related files
<!-- Files related to this issue; this is super useful for new contributors who might want to help! If you're not sure, leave this blank; a maintainer will add them. -->

### Stakeholders
@cclauss 


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `openlibrary/core/processors/readableurls.py`
Content:
```
1 """Various web.py application processors used in OL.
2 """
3 import os
4 import web
5 
6 from infogami.utils.view import render
7 from openlibrary.core import helpers as h
8 
9 from six.moves import urllib
10 
11 
12 try:
13     from booklending_utils.openlibrary import is_exclusion
14 except ImportError:
15     def is_exclusion(obj):
16         """Processor for determining whether records require exclusion"""
17         return False
18 
19 class ReadableUrlProcessor:
20     """Open Library code works with urls like /books/OL1M and
21     /books/OL1M/edit. This processor seamlessly changes the urls to
22     /books/OL1M/title and /books/OL1M/title/edit.
23 
24     The changequery function is also customized to support this.
25     """
26     patterns = [
27         (r'/\w+/OL\d+M', '/type/edition', 'title', 'untitled'),
28         (r'/\w+/ia:[a-zA-Z0-9_\.-]+', '/type/edition', 'title', 'untitled'),
29         (r'/\w+/OL\d+A', '/type/author', 'name', 'noname'),
30         (r'/\w+/OL\d+W', '/type/work', 'title', 'untitled'),
31         (r'/[/\w]+/OL\d+L', '/type/list', 'name', 'unnamed')
32     ]
33 
34     def __call__(self, handler):
35         # temp hack to handle languages and users during upstream-to-www migration
36         if web.ctx.path.startswith("/l/"):
37             raise web.seeother("/languages/" + web.ctx.path[len("/l/"):])
38 
39         if web.ctx.path.startswith("/user/"):
40             if not web.ctx.site.get(web.ctx.path):
41                 raise web.seeother("/people/" + web.ctx.path[len("/user/"):])
42 
43         real_path, readable_path = get_readable_path(web.ctx.site, web.ctx.path, self.patterns, encoding=web.ctx.encoding)
44 
45         #@@ web.ctx.path is either quoted or unquoted depends on whether the application is running
46         #@@ using builtin-server or lighttpd. That is probably a bug in web.py.
47         #@@ take care of that case here till that is fixed.
48         # @@ Also, the redirection must be done only for GET requests.
49         if readable_path != web.ctx.path and readable_path != urllib.parse.quote(web.safestr(web.ctx.path)) and web.ctx.method == "GET":
50             raise web.redirect(web.safeunicode(readable_path) + web.safeunicode(web.ctx.query))
51 
52         web.ctx.readable_path = readable_path
53         web.ctx.path = real_path
54         web.ctx.fullpath = web.ctx.path + web.ctx.query
55         out = handler()
56         V2_TYPES = ['works', 'books', 'people', 'authors',
57                     'publishers', 'languages', 'account']
58         if out and any(web.ctx.path.startswith('/%s/' % _type) for _type in V2_TYPES):
59             out.v2 = True
60 
61         # Exclude noindex items
62         if web.ctx.get('exclude'):
63             web.ctx.status = "404 Not Found"
64             return render.notfound(web.ctx.path)
65 
66         return out
67 
68 
69 def _get_object(site, key):
70     """Returns the object with the given key.
71 
72     If the key has an OLID and no object is found with that key, it tries to
73     find object with the same OLID. OL database makes sures that OLIDs are
74     unique.
75     """
76     obj = site.get(key)
77 
78     if obj is None and key.startswith("/a/"):
79         key = "/authors/" + key[len("/a/"):]
80         obj = key and site.get(key)
81 
82     if obj is None and key.startswith("/b/"):
83         key = "/books/" + key[len("/b/"):]
84         obj = key and site.get(key)
85 
86     if obj is None and key.startswith("/user/"):
87         key = "/people/" + key[len("/user/"):]
88         obj = key and site.get(key)
89 
90     basename = key.split("/")[-1]
91 
92     # redirect all /.*/ia:foo to /books/ia:foo
93     if obj is None and basename.startswith("ia:"):
94         key = "/books/" + basename
95         obj = site.get(key)
96 
97     # redirect all /.*/OL123W to /works/OL123W
98     if obj is None and basename.startswith("OL") and basename.endswith("W"):
99         key = "/works/" + basename
100         obj = site.get(key)
101 
102     # redirect all /.*/OL123M to /books/OL123M
103     if obj is None and basename.startswith("OL") and basename.endswith("M"):
104         key = "/books/" + basename
105         obj = site.get(key)
106 
107     # redirect all /.*/OL123A to /authors/OL123A
108     if obj is None and basename.startswith("OL") and basename.endswith("A"):
109         key = "/authors/" + basename
110         obj = site.get(key)
111 
112     # Disabled temporarily as the index is not ready the db
113 
114     #if obj is None and web.re_compile(r"/.*/OL\d+[A-Z]"):
115     #    olid = web.safestr(key).split("/")[-1]
116     #    key = site._request("/olid_to_key", data={"olid": olid}).key
117     #    obj = key and site.get(key)
118     return obj
119 
120 def get_readable_path(site, path, patterns, encoding=None):
121     """Returns real_path and readable_path from the given path.
122 
123     The patterns is a list of (path_regex, type, property_name, default_value)
124     tuples.
125     """
126     def match(path):
127         for pat, type, property, default_title in patterns:
128             m = web.re_compile('^' + pat).match(path)
129             if m:
130                 prefix = m.group()
131                 extra = web.lstrips(path, prefix)
132                 tokens = extra.split("/", 2)
133 
134                 # `extra` starts with "/". So first token is always empty.
135                 middle = web.listget(tokens, 1, "")
136                 suffix = web.listget(tokens, 2, "")
137                 if suffix:
138                     suffix = "/" + suffix
139 
140                 return type, property, default_title, prefix, middle, suffix
141         return None, None, None, None, None, None
142 
143     type, property, default_title, prefix, middle, suffix = match(path)
144     if type is None:
145         path = web.safeunicode(path)
146         return (path, path)
147 
148     if encoding is not None \
149        or path.endswith(".json") or path.endswith(".yml") or path.endswith(".rdf"):
150         key, ext = os.path.splitext(path)
151 
152         thing = _get_object(site, key)
153         if thing:
154             path = thing.key + ext
155         path = web.safeunicode(path)
156         return (path, path)
157 
158     thing = _get_object(site, prefix)
159 
160     # get_object may handle redirections.
161     if thing:
162         prefix = thing.key
163 
164     if thing and thing.type.key == type:
165         title = thing.get(property) or default_title
166         middle = '/' + h.urlsafe(title.strip())
167     else:
168         middle = ""
169 
170     if is_exclusion(thing):
171         web.ctx.exclude = True
172 
173     prefix = web.safeunicode(prefix)
174     middle = web.safeunicode(middle)
175     suffix = web.safeunicode(suffix)
176 
177     return (prefix + suffix, prefix + middle + suffix)
178
```
--- 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/openlibrary/core/processors/readableurls.py b/openlibrary/core/processors/readableurls.py
--- a/openlibrary/core/processors/readableurls.py
+++ b/openlibrary/core/processors/readableurls.py
@@ -1,5 +1,6 @@
 """Various web.py application processors used in OL.
 """
+import logging
 import os
 import web
 
@@ -8,6 +9,7 @@
 
 from six.moves import urllib
 
+logger = logging.getLogger("openlibrary.readableurls")
 
 try:
     from booklending_utils.openlibrary import is_exclusion
@@ -123,8 +125,9 @@
     The patterns is a list of (path_regex, type, property_name, default_value)
     tuples.
     """
+
     def match(path):
-        for pat, type, property, default_title in patterns:
+        for pat, _type, _property, default_title in patterns:
             m = web.re_compile('^' + pat).match(path)
             if m:
                 prefix = m.group()
@@ -137,11 +140,12 @@
                 if suffix:
                     suffix = "/" + suffix
 
-                return type, property, default_title, prefix, middle, suffix
+                return _type, _property, default_title, prefix, middle, suffix
         return None, None, None, None, None, None
 
-    type, property, default_title, prefix, middle, suffix = match(path)
-    if type is None:
+    _type, _property, default_title, prefix, middle, suffix = match(path)
+
+    if _type is None:
         path = web.safeunicode(path)
         return (path, path)
 
@@ -161,9 +165,14 @@
     if thing:
         prefix = thing.key
 
-    if thing and thing.type.key == type:
-        title = thing.get(property) or default_title
-        middle = '/' + h.urlsafe(title.strip())
+    if thing and thing.type.key == _type:
+        title = thing.get(_property) or default_title
+        try:
+            # Explicitly only run for python3 to solve #4033
+            from urllib.parse import quote_plus
+            middle = '/' + quote_plus(h.urlsafe(title.strip()))
+        except ImportError:
+            middle = '/' + h.urlsafe(title.strip())
     else:
         middle = ""

{"golden_diff": "diff --git a/openlibrary/core/processors/readableurls.py b/openlibrary/core/processors/readableurls.py\n--- a/openlibrary/core/processors/readableurls.py\n+++ b/openlibrary/core/processors/readableurls.py\n@@ -1,5 +1,6 @@\n \"\"\"Various web.py application processors used in OL.\n \"\"\"\n+import logging\n import os\n import web\n \n@@ -8,6 +9,7 @@\n \n from six.moves import urllib\n \n+logger = logging.getLogger(\"openlibrary.readableurls\")\n \n try:\n     from booklending_utils.openlibrary import is_exclusion\n@@ -123,8 +125,9 @@\n     The patterns is a list of (path_regex, type, property_name, default_value)\n     tuples.\n     \"\"\"\n+\n     def match(path):\n-        for pat, type, property, default_title in patterns:\n+        for pat, _type, _property, default_title in patterns:\n             m = web.re_compile('^' + pat).match(path)\n             if m:\n                 prefix = m.group()\n@@ -137,11 +140,12 @@\n                 if suffix:\n                     suffix = \"/\" + suffix\n \n-                return type, property, default_title, prefix, middle, suffix\n+                return _type, _property, default_title, prefix, middle, suffix\n         return None, None, None, None, None, None\n \n-    type, property, default_title, prefix, middle, suffix = match(path)\n-    if type is None:\n+    _type, _property, default_title, prefix, middle, suffix = match(path)\n+\n+    if _type is None:\n         path = web.safeunicode(path)\n         return (path, path)\n \n@@ -161,9 +165,14 @@\n     if thing:\n         prefix = thing.key\n \n-    if thing and thing.type.key == type:\n-        title = thing.get(property) or default_title\n-        middle = '/' + h.urlsafe(title.strip())\n+    if thing and thing.type.key == _type:\n+        title = thing.get(_property) or default_title\n+        try:\n+            # Explicitly only run for python3 to solve #4033\n+            from urllib.parse import quote_plus\n+            middle = '/' + quote_plus(h.urlsafe(title.strip()))\n+        except ImportError:\n+            middle = '/' + h.urlsafe(title.strip())\n     else:\n         middle = \"\"\n", "issue": "Books with non-ascii titles erroring when clicked from search\n### Evidence / Screenshot (if possible)\r\n\r\n### Relevant url?\r\n<!-- `https://openlibrary.org/...` -->\r\n\r\n### Steps to Reproduce\r\n<!-- What steps caused you to find the bug? -->\r\n1. Go to https://openlibrary.org/search?q=h%C3%A9las&mode=everything\r\n2. Click on any book with a non-ascii character in the title\r\n\r\n<!-- What actually happened after these steps? What did you expect to happen? -->\r\n* Actual: 500 internal error on e.g. https://openlibrary.org/works/OL11565520W?edition=\r\n* Expected: See the book\r\n\r\n### Details\r\n\r\n- **Logged in (Y/N)?** Y\r\n- **Browser type/version?** FF82\r\n- **Operating system?** Win10\r\n- **Environment (prod/dev/local)?** prod\r\n<!-- If not sure, put prod -->\r\n\r\n### Proposal & Constraints\r\n<!-- What is the proposed solution / implementation? Is there a precedent of this approach succeeding elsewhere? -->\r\n\r\n### Related files\r\n<!-- Files related to this issue; this is super useful for new contributors who might want to help! If you're not sure, leave this blank; a maintainer will add them. -->\r\n\r\n### Stakeholders\r\n@cclauss \r\n\n", "before_files": [{"content": "\"\"\"Various web.py application processors used in OL.\n\"\"\"\nimport os\nimport web\n\nfrom infogami.utils.view import render\nfrom openlibrary.core import helpers as h\n\nfrom six.moves import urllib\n\n\ntry:\n    from booklending_utils.openlibrary import is_exclusion\nexcept ImportError:\n    def is_exclusion(obj):\n        \"\"\"Processor for determining whether records require exclusion\"\"\"\n        return False\n\nclass ReadableUrlProcessor:\n    \"\"\"Open Library code works with urls like /books/OL1M and\n    /books/OL1M/edit. This processor seamlessly changes the urls to\n    /books/OL1M/title and /books/OL1M/title/edit.\n\n    The changequery function is also customized to support this.\n    \"\"\"\n    patterns = [\n        (r'/\\w+/OL\\d+M', '/type/edition', 'title', 'untitled'),\n        (r'/\\w+/ia:[a-zA-Z0-9_\\.-]+', '/type/edition', 'title', 'untitled'),\n        (r'/\\w+/OL\\d+A', '/type/author', 'name', 'noname'),\n        (r'/\\w+/OL\\d+W', '/type/work', 'title', 'untitled'),\n        (r'/[/\\w]+/OL\\d+L', '/type/list', 'name', 'unnamed')\n    ]\n\n    def __call__(self, handler):\n        # temp hack to handle languages and users during upstream-to-www migration\n        if web.ctx.path.startswith(\"/l/\"):\n            raise web.seeother(\"/languages/\" + web.ctx.path[len(\"/l/\"):])\n\n        if web.ctx.path.startswith(\"/user/\"):\n            if not web.ctx.site.get(web.ctx.path):\n                raise web.seeother(\"/people/\" + web.ctx.path[len(\"/user/\"):])\n\n        real_path, readable_path = get_readable_path(web.ctx.site, web.ctx.path, self.patterns, encoding=web.ctx.encoding)\n\n        #@@ web.ctx.path is either quoted or unquoted depends on whether the application is running\n        #@@ using builtin-server or lighttpd. That is probably a bug in web.py.\n        #@@ take care of that case here till that is fixed.\n        # @@ Also, the redirection must be done only for GET requests.\n        if readable_path != web.ctx.path and readable_path != urllib.parse.quote(web.safestr(web.ctx.path)) and web.ctx.method == \"GET\":\n            raise web.redirect(web.safeunicode(readable_path) + web.safeunicode(web.ctx.query))\n\n        web.ctx.readable_path = readable_path\n        web.ctx.path = real_path\n        web.ctx.fullpath = web.ctx.path + web.ctx.query\n        out = handler()\n        V2_TYPES = ['works', 'books', 'people', 'authors',\n                    'publishers', 'languages', 'account']\n        if out and any(web.ctx.path.startswith('/%s/' % _type) for _type in V2_TYPES):\n            out.v2 = True\n\n        # Exclude noindex items\n        if web.ctx.get('exclude'):\n            web.ctx.status = \"404 Not Found\"\n            return render.notfound(web.ctx.path)\n\n        return out\n\n\ndef _get_object(site, key):\n    \"\"\"Returns the object with the given key.\n\n    If the key has an OLID and no object is found with that key, it tries to\n    find object with the same OLID. OL database makes sures that OLIDs are\n    unique.\n    \"\"\"\n    obj = site.get(key)\n\n    if obj is None and key.startswith(\"/a/\"):\n        key = \"/authors/\" + key[len(\"/a/\"):]\n        obj = key and site.get(key)\n\n    if obj is None and key.startswith(\"/b/\"):\n        key = \"/books/\" + key[len(\"/b/\"):]\n        obj = key and site.get(key)\n\n    if obj is None and key.startswith(\"/user/\"):\n        key = \"/people/\" + key[len(\"/user/\"):]\n        obj = key and site.get(key)\n\n    basename = key.split(\"/\")[-1]\n\n    # redirect all /.*/ia:foo to /books/ia:foo\n    if obj is None and basename.startswith(\"ia:\"):\n        key = \"/books/\" + basename\n        obj = site.get(key)\n\n    # redirect all /.*/OL123W to /works/OL123W\n    if obj is None and basename.startswith(\"OL\") and basename.endswith(\"W\"):\n        key = \"/works/\" + basename\n        obj = site.get(key)\n\n    # redirect all /.*/OL123M to /books/OL123M\n    if obj is None and basename.startswith(\"OL\") and basename.endswith(\"M\"):\n        key = \"/books/\" + basename\n        obj = site.get(key)\n\n    # redirect all /.*/OL123A to /authors/OL123A\n    if obj is None and basename.startswith(\"OL\") and basename.endswith(\"A\"):\n        key = \"/authors/\" + basename\n        obj = site.get(key)\n\n    # Disabled temporarily as the index is not ready the db\n\n    #if obj is None and web.re_compile(r\"/.*/OL\\d+[A-Z]\"):\n    #    olid = web.safestr(key).split(\"/\")[-1]\n    #    key = site._request(\"/olid_to_key\", data={\"olid\": olid}).key\n    #    obj = key and site.get(key)\n    return obj\n\ndef get_readable_path(site, path, patterns, encoding=None):\n    \"\"\"Returns real_path and readable_path from the given path.\n\n    The patterns is a list of (path_regex, type, property_name, default_value)\n    tuples.\n    \"\"\"\n    def match(path):\n        for pat, type, property, default_title in patterns:\n            m = web.re_compile('^' + pat).match(path)\n            if m:\n                prefix = m.group()\n                extra = web.lstrips(path, prefix)\n                tokens = extra.split(\"/\", 2)\n\n                # `extra` starts with \"/\". So first token is always empty.\n                middle = web.listget(tokens, 1, \"\")\n                suffix = web.listget(tokens, 2, \"\")\n                if suffix:\n                    suffix = \"/\" + suffix\n\n                return type, property, default_title, prefix, middle, suffix\n        return None, None, None, None, None, None\n\n    type, property, default_title, prefix, middle, suffix = match(path)\n    if type is None:\n        path = web.safeunicode(path)\n        return (path, path)\n\n    if encoding is not None \\\n       or path.endswith(\".json\") or path.endswith(\".yml\") or path.endswith(\".rdf\"):\n        key, ext = os.path.splitext(path)\n\n        thing = _get_object(site, key)\n        if thing:\n            path = thing.key + ext\n        path = web.safeunicode(path)\n        return (path, path)\n\n    thing = _get_object(site, prefix)\n\n    # get_object may handle redirections.\n    if thing:\n        prefix = thing.key\n\n    if thing and thing.type.key == type:\n        title = thing.get(property) or default_title\n        middle = '/' + h.urlsafe(title.strip())\n    else:\n        middle = \"\"\n\n    if is_exclusion(thing):\n        web.ctx.exclude = True\n\n    prefix = web.safeunicode(prefix)\n    middle = web.safeunicode(middle)\n    suffix = web.safeunicode(suffix)\n\n    return (prefix + suffix, prefix + middle + suffix)\n", "path": "openlibrary/core/processors/readableurls.py"}], "after_files": [{"content": "\"\"\"Various web.py application processors used in OL.\n\"\"\"\nimport logging\nimport os\nimport web\n\nfrom infogami.utils.view import render\nfrom openlibrary.core import helpers as h\n\nfrom six.moves import urllib\n\nlogger = logging.getLogger(\"openlibrary.readableurls\")\n\ntry:\n    from booklending_utils.openlibrary import is_exclusion\nexcept ImportError:\n    def is_exclusion(obj):\n        \"\"\"Processor for determining whether records require exclusion\"\"\"\n        return False\n\nclass ReadableUrlProcessor:\n    \"\"\"Open Library code works with urls like /books/OL1M and\n    /books/OL1M/edit. This processor seamlessly changes the urls to\n    /books/OL1M/title and /books/OL1M/title/edit.\n\n    The changequery function is also customized to support this.\n    \"\"\"\n    patterns = [\n        (r'/\\w+/OL\\d+M', '/type/edition', 'title', 'untitled'),\n        (r'/\\w+/ia:[a-zA-Z0-9_\\.-]+', '/type/edition', 'title', 'untitled'),\n        (r'/\\w+/OL\\d+A', '/type/author', 'name', 'noname'),\n        (r'/\\w+/OL\\d+W', '/type/work', 'title', 'untitled'),\n        (r'/[/\\w]+/OL\\d+L', '/type/list', 'name', 'unnamed')\n    ]\n\n    def __call__(self, handler):\n        # temp hack to handle languages and users during upstream-to-www migration\n        if web.ctx.path.startswith(\"/l/\"):\n            raise web.seeother(\"/languages/\" + web.ctx.path[len(\"/l/\"):])\n\n        if web.ctx.path.startswith(\"/user/\"):\n            if not web.ctx.site.get(web.ctx.path):\n                raise web.seeother(\"/people/\" + web.ctx.path[len(\"/user/\"):])\n\n        real_path, readable_path = get_readable_path(web.ctx.site, web.ctx.path, self.patterns, encoding=web.ctx.encoding)\n\n        #@@ web.ctx.path is either quoted or unquoted depends on whether the application is running\n        #@@ using builtin-server or lighttpd. That is probably a bug in web.py.\n        #@@ take care of that case here till that is fixed.\n        # @@ Also, the redirection must be done only for GET requests.\n        if readable_path != web.ctx.path and readable_path != urllib.parse.quote(web.safestr(web.ctx.path)) and web.ctx.method == \"GET\":\n            raise web.redirect(web.safeunicode(readable_path) + web.safeunicode(web.ctx.query))\n\n        web.ctx.readable_path = readable_path\n        web.ctx.path = real_path\n        web.ctx.fullpath = web.ctx.path + web.ctx.query\n        out = handler()\n        V2_TYPES = ['works', 'books', 'people', 'authors',\n                    'publishers', 'languages', 'account']\n        if out and any(web.ctx.path.startswith('/%s/' % _type) for _type in V2_TYPES):\n            out.v2 = True\n\n        # Exclude noindex items\n        if web.ctx.get('exclude'):\n            web.ctx.status = \"404 Not Found\"\n            return render.notfound(web.ctx.path)\n\n        return out\n\n\ndef _get_object(site, key):\n    \"\"\"Returns the object with the given key.\n\n    If the key has an OLID and no object is found with that key, it tries to\n    find object with the same OLID. OL database makes sures that OLIDs are\n    unique.\n    \"\"\"\n    obj = site.get(key)\n\n    if obj is None and key.startswith(\"/a/\"):\n        key = \"/authors/\" + key[len(\"/a/\"):]\n        obj = key and site.get(key)\n\n    if obj is None and key.startswith(\"/b/\"):\n        key = \"/books/\" + key[len(\"/b/\"):]\n        obj = key and site.get(key)\n\n    if obj is None and key.startswith(\"/user/\"):\n        key = \"/people/\" + key[len(\"/user/\"):]\n        obj = key and site.get(key)\n\n    basename = key.split(\"/\")[-1]\n\n    # redirect all /.*/ia:foo to /books/ia:foo\n    if obj is None and basename.startswith(\"ia:\"):\n        key = \"/books/\" + basename\n        obj = site.get(key)\n\n    # redirect all /.*/OL123W to /works/OL123W\n    if obj is None and basename.startswith(\"OL\") and basename.endswith(\"W\"):\n        key = \"/works/\" + basename\n        obj = site.get(key)\n\n    # redirect all /.*/OL123M to /books/OL123M\n    if obj is None and basename.startswith(\"OL\") and basename.endswith(\"M\"):\n        key = \"/books/\" + basename\n        obj = site.get(key)\n\n    # redirect all /.*/OL123A to /authors/OL123A\n    if obj is None and basename.startswith(\"OL\") and basename.endswith(\"A\"):\n        key = \"/authors/\" + basename\n        obj = site.get(key)\n\n    # Disabled temporarily as the index is not ready the db\n\n    #if obj is None and web.re_compile(r\"/.*/OL\\d+[A-Z]\"):\n    #    olid = web.safestr(key).split(\"/\")[-1]\n    #    key = site._request(\"/olid_to_key\", data={\"olid\": olid}).key\n    #    obj = key and site.get(key)\n    return obj\n\ndef get_readable_path(site, path, patterns, encoding=None):\n    \"\"\"Returns real_path and readable_path from the given path.\n\n    The patterns is a list of (path_regex, type, property_name, default_value)\n    tuples.\n    \"\"\"\n\n    def match(path):\n        for pat, _type, _property, default_title in patterns:\n            m = web.re_compile('^' + pat).match(path)\n            if m:\n                prefix = m.group()\n                extra = web.lstrips(path, prefix)\n                tokens = extra.split(\"/\", 2)\n\n                # `extra` starts with \"/\". So first token is always empty.\n                middle = web.listget(tokens, 1, \"\")\n                suffix = web.listget(tokens, 2, \"\")\n                if suffix:\n                    suffix = \"/\" + suffix\n\n                return _type, _property, default_title, prefix, middle, suffix\n        return None, None, None, None, None, None\n\n    _type, _property, default_title, prefix, middle, suffix = match(path)\n\n    if _type is None:\n        path = web.safeunicode(path)\n        return (path, path)\n\n    if encoding is not None \\\n       or path.endswith(\".json\") or path.endswith(\".yml\") or path.endswith(\".rdf\"):\n        key, ext = os.path.splitext(path)\n\n        thing = _get_object(site, key)\n        if thing:\n            path = thing.key + ext\n        path = web.safeunicode(path)\n        return (path, path)\n\n    thing = _get_object(site, prefix)\n\n    # get_object may handle redirections.\n    if thing:\n        prefix = thing.key\n\n    if thing and thing.type.key == _type:\n        title = thing.get(_property) or default_title\n        try:\n            # Explicitly only run for python3 to solve #4033\n            from urllib.parse import quote_plus\n            middle = '/' + quote_plus(h.urlsafe(title.strip()))\n        except ImportError:\n            middle = '/' + h.urlsafe(title.strip())\n    else:\n        middle = \"\"\n\n    if is_exclusion(thing):\n        web.ctx.exclude = True\n\n    prefix = web.safeunicode(prefix)\n    middle = web.safeunicode(middle)\n    suffix = web.safeunicode(suffix)\n\n    return (prefix + suffix, prefix + middle + suffix)\n", "path": "openlibrary/core/processors/readableurls.py"}]}