We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Streamer.py handles IOError exception in non-python3 compatible way
Under python 3 and the latest repository, browsing to `http://127.0.0.1:8000/welcome/favicon.ico` causes `streamer.py` to crash because it treats the `IOError` exception in a non-python3 compatible way. The IOError exception occurs because `favicon.ico` is not found at `/ `. No error ticket is generated.

    except IOError as e:
        if e[0] == errno.EISDIR:
            raise HTTP(403, error_message, web2py_error='file is a directory')
        elif e[0] == errno.EACCES:
            raise HTTP(403, error_message, web2py_error='inaccessible file')
        else:
            raise HTTP(404, error_message, web2py_error='invalid file')

This works in python 2, but `e[0]` should be accesed as `e.errno` under python 3

Partial stack trace:

    Traceback (most recent call last):
      File "C:\web2py\gluon\main.py", line 329, in wsgibase
        response.stream(static_file, request=request)
      File "C:\web2py\gluon\globals.py", line 617, in stream
        status=self.status)
      File "C:\web2py\gluon\streamer.py", line 66, in stream_file_or_304_or_206
        if e[0] == errno.EISDIR:
    TypeError: 'FileNotFoundError' object is not subscriptable


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `gluon/streamer.py`
Content:
```
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 
4 """
5 | This file is part of the web2py Web Framework
6 | Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 | License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8 
9 Facilities to handle file streaming
10 ------------------------------------
11 """
12 
13 import os
14 import stat
15 import time
16 import re
17 import errno
18 from gluon.http import HTTP
19 from gluon.contenttype import contenttype
20 from gluon._compat import PY2
21 
22 
23 regex_start_range = re.compile('\d+(?=\-)')
24 regex_stop_range = re.compile('(?<=\-)\d+')
25 
26 DEFAULT_CHUNK_SIZE = 64 * 1024
27 
28 def streamer(stream, chunk_size=DEFAULT_CHUNK_SIZE, bytes=None, callback=None):
29     try:
30         offset = 0
31         while bytes is None or offset < bytes:
32             if not bytes is None and bytes - offset < chunk_size:
33                 chunk_size = bytes - offset
34             data = stream.read(chunk_size)
35             length = len(data)
36             if not length:
37                 break
38             else:
39                 yield data
40             if length < chunk_size:
41                 break
42             offset += length
43     finally:
44         stream.close()
45         if callback:
46             callback()
47 
48 def stream_file_or_304_or_206(
49     static_file,
50     chunk_size=DEFAULT_CHUNK_SIZE,
51     request=None,
52     headers={},
53     status=200,
54     error_message=None
55     ):
56     # FIX THIS
57     # if error_message is None:
58     #     error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request'
59     try:
60         if PY2:
61             open_f = file # this makes no sense but without it GAE cannot open files
62         else:
63             open_f = open
64         fp = open_f(static_file,'rb')
65     except IOError as e:
66         if e[0] == errno.EISDIR:
67             raise HTTP(403, error_message, web2py_error='file is a directory')
68         elif e[0] == errno.EACCES:
69             raise HTTP(403, error_message, web2py_error='inaccessible file')
70         else:
71             raise HTTP(404, error_message, web2py_error='invalid file')
72     else:
73         fp.close()
74     stat_file = os.stat(static_file)
75     fsize = stat_file[stat.ST_SIZE]
76     modified = stat_file[stat.ST_MTIME]
77     mtime = time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(modified))
78     headers.setdefault('Content-Type', contenttype(static_file))
79     headers.setdefault('Last-Modified', mtime)
80     headers.setdefault('Pragma', 'cache')
81     headers.setdefault('Cache-Control', 'private')
82 
83     # if this is a normal response and not a respnse to an error page
84     if status == 200:
85         if request and request.env.http_if_modified_since == mtime:
86             raise HTTP(304, **{'Content-Type': headers['Content-Type']})
87 
88         elif request and request.env.http_range:
89             start_items = regex_start_range.findall(request.env.http_range)
90             if not start_items:
91                 start_items = [0]
92             stop_items = regex_stop_range.findall(request.env.http_range)
93             if not stop_items or int(stop_items[0]) > fsize - 1:
94                 stop_items = [fsize - 1]
95             part = (int(start_items[0]), int(stop_items[0]), fsize)
96             bytes = part[1] - part[0] + 1
97             try:
98                 stream = open(static_file, 'rb')
99             except IOError as e:
100                 if e[0] in (errno.EISDIR, errno.EACCES):
101                     raise HTTP(403)
102                 else:
103                     raise HTTP(404)
104             stream.seek(part[0])
105             headers['Content-Range'] = 'bytes %i-%i/%i' % part
106             headers['Content-Length'] = '%i' % bytes
107             status = 206
108     # in all the other cases (not 304, not 206, but 200 or error page)
109     if status != 206:
110         enc = request.env.http_accept_encoding
111         if enc and 'gzip' in enc and not 'Content-Encoding' in headers:
112             gzipped = static_file + '.gz'
113             if os.path.isfile(gzipped) and os.path.getmtime(gzipped) >= modified:
114                 static_file = gzipped
115                 fsize = os.path.getsize(gzipped)
116                 headers['Content-Encoding'] = 'gzip'
117                 headers['Vary'] = 'Accept-Encoding'
118         try:
119             stream = open(static_file, 'rb')
120         except IOError as e:
121             # this better does not happer when returning an error page ;-)
122             if e[0] in (errno.EISDIR, errno.EACCES):
123                 raise HTTP(403)
124             else:
125                 raise HTTP(404)
126         headers['Content-Length'] = fsize
127         bytes = None
128     if request and request.env.web2py_use_wsgi_file_wrapper:
129         wrapped = request.env.wsgi_file_wrapper(stream, chunk_size)
130     else:
131         wrapped = streamer(stream, chunk_size=chunk_size, bytes=bytes)
132     raise HTTP(status, wrapped, **headers)
133
```
--- 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/gluon/streamer.py b/gluon/streamer.py
--- a/gluon/streamer.py
+++ b/gluon/streamer.py
@@ -63,9 +63,9 @@
             open_f = open
         fp = open_f(static_file,'rb')
     except IOError as e:
