mirror of
https://github.com/python/cpython.git
synced 2025-07-16 15:55:18 +00:00
Update wsgiref for PEP 3333, and fix errors introduced into the test suite by converting type() checks to isinstance().
(When WSGI specifies a built-in type, it does NOT mean "this type or a subclass" -- it means 'type(x) is SpecifiedType'.)
This commit is contained in:
parent
5a43f72d1b
commit
e159422ce9
4 changed files with 67 additions and 93 deletions
|
@ -47,7 +47,7 @@ def hello_app(environ,start_response):
|
||||||
('Content-Type','text/plain'),
|
('Content-Type','text/plain'),
|
||||||
('Date','Mon, 05 Jun 2006 18:49:54 GMT')
|
('Date','Mon, 05 Jun 2006 18:49:54 GMT')
|
||||||
])
|
])
|
||||||
return ["Hello, world!"]
|
return [b"Hello, world!"]
|
||||||
|
|
||||||
def run_amock(app=hello_app, data=b"GET / HTTP/1.0\n\n"):
|
def run_amock(app=hello_app, data=b"GET / HTTP/1.0\n\n"):
|
||||||
server = make_server("", 80, app, MockServer, MockHandler)
|
server = make_server("", 80, app, MockServer, MockHandler)
|
||||||
|
@ -165,7 +165,7 @@ class IntegrationTests(TestCase):
|
||||||
def test_wsgi_input(self):
|
def test_wsgi_input(self):
|
||||||
def bad_app(e,s):
|
def bad_app(e,s):
|
||||||
e["wsgi.input"].read()
|
e["wsgi.input"].read()
|
||||||
s(b"200 OK", [(b"Content-Type", b"text/plain; charset=utf-8")])
|
s("200 OK", [("Content-Type", "text/plain; charset=utf-8")])
|
||||||
return [b"data"]
|
return [b"data"]
|
||||||
out, err = run_amock(validator(bad_app))
|
out, err = run_amock(validator(bad_app))
|
||||||
self.assertTrue(out.endswith(
|
self.assertTrue(out.endswith(
|
||||||
|
@ -177,8 +177,8 @@ class IntegrationTests(TestCase):
|
||||||
|
|
||||||
def test_bytes_validation(self):
|
def test_bytes_validation(self):
|
||||||
def app(e, s):
|
def app(e, s):
|
||||||
s(b"200 OK", [
|
s("200 OK", [
|
||||||
(b"Content-Type", b"text/plain; charset=utf-8"),
|
("Content-Type", "text/plain; charset=utf-8"),
|
||||||
("Date", "Wed, 24 Dec 2008 13:29:32 GMT"),
|
("Date", "Wed, 24 Dec 2008 13:29:32 GMT"),
|
||||||
])
|
])
|
||||||
return [b"data"]
|
return [b"data"]
|
||||||
|
@ -420,29 +420,6 @@ class HeaderTests(TestCase):
|
||||||
'\r\n'
|
'\r\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
def testBytes(self):
|
|
||||||
h = Headers([
|
|
||||||
(b"Content-Type", b"text/plain; charset=utf-8"),
|
|
||||||
])
|
|
||||||
self.assertEqual("text/plain; charset=utf-8", h.get("Content-Type"))
|
|
||||||
|
|
||||||
h[b"Foo"] = bytes(b"bar")
|
|
||||||
self.assertEqual("bar", h.get("Foo"))
|
|
||||||
self.assertEqual("bar", h.get(b"Foo"))
|
|
||||||
|
|
||||||
h.setdefault(b"Bar", b"foo")
|
|
||||||
self.assertEqual("foo", h.get("Bar"))
|
|
||||||
self.assertEqual("foo", h.get(b"Bar"))
|
|
||||||
|
|
||||||
h.add_header(b'content-disposition', b'attachment',
|
|
||||||
filename=b'bud.gif')
|
|
||||||
self.assertEqual('attachment; filename="bud.gif"',
|
|
||||||
h.get("content-disposition"))
|
|
||||||
|
|
||||||
del h['content-disposition']
|
|
||||||
self.assertNotIn(b'content-disposition', h)
|
|
||||||
|
|
||||||
|
|
||||||
class ErrorHandler(BaseCGIHandler):
|
class ErrorHandler(BaseCGIHandler):
|
||||||
"""Simple handler subclass for testing BaseHandler"""
|
"""Simple handler subclass for testing BaseHandler"""
|
||||||
|
|
||||||
|
@ -529,10 +506,10 @@ class HandlerTests(TestCase):
|
||||||
|
|
||||||
def trivial_app1(e,s):
|
def trivial_app1(e,s):
|
||||||
s('200 OK',[])
|
s('200 OK',[])
|
||||||
return [e['wsgi.url_scheme']]
|
return [e['wsgi.url_scheme'].encode('iso-8859-1')]
|
||||||
|
|
||||||
def trivial_app2(e,s):
|
def trivial_app2(e,s):
|
||||||
s('200 OK',[])(e['wsgi.url_scheme'])
|
s('200 OK',[])(e['wsgi.url_scheme'].encode('iso-8859-1'))
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def trivial_app3(e,s):
|
def trivial_app3(e,s):
|
||||||
|
@ -590,13 +567,13 @@ class HandlerTests(TestCase):
|
||||||
("Status: %s\r\n"
|
("Status: %s\r\n"
|
||||||
"Content-Type: text/plain\r\n"
|
"Content-Type: text/plain\r\n"
|
||||||
"Content-Length: %d\r\n"
|
"Content-Length: %d\r\n"
|
||||||
"\r\n%s" % (h.error_status,len(h.error_body),h.error_body)
|
"\r\n" % (h.error_status,len(h.error_body))).encode('iso-8859-1')
|
||||||
).encode("iso-8859-1"))
|
+ h.error_body)
|
||||||
|
|
||||||
self.assertIn("AssertionError", h.stderr.getvalue())
|
self.assertIn("AssertionError", h.stderr.getvalue())
|
||||||
|
|
||||||
def testErrorAfterOutput(self):
|
def testErrorAfterOutput(self):
|
||||||
MSG = "Some output has been sent"
|
MSG = b"Some output has been sent"
|
||||||
def error_app(e,s):
|
def error_app(e,s):
|
||||||
s("200 OK",[])(MSG)
|
s("200 OK",[])(MSG)
|
||||||
raise AssertionError("This should be caught by handler")
|
raise AssertionError("This should be caught by handler")
|
||||||
|
@ -605,7 +582,7 @@ class HandlerTests(TestCase):
|
||||||
h.run(error_app)
|
h.run(error_app)
|
||||||
self.assertEqual(h.stdout.getvalue(),
|
self.assertEqual(h.stdout.getvalue(),
|
||||||
("Status: 200 OK\r\n"
|
("Status: 200 OK\r\n"
|
||||||
"\r\n"+MSG).encode("iso-8859-1"))
|
"\r\n".encode("iso-8859-1")+MSG))
|
||||||
self.assertIn("AssertionError", h.stderr.getvalue())
|
self.assertIn("AssertionError", h.stderr.getvalue())
|
||||||
|
|
||||||
|
|
||||||
|
@ -654,8 +631,8 @@ class HandlerTests(TestCase):
|
||||||
|
|
||||||
def testBytesData(self):
|
def testBytesData(self):
|
||||||
def app(e, s):
|
def app(e, s):
|
||||||
s(b"200 OK", [
|
s("200 OK", [
|
||||||
(b"Content-Type", b"text/plain; charset=utf-8"),
|
("Content-Type", "text/plain; charset=utf-8"),
|
||||||
])
|
])
|
||||||
return [b"data"]
|
return [b"data"]
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ class BaseHandler:
|
||||||
traceback_limit = None # Print entire traceback to self.get_stderr()
|
traceback_limit = None # Print entire traceback to self.get_stderr()
|
||||||
error_status = "500 Internal Server Error"
|
error_status = "500 Internal Server Error"
|
||||||
error_headers = [('Content-Type','text/plain')]
|
error_headers = [('Content-Type','text/plain')]
|
||||||
error_body = "A server error occurred. Please contact the administrator."
|
error_body = b"A server error occurred. Please contact the administrator."
|
||||||
|
|
||||||
# State variables (don't mess with these)
|
# State variables (don't mess with these)
|
||||||
status = result = None
|
status = result = None
|
||||||
|
@ -137,7 +137,7 @@ class BaseHandler:
|
||||||
self.set_content_length()
|
self.set_content_length()
|
||||||
|
|
||||||
def start_response(self, status, headers,exc_info=None):
|
def start_response(self, status, headers,exc_info=None):
|
||||||
"""'start_response()' callable as specified by PEP 333"""
|
"""'start_response()' callable as specified by PEP 3333"""
|
||||||
|
|
||||||
if exc_info:
|
if exc_info:
|
||||||
try:
|
try:
|
||||||
|
@ -149,49 +149,48 @@ class BaseHandler:
|
||||||
elif self.headers is not None:
|
elif self.headers is not None:
|
||||||
raise AssertionError("Headers already set!")
|
raise AssertionError("Headers already set!")
|
||||||
|
|
||||||
|
self.status = status
|
||||||
|
self.headers = self.headers_class(headers)
|
||||||
status = self._convert_string_type(status, "Status")
|
status = self._convert_string_type(status, "Status")
|
||||||
assert len(status)>=4,"Status must be at least 4 characters"
|
assert len(status)>=4,"Status must be at least 4 characters"
|
||||||
assert int(status[:3]),"Status message must begin w/3-digit code"
|
assert int(status[:3]),"Status message must begin w/3-digit code"
|
||||||
assert status[3]==" ", "Status message must have a space after code"
|
assert status[3]==" ", "Status message must have a space after code"
|
||||||
|
|
||||||
str_headers = []
|
if __debug__:
|
||||||
for name,val in headers:
|
for name, val in headers:
|
||||||
name = self._convert_string_type(name, "Header name")
|
name = self._convert_string_type(name, "Header name")
|
||||||
val = self._convert_string_type(val, "Header value")
|
val = self._convert_string_type(val, "Header value")
|
||||||
str_headers.append((name, val))
|
assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed"
|
||||||
assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed"
|
|
||||||
|
|
||||||
self.status = status
|
|
||||||
self.headers = self.headers_class(str_headers)
|
|
||||||
return self.write
|
return self.write
|
||||||
|
|
||||||
def _convert_string_type(self, value, title):
|
def _convert_string_type(self, value, title):
|
||||||
"""Convert/check value type."""
|
"""Convert/check value type."""
|
||||||
if isinstance(value, str):
|
if type(value) is str:
|
||||||
return value
|
return value
|
||||||
assert isinstance(value, bytes), \
|
raise AssertionError(
|
||||||
"{0} must be a string or bytes object (not {1})".format(title, value)
|
"{0} must be of type str (got {1})".format(title, repr(value))
|
||||||
return str(value, "iso-8859-1")
|
)
|
||||||
|
|
||||||
def send_preamble(self):
|
def send_preamble(self):
|
||||||
"""Transmit version/status/date/server, via self._write()"""
|
"""Transmit version/status/date/server, via self._write()"""
|
||||||
if self.origin_server:
|
if self.origin_server:
|
||||||
if self.client_is_modern():
|
if self.client_is_modern():
|
||||||
self._write('HTTP/%s %s\r\n' % (self.http_version,self.status))
|
self._write(('HTTP/%s %s\r\n' % (self.http_version,self.status)).encode('iso-8859-1'))
|
||||||
if 'Date' not in self.headers:
|
if 'Date' not in self.headers:
|
||||||
self._write(
|
self._write(
|
||||||
'Date: %s\r\n' % format_date_time(time.time())
|
('Date: %s\r\n' % format_date_time(time.time())).encode('iso-8859-1')
|
||||||
)
|
)
|
||||||
if self.server_software and 'Server' not in self.headers:
|
if self.server_software and 'Server' not in self.headers:
|
||||||
self._write('Server: %s\r\n' % self.server_software)
|
self._write(('Server: %s\r\n' % self.server_software).encode('iso-8859-1'))
|
||||||
else:
|
else:
|
||||||
self._write('Status: %s\r\n' % self.status)
|
self._write(('Status: %s\r\n' % self.status).encode('iso-8859-1'))
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
"""'write()' callable as specified by PEP 333"""
|
"""'write()' callable as specified by PEP 3333"""
|
||||||
|
|
||||||
assert isinstance(data, (str, bytes)), \
|
assert type(data) is bytes, \
|
||||||
"write() argument must be a string or bytes"
|
"write() argument must be a bytes instance"
|
||||||
|
|
||||||
if not self.status:
|
if not self.status:
|
||||||
raise AssertionError("write() before start_response()")
|
raise AssertionError("write() before start_response()")
|
||||||
|
@ -256,7 +255,7 @@ class BaseHandler:
|
||||||
self.headers_sent = True
|
self.headers_sent = True
|
||||||
if not self.origin_server or self.client_is_modern():
|
if not self.origin_server or self.client_is_modern():
|
||||||
self.send_preamble()
|
self.send_preamble()
|
||||||
self._write(str(self.headers))
|
self._write(bytes(self.headers))
|
||||||
|
|
||||||
|
|
||||||
def result_is_file(self):
|
def result_is_file(self):
|
||||||
|
@ -376,12 +375,6 @@ class SimpleHandler(BaseHandler):
|
||||||
self.environ.update(self.base_env)
|
self.environ.update(self.base_env)
|
||||||
|
|
||||||
def _write(self,data):
|
def _write(self,data):
|
||||||
if isinstance(data, str):
|
|
||||||
try:
|
|
||||||
data = data.encode("iso-8859-1")
|
|
||||||
except UnicodeEncodeError:
|
|
||||||
raise ValueError("Unicode data must contain only code points"
|
|
||||||
" representable in ISO-8859-1 encoding")
|
|
||||||
self.stdout.write(data)
|
self.stdout.write(data)
|
||||||
|
|
||||||
def _flush(self):
|
def _flush(self):
|
||||||
|
|
|
@ -30,21 +30,20 @@ class Headers:
|
||||||
"""Manage a collection of HTTP response headers"""
|
"""Manage a collection of HTTP response headers"""
|
||||||
|
|
||||||
def __init__(self,headers):
|
def __init__(self,headers):
|
||||||
if not isinstance(headers, list):
|
if type(headers) is not list:
|
||||||
raise TypeError("Headers must be a list of name/value tuples")
|
raise TypeError("Headers must be a list of name/value tuples")
|
||||||
self._headers = []
|
self._headers = headers
|
||||||
for k, v in headers:
|
if __debug__:
|
||||||
k = self._convert_string_type(k)
|
for k, v in headers:
|
||||||
v = self._convert_string_type(v)
|
self._convert_string_type(k)
|
||||||
self._headers.append((k, v))
|
self._convert_string_type(v)
|
||||||
|
|
||||||
def _convert_string_type(self, value):
|
def _convert_string_type(self, value):
|
||||||
"""Convert/check value type."""
|
"""Convert/check value type."""
|
||||||
if isinstance(value, str):
|
if type(value) is str:
|
||||||
return value
|
return value
|
||||||
assert isinstance(value, bytes), ("Header names/values must be"
|
raise AssertionError("Header names/values must be"
|
||||||
" a string or bytes object (not {0})".format(value))
|
" of type str (got {0})".format(repr(value)))
|
||||||
return str(value, "iso-8859-1")
|
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
"""Return the total number of headers, including duplicates."""
|
"""Return the total number of headers, including duplicates."""
|
||||||
|
@ -139,6 +138,9 @@ class Headers:
|
||||||
suitable for direct HTTP transmission."""
|
suitable for direct HTTP transmission."""
|
||||||
return '\r\n'.join(["%s: %s" % kv for kv in self._headers]+['',''])
|
return '\r\n'.join(["%s: %s" % kv for kv in self._headers]+['',''])
|
||||||
|
|
||||||
|
def __bytes__(self):
|
||||||
|
return str(self).encode('iso-8859-1')
|
||||||
|
|
||||||
def setdefault(self,name,value):
|
def setdefault(self,name,value):
|
||||||
"""Return first matching header value for 'name', or 'value'
|
"""Return first matching header value for 'name', or 'value'
|
||||||
|
|
||||||
|
|
|
@ -128,11 +128,10 @@ def assert_(cond, *args):
|
||||||
raise AssertionError(*args)
|
raise AssertionError(*args)
|
||||||
|
|
||||||
def check_string_type(value, title):
|
def check_string_type(value, title):
|
||||||
if isinstance(value, str):
|
if type (value) is str:
|
||||||
return value
|
return value
|
||||||
assert isinstance(value, bytes), \
|
raise AssertionError(
|
||||||
"{0} must be a string or bytes object (not {1})".format(title, value)
|
"{0} must be of type str (got {1})".format(title, repr(value)))
|
||||||
return str(value, "iso-8859-1")
|
|
||||||
|
|
||||||
def validator(application):
|
def validator(application):
|
||||||
|
|
||||||
|
@ -197,20 +196,21 @@ class InputWrapper:
|
||||||
def read(self, *args):
|
def read(self, *args):
|
||||||
assert_(len(args) == 1)
|
assert_(len(args) == 1)
|
||||||
v = self.input.read(*args)
|
v = self.input.read(*args)
|
||||||
assert_(isinstance(v, bytes))
|
assert_(type(v) is bytes)
|
||||||
return v
|
return v
|
||||||
|
|
||||||
def readline(self):
|
def readline(self, *args):
|
||||||
v = self.input.readline()
|
assert_(len(args) <= 1)
|
||||||
assert_(isinstance(v, bytes))
|
v = self.input.readline(*args)
|
||||||
|
assert_(type(v) is bytes)
|
||||||
return v
|
return v
|
||||||
|
|
||||||
def readlines(self, *args):
|
def readlines(self, *args):
|
||||||
assert_(len(args) <= 1)
|
assert_(len(args) <= 1)
|
||||||
lines = self.input.readlines(*args)
|
lines = self.input.readlines(*args)
|
||||||
assert_(isinstance(lines, list))
|
assert_(type(lines) is list)
|
||||||
for line in lines:
|
for line in lines:
|
||||||
assert_(isinstance(line, bytes))
|
assert_(type(line) is bytes)
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
|
@ -229,7 +229,7 @@ class ErrorWrapper:
|
||||||
self.errors = wsgi_errors
|
self.errors = wsgi_errors
|
||||||
|
|
||||||
def write(self, s):
|
def write(self, s):
|
||||||
assert_(isinstance(s, str))
|
assert_(type(s) is str)
|
||||||
self.errors.write(s)
|
self.errors.write(s)
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
|
@ -248,7 +248,7 @@ class WriteWrapper:
|
||||||
self.writer = wsgi_writer
|
self.writer = wsgi_writer
|
||||||
|
|
||||||
def __call__(self, s):
|
def __call__(self, s):
|
||||||
assert_(isinstance(s, (str, bytes)))
|
assert_(type(s) is bytes)
|
||||||
self.writer(s)
|
self.writer(s)
|
||||||
|
|
||||||
class PartialIteratorWrapper:
|
class PartialIteratorWrapper:
|
||||||
|
@ -275,6 +275,8 @@ class IteratorWrapper:
|
||||||
assert_(not self.closed,
|
assert_(not self.closed,
|
||||||
"Iterator read after closed")
|
"Iterator read after closed")
|
||||||
v = next(self.iterator)
|
v = next(self.iterator)
|
||||||
|
if type(v) is not bytes:
|
||||||
|
assert_(False, "Iterator yielded non-bytestring (%r)" % (v,))
|
||||||
if self.check_start_response is not None:
|
if self.check_start_response is not None:
|
||||||
assert_(self.check_start_response,
|
assert_(self.check_start_response,
|
||||||
"The application returns and we started iterating over its body, but start_response has not yet been called")
|
"The application returns and we started iterating over its body, but start_response has not yet been called")
|
||||||
|
@ -294,7 +296,7 @@ class IteratorWrapper:
|
||||||
"Iterator garbage collected without being closed")
|
"Iterator garbage collected without being closed")
|
||||||
|
|
||||||
def check_environ(environ):
|
def check_environ(environ):
|
||||||
assert_(isinstance(environ, dict),
|
assert_(type(environ) is dict,
|
||||||
"Environment is not of the right type: %r (environment: %r)"
|
"Environment is not of the right type: %r (environment: %r)"
|
||||||
% (type(environ), environ))
|
% (type(environ), environ))
|
||||||
|
|
||||||
|
@ -321,11 +323,11 @@ def check_environ(environ):
|
||||||
if '.' in key:
|
if '.' in key:
|
||||||
# Extension, we don't care about its type
|
# Extension, we don't care about its type
|
||||||
continue
|
continue
|
||||||
assert_(isinstance(environ[key], str),
|
assert_(type(environ[key]) is str,
|
||||||
"Environmental variable %s is not a string: %r (value: %r)"
|
"Environmental variable %s is not a string: %r (value: %r)"
|
||||||
% (key, type(environ[key]), environ[key]))
|
% (key, type(environ[key]), environ[key]))
|
||||||
|
|
||||||
assert_(isinstance(environ['wsgi.version'], tuple),
|
assert_(type(environ['wsgi.version']) is tuple,
|
||||||
"wsgi.version should be a tuple (%r)" % (environ['wsgi.version'],))
|
"wsgi.version should be a tuple (%r)" % (environ['wsgi.version'],))
|
||||||
assert_(environ['wsgi.url_scheme'] in ('http', 'https'),
|
assert_(environ['wsgi.url_scheme'] in ('http', 'https'),
|
||||||
"wsgi.url_scheme unknown: %r" % environ['wsgi.url_scheme'])
|
"wsgi.url_scheme unknown: %r" % environ['wsgi.url_scheme'])
|
||||||
|
@ -385,12 +387,12 @@ def check_status(status):
|
||||||
% status, WSGIWarning)
|
% status, WSGIWarning)
|
||||||
|
|
||||||
def check_headers(headers):
|
def check_headers(headers):
|
||||||
assert_(isinstance(headers, list),
|
assert_(type(headers) is list,
|
||||||
"Headers (%r) must be of type list: %r"
|
"Headers (%r) must be of type list: %r"
|
||||||
% (headers, type(headers)))
|
% (headers, type(headers)))
|
||||||
header_names = {}
|
header_names = {}
|
||||||
for item in headers:
|
for item in headers:
|
||||||
assert_(isinstance(item, tuple),
|
assert_(type(item) is tuple,
|
||||||
"Individual headers (%r) must be of type tuple: %r"
|
"Individual headers (%r) must be of type tuple: %r"
|
||||||
% (item, type(item)))
|
% (item, type(item)))
|
||||||
assert_(len(item) == 2)
|
assert_(len(item) == 2)
|
||||||
|
@ -428,14 +430,14 @@ def check_content_type(status, headers):
|
||||||
assert_(0, "No Content-Type header found in headers (%s)" % headers)
|
assert_(0, "No Content-Type header found in headers (%s)" % headers)
|
||||||
|
|
||||||
def check_exc_info(exc_info):
|
def check_exc_info(exc_info):
|
||||||
assert_(exc_info is None or isinstance(exc_info, tuple),
|
assert_(exc_info is None or type(exc_info) is tuple,
|
||||||
"exc_info (%r) is not a tuple: %r" % (exc_info, type(exc_info)))
|
"exc_info (%r) is not a tuple: %r" % (exc_info, type(exc_info)))
|
||||||
# More exc_info checks?
|
# More exc_info checks?
|
||||||
|
|
||||||
def check_iterator(iterator):
|
def check_iterator(iterator):
|
||||||
# Technically a string is legal, which is why it's a really bad
|
# Technically a bytestring is legal, which is why it's a really bad
|
||||||
# idea, because it may cause the response to be returned
|
# idea, because it may cause the response to be returned
|
||||||
# character-by-character
|
# character-by-character
|
||||||
assert_(not isinstance(iterator, (str, bytes)),
|
assert_(not isinstance(iterator, (str, bytes)),
|
||||||
"You should not return a string as your application iterator, "
|
"You should not return a string as your application iterator, "
|
||||||
"instead return a single-item list containing that string.")
|
"instead return a single-item list containing a bytestring.")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue