We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Update cgi.FieldStorage to unblock Python 3.13
Python's `cgi` module has been [deprecated](https://docs.python.org/3/library/cgi.html) and will be removed in Python 3.13, `web.py` relies on `cgi.FieldStorage` for file upload, and Open Library uses `web.py` (and by proxy, `cgi.FieldStorage`, including, but not limited to, for cover upload). See [PEP-0594](https://peps.python.org/pep-0594/#cgi) for more.

@cclauss created an issue issue for this on the `web.py` GitHub: webpy/webpy#778.
### Proposal & Constraints
<!-- What is the proposed solution / implementation? Is there a precedent of this approach succeeding elsewhere? -->
We need `web.py` updated to no longer use `cgi.FieldStorage`, we need to stick with Python <= 3.12, or we need to use something other than `web.py`, I think.

<!-- Which suggestions or requirements should be considered for how feature needs to appear or be implemented? -->
There's a PR to replace `cgi.FieldStorage` with [multipart](https://github.com/defnull/multipart/) and `urllib.parse.parse_qsl`: webpy/webpy#768.

### Additional context
<!-- Add any other context or screenshots about the feature request here. -->

### Stakeholders
<!-- @ tag stakeholders of this bug -->
@mekarpeles 
@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/plugins/upstream/covers.py`
Content:
```
1 """Handle book cover/author photo upload.
2 """
3 from logging import getLogger
4 
5 import requests
6 import web
7 from io import BytesIO
8 
9 from infogami.utils import delegate
10 from infogami.utils.view import safeint
11 from openlibrary import accounts
12 from openlibrary.plugins.upstream.models import Image
13 from openlibrary.plugins.upstream.utils import (
14     get_coverstore_url,
15     get_coverstore_public_url,
16     render_template,
17 )
18 
19 logger = getLogger("openlibrary.plugins.upstream.covers")
20 
21 
22 def setup():
23     pass
24 
25 
26 class add_cover(delegate.page):
27     path = r"(/books/OL\d+M)/add-cover"
28     cover_category = "b"
29 
30     def GET(self, key):
31         book = web.ctx.site.get(key)
32         return render_template('covers/add', book)
33 
34     def POST(self, key):
35         book = web.ctx.site.get(key)
36         if not book:
37             raise web.notfound("")
38 
39         user = accounts.get_current_user()
40         if user and user.is_read_only():
41             raise web.forbidden(message="Patron not permitted to upload images")
42 
43         i = web.input(file={}, url="")
44 
45         # remove references to field storage objects
46         web.ctx.pop("_fieldstorage", None)
47 
48         data = self.upload(key, i)
49 
50         if coverid := data.get('id'):
51             if isinstance(i.url, bytes):
52                 i.url = i.url.decode("utf-8")
53             self.save(book, coverid, url=i.url)
54             cover = Image(web.ctx.site, "b", coverid)
55             return render_template("covers/saved", cover)
56         else:
57             return render_template("covers/add", book, {'url': i.url}, data)
58 
59     def upload(self, key, i):
60         """Uploads a cover to coverstore and returns the response."""
61         olid = key.split("/")[-1]
62 
63         if i.file is not None and hasattr(i.file, 'value'):
64             data = i.file.value
65         else:
66             data = None
67 
68         if i.url and i.url.strip() == "https://":
69             i.url = ""
70 
71         user = accounts.get_current_user()
72         params = {
73             "author": user and user.key,
74             "source_url": i.url,
75             "olid": olid,
76             "ip": web.ctx.ip,
77         }
78 
79         upload_url = f'{get_coverstore_url()}/{self.cover_category}/upload2'
80 
81         if upload_url.startswith("//"):
82             upload_url = "http:" + upload_url
83 
84         try:
85             files = {'data': BytesIO(data)}
86             response = requests.post(upload_url, data=params, files=files)
87             return web.storage(response.json())
88         except requests.HTTPError as e:
89             logger.exception("Covers upload failed")
90             return web.storage({'error': str(e)})
91 
92     def save(self, book, coverid, url=None):
93         book.covers = [coverid] + [cover.id for cover in book.get_covers()]
94         book._save(
95             f'{get_coverstore_public_url()}/b/id/{coverid}-S.jpg',
96             action="add-cover",
97             data={"url": url},
98         )
99 
100 
101 class add_work_cover(add_cover):
102     path = r"(/works/OL\d+W)/add-cover"
103     cover_category = "w"
104 
105     def upload(self, key, i):
106         if "coverid" in i and safeint(i.coverid):
107             return web.storage(id=int(i.coverid))
108         else:
109             return add_cover.upload(self, key, i)
110 
111 
112 class add_photo(add_cover):
113     path = r"(/authors/OL\d+A)/add-photo"
114     cover_category = "a"
115 
116     def save(self, author, photoid, url=None):
117         author.photos = [photoid] + [photo.id for photo in author.get_photos()]
118         author._save("Added new photo", action="add-photo", data={"url": url})
119 
120 
121 class manage_covers(delegate.page):
122     path = r"(/books/OL\d+M)/manage-covers"
123 
124     def GET(self, key):
125         book = web.ctx.site.get(key)
126         if not book:
127             raise web.notfound()
128         return render_template("covers/manage", key, self.get_images(book))
129 
130     def get_images(self, book):
131         return book.get_covers()
132 
133     def get_image(self, book):
134         return book.get_cover()
135 
136     def save_images(self, book, covers):
137         book.covers = covers
138         book._save('Update covers')
139 
140     def POST(self, key):
141         book = web.ctx.site.get(key)
142         if not book:
143             raise web.notfound()
144 
145         images = web.input(image=[]).image
146         if '-' in images:
147             images = [int(id) for id in images[: images.index('-')]]
148             self.save_images(book, images)
149             return render_template("covers/saved", self.get_image(book), showinfo=False)
150         else:
151             # ERROR
152             pass
153 
154 
155 class manage_work_covers(manage_covers):
156     path = r"(/works/OL\d+W)/manage-covers"
157 
158 
159 class manage_photos(manage_covers):
160     path = r"(/authors/OL\d+A)/manage-photos"
161 
162     def get_images(self, author):
163         return author.get_photos()
164 
165     def get_image(self, author):
166         return author.get_photo()
167 
168     def save_images(self, author, photos):
169         author.photos = photos
170         author._save('Update photos')
171
```
--- 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/plugins/upstream/covers.py b/openlibrary/plugins/upstream/covers.py
--- a/openlibrary/plugins/upstream/covers.py
+++ b/openlibrary/plugins/upstream/covers.py
@@ -60,8 +60,8 @@
         """Uploads a cover to coverstore and returns the response."""
         olid = key.split("/")[-1]
 
-        if i.file is not None and hasattr(i.file, 'value'):
-            data = i.file.value
+        if i.file is not None and hasattr(i.file, 'file'):
+            data = i.file.file
         else:
             data = None
 
@@ -82,7 +82,7 @@
             upload_url = "http:" + upload_url
 
         try:
-            files = {'data': BytesIO(data)}
+            files = {'data': data}
             response = requests.post(upload_url, data=params, files=files)
             return web.storage(response.json())
         except requests.HTTPError as e:

{"golden_diff": "diff --git a/openlibrary/plugins/upstream/covers.py b/openlibrary/plugins/upstream/covers.py\n--- a/openlibrary/plugins/upstream/covers.py\n+++ b/openlibrary/plugins/upstream/covers.py\n@@ -60,8 +60,8 @@\n         \"\"\"Uploads a cover to coverstore and returns the response.\"\"\"\n         olid = key.split(\"/\")[-1]\n \n-        if i.file is not None and hasattr(i.file, 'value'):\n-            data = i.file.value\n+        if i.file is not None and hasattr(i.file, 'file'):\n+            data = i.file.file\n         else:\n             data = None\n \n@@ -82,7 +82,7 @@\n             upload_url = \"http:\" + upload_url\n \n         try:\n-            files = {'data': BytesIO(data)}\n+            files = {'data': data}\n             response = requests.post(upload_url, data=params, files=files)\n             return web.storage(response.json())\n         except requests.HTTPError as e:\n", "issue": "Update cgi.FieldStorage to unblock Python 3.13\nPython's `cgi` module has been [deprecated](https://docs.python.org/3/library/cgi.html) and will be removed in Python 3.13, `web.py` relies on `cgi.FieldStorage` for file upload, and Open Library uses `web.py` (and by proxy, `cgi.FieldStorage`, including, but not limited to, for cover upload). See [PEP-0594](https://peps.python.org/pep-0594/#cgi) for more.\r\n\r\n@cclauss created an issue issue for this on the `web.py` GitHub: webpy/webpy#778.\r\n### Proposal & Constraints\r\n<!-- What is the proposed solution / implementation? Is there a precedent of this approach succeeding elsewhere? -->\r\nWe need `web.py` updated to no longer use `cgi.FieldStorage`, we need to stick with Python <= 3.12, or we need to use something other than `web.py`, I think.\r\n\r\n<!-- Which suggestions or requirements should be considered for how feature needs to appear or be implemented? -->\r\nThere's a PR to replace `cgi.FieldStorage` with [multipart](https://github.com/defnull/multipart/) and `urllib.parse.parse_qsl`: webpy/webpy#768.\r\n\r\n### Additional context\r\n<!-- Add any other context or screenshots about the feature request here. -->\r\n\r\n### Stakeholders\r\n<!-- @ tag stakeholders of this bug -->\r\n@mekarpeles \r\n@cclauss \r\n\r\n\n", "before_files": [{"content": "\"\"\"Handle book cover/author photo upload.\n\"\"\"\nfrom logging import getLogger\n\nimport requests\nimport web\nfrom io import BytesIO\n\nfrom infogami.utils import delegate\nfrom infogami.utils.view import safeint\nfrom openlibrary import accounts\nfrom openlibrary.plugins.upstream.models import Image\nfrom openlibrary.plugins.upstream.utils import (\n    get_coverstore_url,\n    get_coverstore_public_url,\n    render_template,\n)\n\nlogger = getLogger(\"openlibrary.plugins.upstream.covers\")\n\n\ndef setup():\n    pass\n\n\nclass add_cover(delegate.page):\n    path = r\"(/books/OL\\d+M)/add-cover\"\n    cover_category = \"b\"\n\n    def GET(self, key):\n        book = web.ctx.site.get(key)\n        return render_template('covers/add', book)\n\n    def POST(self, key):\n        book = web.ctx.site.get(key)\n        if not book:\n            raise web.notfound(\"\")\n\n        user = accounts.get_current_user()\n        if user and user.is_read_only():\n            raise web.forbidden(message=\"Patron not permitted to upload images\")\n\n        i = web.input(file={}, url=\"\")\n\n        # remove references to field storage objects\n        web.ctx.pop(\"_fieldstorage\", None)\n\n        data = self.upload(key, i)\n\n        if coverid := data.get('id'):\n            if isinstance(i.url, bytes):\n                i.url = i.url.decode(\"utf-8\")\n            self.save(book, coverid, url=i.url)\n            cover = Image(web.ctx.site, \"b\", coverid)\n            return render_template(\"covers/saved\", cover)\n        else:\n            return render_template(\"covers/add\", book, {'url': i.url}, data)\n\n    def upload(self, key, i):\n        \"\"\"Uploads a cover to coverstore and returns the response.\"\"\"\n        olid = key.split(\"/\")[-1]\n\n        if i.file is not None and hasattr(i.file, 'value'):\n            data = i.file.value\n        else:\n            data = None\n\n        if i.url and i.url.strip() == \"https://\":\n            i.url = \"\"\n\n        user = accounts.get_current_user()\n        params = {\n            \"author\": user and user.key,\n            \"source_url\": i.url,\n            \"olid\": olid,\n            \"ip\": web.ctx.ip,\n        }\n\n        upload_url = f'{get_coverstore_url()}/{self.cover_category}/upload2'\n\n        if upload_url.startswith(\"//\"):\n            upload_url = \"http:\" + upload_url\n\n        try:\n            files = {'data': BytesIO(data)}\n            response = requests.post(upload_url, data=params, files=files)\n            return web.storage(response.json())\n        except requests.HTTPError as e:\n            logger.exception(\"Covers upload failed\")\n            return web.storage({'error': str(e)})\n\n    def save(self, book, coverid, url=None):\n        book.covers = [coverid] + [cover.id for cover in book.get_covers()]\n        book._save(\n            f'{get_coverstore_public_url()}/b/id/{coverid}-S.jpg',\n            action=\"add-cover\",\n            data={\"url\": url},\n        )\n\n\nclass add_work_cover(add_cover):\n    path = r\"(/works/OL\\d+W)/add-cover\"\n    cover_category = \"w\"\n\n    def upload(self, key, i):\n        if \"coverid\" in i and safeint(i.coverid):\n            return web.storage(id=int(i.coverid))\n        else:\n            return add_cover.upload(self, key, i)\n\n\nclass add_photo(add_cover):\n    path = r\"(/authors/OL\\d+A)/add-photo\"\n    cover_category = \"a\"\n\n    def save(self, author, photoid, url=None):\n        author.photos = [photoid] + [photo.id for photo in author.get_photos()]\n        author._save(\"Added new photo\", action=\"add-photo\", data={\"url\": url})\n\n\nclass manage_covers(delegate.page):\n    path = r\"(/books/OL\\d+M)/manage-covers\"\n\n    def GET(self, key):\n        book = web.ctx.site.get(key)\n        if not book:\n            raise web.notfound()\n        return render_template(\"covers/manage\", key, self.get_images(book))\n\n    def get_images(self, book):\n        return book.get_covers()\n\n    def get_image(self, book):\n        return book.get_cover()\n\n    def save_images(self, book, covers):\n        book.covers = covers\n        book._save('Update covers')\n\n    def POST(self, key):\n        book = web.ctx.site.get(key)\n        if not book:\n            raise web.notfound()\n\n        images = web.input(image=[]).image\n        if '-' in images:\n            images = [int(id) for id in images[: images.index('-')]]\n            self.save_images(book, images)\n            return render_template(\"covers/saved\", self.get_image(book), showinfo=False)\n        else:\n            # ERROR\n            pass\n\n\nclass manage_work_covers(manage_covers):\n    path = r\"(/works/OL\\d+W)/manage-covers\"\n\n\nclass manage_photos(manage_covers):\n    path = r\"(/authors/OL\\d+A)/manage-photos\"\n\n    def get_images(self, author):\n        return author.get_photos()\n\n    def get_image(self, author):\n        return author.get_photo()\n\n    def save_images(self, author, photos):\n        author.photos = photos\n        author._save('Update photos')\n", "path": "openlibrary/plugins/upstream/covers.py"}], "after_files": [{"content": "\"\"\"Handle book cover/author photo upload.\n\"\"\"\nfrom logging import getLogger\n\nimport requests\nimport web\nfrom io import BytesIO\n\nfrom infogami.utils import delegate\nfrom infogami.utils.view import safeint\nfrom openlibrary import accounts\nfrom openlibrary.plugins.upstream.models import Image\nfrom openlibrary.plugins.upstream.utils import (\n    get_coverstore_url,\n    get_coverstore_public_url,\n    render_template,\n)\n\nlogger = getLogger(\"openlibrary.plugins.upstream.covers\")\n\n\ndef setup():\n    pass\n\n\nclass add_cover(delegate.page):\n    path = r\"(/books/OL\\d+M)/add-cover\"\n    cover_category = \"b\"\n\n    def GET(self, key):\n        book = web.ctx.site.get(key)\n        return render_template('covers/add', book)\n\n    def POST(self, key):\n        book = web.ctx.site.get(key)\n        if not book:\n            raise web.notfound(\"\")\n\n        user = accounts.get_current_user()\n        if user and user.is_read_only():\n            raise web.forbidden(message=\"Patron not permitted to upload images\")\n\n        i = web.input(file={}, url=\"\")\n\n        # remove references to field storage objects\n        web.ctx.pop(\"_fieldstorage\", None)\n\n        data = self.upload(key, i)\n\n        if coverid := data.get('id'):\n            if isinstance(i.url, bytes):\n                i.url = i.url.decode(\"utf-8\")\n            self.save(book, coverid, url=i.url)\n            cover = Image(web.ctx.site, \"b\", coverid)\n            return render_template(\"covers/saved\", cover)\n        else:\n            return render_template(\"covers/add\", book, {'url': i.url}, data)\n\n    def upload(self, key, i):\n        \"\"\"Uploads a cover to coverstore and returns the response.\"\"\"\n        olid = key.split(\"/\")[-1]\n\n        if i.file is not None and hasattr(i.file, 'file'):\n            data = i.file.file\n        else:\n            data = None\n\n        if i.url and i.url.strip() == \"https://\":\n            i.url = \"\"\n\n        user = accounts.get_current_user()\n        params = {\n            \"author\": user and user.key,\n            \"source_url\": i.url,\n            \"olid\": olid,\n            \"ip\": web.ctx.ip,\n        }\n\n        upload_url = f'{get_coverstore_url()}/{self.cover_category}/upload2'\n\n        if upload_url.startswith(\"//\"):\n            upload_url = \"http:\" + upload_url\n\n        try:\n            files = {'data': data}\n            response = requests.post(upload_url, data=params, files=files)\n            return web.storage(response.json())\n        except requests.HTTPError as e:\n            logger.exception(\"Covers upload failed\")\n            return web.storage({'error': str(e)})\n\n    def save(self, book, coverid, url=None):\n        book.covers = [coverid] + [cover.id for cover in book.get_covers()]\n        book._save(\n            f'{get_coverstore_public_url()}/b/id/{coverid}-S.jpg',\n            action=\"add-cover\",\n            data={\"url\": url},\n        )\n\n\nclass add_work_cover(add_cover):\n    path = r\"(/works/OL\\d+W)/add-cover\"\n    cover_category = \"w\"\n\n    def upload(self, key, i):\n        if \"coverid\" in i and safeint(i.coverid):\n            return web.storage(id=int(i.coverid))\n        else:\n            return add_cover.upload(self, key, i)\n\n\nclass add_photo(add_cover):\n    path = r\"(/authors/OL\\d+A)/add-photo\"\n    cover_category = \"a\"\n\n    def save(self, author, photoid, url=None):\n        author.photos = [photoid] + [photo.id for photo in author.get_photos()]\n        author._save(\"Added new photo\", action=\"add-photo\", data={\"url\": url})\n\n\nclass manage_covers(delegate.page):\n    path = r\"(/books/OL\\d+M)/manage-covers\"\n\n    def GET(self, key):\n        book = web.ctx.site.get(key)\n        if not book:\n            raise web.notfound()\n        return render_template(\"covers/manage\", key, self.get_images(book))\n\n    def get_images(self, book):\n        return book.get_covers()\n\n    def get_image(self, book):\n        return book.get_cover()\n\n    def save_images(self, book, covers):\n        book.covers = covers\n        book._save('Update covers')\n\n    def POST(self, key):\n        book = web.ctx.site.get(key)\n        if not book:\n            raise web.notfound()\n\n        images = web.input(image=[]).image\n        if '-' in images:\n            images = [int(id) for id in images[: images.index('-')]]\n            self.save_images(book, images)\n            return render_template(\"covers/saved\", self.get_image(book), showinfo=False)\n        else:\n            # ERROR\n            pass\n\n\nclass manage_work_covers(manage_covers):\n    path = r\"(/works/OL\\d+W)/manage-covers\"\n\n\nclass manage_photos(manage_covers):\n    path = r\"(/authors/OL\\d+A)/manage-photos\"\n\n    def get_images(self, author):\n        return author.get_photos()\n\n    def get_image(self, author):\n        return author.get_photo()\n\n    def save_images(self, author, photos):\n        author.photos = photos\n        author._save('Update photos')\n", "path": "openlibrary/plugins/upstream/covers.py"}]}