-        if e[0] == errno.EISDIR:
+        if e.errno == errno.EISDIR:
             raise HTTP(403, error_message, web2py_error='file is a directory')
-        elif e[0] == errno.EACCES:
+        elif e.errno == errno.EACCES:
             raise HTTP(403, error_message, web2py_error='inaccessible file')
         else:
             raise HTTP(404, error_message, web2py_error='invalid file')
@@ -97,7 +97,7 @@
             try:
                 stream = open(static_file, 'rb')
             except IOError as e:
-                if e[0] in (errno.EISDIR, errno.EACCES):
+                if e.errno in (errno.EISDIR, errno.EACCES):
                     raise HTTP(403)
                 else:
                     raise HTTP(404)
@@ -118,8 +118,8 @@
         try:
             stream = open(static_file, 'rb')
         except IOError as e:
-            # this better does not happer when returning an error page ;-)
-            if e[0] in (errno.EISDIR, errno.EACCES):
+            # this better not happen when returning an error page ;-)
+            if e.errno in (errno.EISDIR, errno.EACCES):
                 raise HTTP(403)
             else:
                 raise HTTP(404)

{"golden_diff": "diff --git a/gluon/streamer.py b/gluon/streamer.py\n--- a/gluon/streamer.py\n+++ b/gluon/streamer.py\n@@ -63,9 +63,9 @@\n             open_f = open\n         fp = open_f(static_file,'rb')\n     except IOError as e:\n-        if e[0] == errno.EISDIR:\n+        if e.errno == errno.EISDIR:\n             raise HTTP(403, error_message, web2py_error='file is a directory')\n-        elif e[0] == errno.EACCES:\n+        elif e.errno == errno.EACCES:\n             raise HTTP(403, error_message, web2py_error='inaccessible file')\n         else:\n             raise HTTP(404, error_message, web2py_error='invalid file')\n@@ -97,7 +97,7 @@\n             try:\n                 stream = open(static_file, 'rb')\n             except IOError as e:\n-                if e[0] in (errno.EISDIR, errno.EACCES):\n+                if e.errno in (errno.EISDIR, errno.EACCES):\n                     raise HTTP(403)\n                 else:\n                     raise HTTP(404)\n@@ -118,8 +118,8 @@\n         try:\n             stream = open(static_file, 'rb')\n         except IOError as e:\n-            # this better does not happer when returning an error page ;-)\n-            if e[0] in (errno.EISDIR, errno.EACCES):\n+            # this better not happen when returning an error page ;-)\n+            if e.errno in (errno.EISDIR, errno.EACCES):\n                 raise HTTP(403)\n             else:\n                 raise HTTP(404)\n", "issue": "Streamer.py handles IOError exception in non-python3 compatible way\nUnder python 3 and the latest repository, browsing to `http://127.0.0.1:8000/welcome/favicon.ico` causes `streamer.py` to crash because it treats the `IOError` exception in a non-python3 compatible way. The IOError exception occurs because `favicon.ico` is not found at `/ `. No error ticket is generated.\r\n\r\n    except IOError as e:\r\n        if e[0] == errno.EISDIR:\r\n            raise HTTP(403, error_message, web2py_error='file is a directory')\r\n        elif e[0] == errno.EACCES:\r\n            raise HTTP(403, error_message, web2py_error='inaccessible file')\r\n        else:\r\n            raise HTTP(404, error_message, web2py_error='invalid file')\r\n\r\nThis works in python 2, but `e[0]` should be accesed as `e.errno` under python 3\r\n\r\nPartial stack trace:\r\n\r\n    Traceback (most recent call last):\r\n      File \"C:\\web2py\\gluon\\main.py\", line 329, in wsgibase\r\n        response.stream(static_file, request=request)\r\n      File \"C:\\web2py\\gluon\\globals.py\", line 617, in stream\r\n        status=self.status)\r\n      File \"C:\\web2py\\gluon\\streamer.py\", line 66, in stream_file_or_304_or_206\r\n        if e[0] == errno.EISDIR:\r\n    TypeError: 'FileNotFoundError' object is not subscriptable\r\n\n", "before_files": [{"content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\n| This file is part of the web2py Web Framework\n| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>\n| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)\n\nFacilities to handle file streaming\n------------------------------------\n\"\"\"\n\nimport os\nimport stat\nimport time\nimport re\nimport errno\nfrom gluon.http import HTTP\nfrom gluon.contenttype import contenttype\nfrom gluon._compat import PY2\n\n\nregex_start_range = re.compile('\\d+(?=\\-)')\nregex_stop_range = re.compile('(?<=\\-)\\d+')\n\nDEFAULT_CHUNK_SIZE = 64 * 1024\n\ndef streamer(stream, chunk_size=DEFAULT_CHUNK_SIZE, bytes=None, callback=None):\n    try:\n        offset = 0\n        while bytes is None or offset < bytes:\n            if not bytes is None and bytes - offset < chunk_size:\n                chunk_size = bytes - offset\n            data = stream.read(chunk_size)\n            length = len(data)\n            if not length:\n                break\n            else:\n                yield data\n            if length < chunk_size:\n                break\n            offset += length\n    finally:\n        stream.close()\n        if callback:\n            callback()\n\ndef stream_file_or_304_or_206(\n    static_file,\n    chunk_size=DEFAULT_CHUNK_SIZE,\n    request=None,\n    headers={},\n    status=200,\n    error_message=None\n    ):\n    # FIX THIS\n    # if error_message is None:\n    #     error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request'\n    try:\n        if PY2:\n            open_f = file # this makes no sense but without it GAE cannot open files\n        else:\n            open_f = open\n        fp = open_f(static_file,'rb')\n    except IOError as e:\n        if e[0] == errno.EISDIR:\n            raise HTTP(403, error_message, web2py_error='file is a directory')\n        elif e[0] == errno.EACCES:\n            raise HTTP(403, error_message, web2py_error='inaccessible file')\n        else:\n            raise HTTP(404, error_message, web2py_error='invalid file')\n    else:\n        fp.close()\n    stat_file = os.stat(static_file)\n    fsize = stat_file[stat.ST_SIZE]\n    modified = stat_file[stat.ST_MTIME]\n    mtime = time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(modified))\n    headers.setdefault('Content-Type', contenttype(static_file))\n    headers.setdefault('Last-Modified', mtime)\n    headers.setdefault('Pragma', 'cache')\n    headers.setdefault('Cache-Control', 'private')\n\n    # if this is a normal response and not a respnse to an error page\n    if status == 200:\n        if request and request.env.http_if_modified_since == mtime:\n            raise HTTP(304, **{'Content-Type': headers['Content-Type']})\n\n        elif request and request.env.http_range:\n            start_items = regex_start_range.findall(request.env.http_range)\n            if not start_items:\n                start_items = [0]\n            stop_items = regex_stop_range.findall(request.env.http_range)\n            if not stop_items or int(stop_items[0]) > fsize - 1:\n                stop_items = [fsize - 1]\n            part = (int(start_items[0]), int(stop_items[0]), fsize)\n            bytes = part[1] - part[0] + 1\n            try:\n                stream = open(static_file, 'rb')\n            except IOError as e:\n                if e[0] in (errno.EISDIR, errno.EACCES):\n                    raise HTTP(403)\n                else:\n                    raise HTTP(404)\n            stream.seek(part[0])\n            headers['Content-Range'] = 'bytes %i-%i/%i' % part\n            headers['Content-Length'] = '%i' % bytes\n            status = 206\n    # in all the other cases (not 304, not 206, but 200 or error page)\n    if status != 206:\n        enc = request.env.http_accept_encoding\n        if enc and 'gzip' in enc and not 'Content-Encoding' in headers:\n            gzipped = static_file + '.gz'\n            if os.path.isfile(gzipped) and os.path.getmtime(gzipped) >= modified:\n                static_file = gzipped\n                fsize = os.path.getsize(gzipped)\n                headers['Content-Encoding'] = 'gzip'\n                headers['Vary'] = 'Accept-Encoding'\n        try:\n            stream = open(static_file, 'rb')\n        except IOError as e:\n            # this better does not happer when returning an error page ;-)\n            if e[0] in (errno.EISDIR, errno.EACCES):\n                raise HTTP(403)\n            else:\n                raise HTTP(404)\n        headers['Content-Length'] = fsize\n        bytes = None\n    if request and request.env.web2py_use_wsgi_file_wrapper:\n        wrapped = request.env.wsgi_file_wrapper(stream, chunk_size)\n    else:\n        wrapped = streamer(stream, chunk_size=chunk_size, bytes=bytes)\n    raise HTTP(status, wrapped, **headers)\n", "path": "gluon/streamer.py"}], "after_files": [{"content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n\"\"\"\n| This file is part of the web2py Web Framework\n| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>\n| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)\n\nFacilities to handle file streaming\n------------------------------------\n\"\"\"\n\nimport os\nimport stat\nimport time\nimport re\nimport errno\nfrom gluon.http import HTTP\nfrom gluon.contenttype import contenttype\nfrom gluon._compat import PY2\n\n\nregex_start_range = re.compile('\\d+(?=\\-)')\nregex_stop_range = re.compile('(?<=\\-)\\d+')\n\nDEFAULT_CHUNK_SIZE = 64 * 1024\n\ndef streamer(stream, chunk_size=DEFAULT_CHUNK_SIZE, bytes=None, callback=None):\n    try:\n        offset = 0\n        while bytes is None or offset < bytes:\n            if not bytes is None and bytes - offset < chunk_size:\n                chunk_size = bytes - offset\n            data = stream.read(chunk_size)\n            length = len(data)\n            if not length:\n                break\n            else:\n                yield data\n            if length < chunk_size:\n                break\n            offset += length\n    finally:\n        stream.close()\n        if callback:\n            callback()\n\ndef stream_file_or_304_or_206(\n    static_file,\n    chunk_size=DEFAULT_CHUNK_SIZE,\n    request=None,\n    headers={},\n    status=200,\n    error_message=None\n    ):\n    # FIX THIS\n    # if error_message is None:\n    #     error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request'\n    try:\n        if PY2:\n            open_f = file # this makes no sense but without it GAE cannot open files\n        else:\n            open_f = open\n        fp = open_f(static_file,'rb')\n    except IOError as e:\n        if e.errno == errno.EISDIR:\n            raise HTTP(403, error_message, web2py_error='file is a directory')\n        elif e.errno == errno.EACCES:\n            raise HTTP(403, error_message, web2py_error='inaccessible file')\n        else:\n            raise HTTP(404, error_message, web2py_error='invalid file')\n    else:\n        fp.close()\n    stat_file = os.stat(static_file)\n    fsize = stat_file[stat.ST_SIZE]\n    modified = stat_file[stat.ST_MTIME]\n    mtime = time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(modified))\n    headers.setdefault('Content-Type', contenttype(static_file))\n    headers.setdefault('Last-Modified', mtime)\n    headers.setdefault('Pragma', 'cache')\n    headers.setdefault('Cache-Control', 'private')\n\n    # if this is a normal response and not a respnse to an error page\n    if status == 200:\n        if request and request.env.http_if_modified_since == mtime:\n            raise HTTP(304, **{'Content-Type': headers['Content-Type']})\n\n        elif request and request.env.http_range:\n            start_items = regex_start_range.findall(request.env.http_range)\n            if not start_items:\n                start_items = [0]\n            stop_items = regex_stop_range.findall(request.env.http_range)\n            if not stop_items or int(stop_items[0]) > fsize - 1:\n                stop_items = [fsize - 1]\n            part = (int(start_items[0]), int(stop_items[0]), fsize)\n            bytes = part[1] - part[0] + 1\n            try:\n                stream = open(static_file, 'rb')\n            except IOError as e:\n                if e.errno in (errno.EISDIR, errno.EACCES):\n                    raise HTTP(403)\n                else:\n                    raise HTTP(404)\n            stream.seek(part[0])\n            headers['Content-Range'] = 'bytes %i-%i/%i' % part\n            headers['Content-Length'] = '%i' % bytes\n            status = 206\n    # in all the other cases (not 304, not 206, but 200 or error page)\n    if status != 206:\n        enc = request.env.http_accept_encoding\n        if enc and 'gzip' in enc and not 'Content-Encoding' in headers:\n            gzipped = static_file + '.gz'\n            if os.path.isfile(gzipped) and os.path.getmtime(gzipped) >= modified:\n                static_file = gzipped\n                fsize = os.path.getsize(gzipped)\n                headers['Content-Encoding'] = 'gzip'\n                headers['Vary'] = 'Accept-Encoding'\n        try:\n            stream = open(static_file, 'rb')\n        except IOError as e:\n            # this better not happen when returning an error page ;-)\n            if e.errno in (errno.EISDIR, errno.EACCES):\n                raise HTTP(403)\n            else:\n                raise HTTP(404)\n        headers['Content-Length'] = fsize\n        bytes = None\n    if request and request.env.web2py_use_wsgi_file_wrapper:\n        wrapped = request.env.wsgi_file_wrapper(stream, chunk_size)\n    else:\n        wrapped = streamer(stream, chunk_size=chunk_size, bytes=bytes)\n    raise HTTP(status, wrapped, **headers)\n", "path": "gluon/streamer.py"}]}