Skip to content

Commit e159422

Browse files
committed
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'.)
1 parent 5a43f72 commit e159422

4 files changed

Lines changed: 67 additions & 93 deletions

File tree

Lib/test/test_wsgiref.py

Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def hello_app(environ,start_response):
4747
('Content-Type','text/plain'),
4848
('Date','Mon, 05 Jun 2006 18:49:54 GMT')
4949
])
50-
return ["Hello, world!"]
50+
return [b"Hello, world!"]
5151

5252
def run_amock(app=hello_app, data=b"GET / HTTP/1.0\n\n"):
5353
server = make_server("", 80, app, MockServer, MockHandler)
@@ -165,7 +165,7 @@ def bad_app(environ,start_response):
165165
def test_wsgi_input(self):
166166
def bad_app(e,s):
167167
e["wsgi.input"].read()
168-
s(b"200 OK", [(b"Content-Type", b"text/plain; charset=utf-8")])
168+
s("200 OK", [("Content-Type", "text/plain; charset=utf-8")])
169169
return [b"data"]
170170
out, err = run_amock(validator(bad_app))
171171
self.assertTrue(out.endswith(
@@ -177,8 +177,8 @@ def bad_app(e,s):
177177

178178
def test_bytes_validation(self):
179179
def app(e, s):
180-
s(b"200 OK", [
181-
(b"Content-Type", b"text/plain; charset=utf-8"),
180+
s("200 OK", [
181+
("Content-Type", "text/plain; charset=utf-8"),
182182
("Date", "Wed, 24 Dec 2008 13:29:32 GMT"),
183183
])
184184
return [b"data"]
@@ -420,29 +420,6 @@ def testExtras(self):
420420
'\r\n'
421421
)
422422

423-
def testBytes(self):
424-
h = Headers([
425-
(b"Content-Type", b"text/plain; charset=utf-8"),
426-
])
427-
self.assertEqual("text/plain; charset=utf-8", h.get("Content-Type"))
428-
429-
h[b"Foo"] = bytes(b"bar")
430-
self.assertEqual("bar", h.get("Foo"))
431-
self.assertEqual("bar", h.get(b"Foo"))
432-
433-
h.setdefault(b"Bar", b"foo")
434-
self.assertEqual("foo", h.get("Bar"))
435-
self.assertEqual("foo", h.get(b"Bar"))
436-
437-
h.add_header(b'content-disposition', b'attachment',
438-
filename=b'bud.gif')
439-
self.assertEqual('attachment; filename="bud.gif"',
440-
h.get("content-disposition"))
441-
442-
del h['content-disposition']
443-
self.assertNotIn(b'content-disposition', h)
444-
445-
446423
class ErrorHandler(BaseCGIHandler):
447424
"""Simple handler subclass for testing BaseHandler"""
448425

@@ -529,10 +506,10 @@ def testContentLength(self):
529506

530507
def trivial_app1(e,s):
531508
s('200 OK',[])
532-
return [e['wsgi.url_scheme']]
509+
return [e['wsgi.url_scheme'].encode('iso-8859-1')]
533510

534511
def trivial_app2(e,s):
535-
s('200 OK',[])(e['wsgi.url_scheme'])
512+
s('200 OK',[])(e['wsgi.url_scheme'].encode('iso-8859-1'))
536513
return []
537514

538515
def trivial_app3(e,s):
@@ -590,13 +567,13 @@ def error_app(e,s):
590567
("Status: %s\r\n"
591568
"Content-Type: text/plain\r\n"
592569
"Content-Length: %d\r\n"
593-
"\r\n%s" % (h.error_status,len(h.error_body),h.error_body)
594-
).encode("iso-8859-1"))
570+
"\r\n" % (h.error_status,len(h.error_body))).encode('iso-8859-1')
571+
+ h.error_body)
595572

596573
self.assertIn("AssertionError", h.stderr.getvalue())
597574

598575
def testErrorAfterOutput(self):
599-
MSG = "Some output has been sent"
576+
MSG = b"Some output has been sent"
600577
def error_app(e,s):
601578
s("200 OK",[])(MSG)
602579
raise AssertionError("This should be caught by handler")
@@ -605,7 +582,7 @@ def error_app(e,s):
605582
h.run(error_app)
606583
self.assertEqual(h.stdout.getvalue(),
607584
("Status: 200 OK\r\n"
608-
"\r\n"+MSG).encode("iso-8859-1"))
585+
"\r\n".encode("iso-8859-1")+MSG))
609586
self.assertIn("AssertionError", h.stderr.getvalue())
610587

611588

@@ -654,8 +631,8 @@ def non_error_app(e,s):
654631

655632
def testBytesData(self):
656633
def app(e, s):
657-
s(b"200 OK", [
658-
(b"Content-Type", b"text/plain; charset=utf-8"),
634+
s("200 OK", [
635+
("Content-Type", "text/plain; charset=utf-8"),
659636
])
660637
return [b"data"]
661638

Lib/wsgiref/handlers.py

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class BaseHandler:
4646
traceback_limit = None # Print entire traceback to self.get_stderr()
4747
error_status = "500 Internal Server Error"
4848
error_headers = [('Content-Type','text/plain')]
49-
error_body = "A server error occurred. Please contact the administrator."
49+
error_body = b"A server error occurred. Please contact the administrator."
5050

5151
# State variables (don't mess with these)
5252
status = result = None
@@ -137,7 +137,7 @@ def cleanup_headers(self):
137137
self.set_content_length()
138138

139139
def start_response(self, status, headers,exc_info=None):
140-
"""'start_response()' callable as specified by PEP 333"""
140+
"""'start_response()' callable as specified by PEP 3333"""
141141

142142
if exc_info:
143143
try:
@@ -149,49 +149,48 @@ def start_response(self, status, headers,exc_info=None):
149149
elif self.headers is not None:
150150
raise AssertionError("Headers already set!")
151151

152+
self.status = status
153+
self.headers = self.headers_class(headers)
152154
status = self._convert_string_type(status, "Status")
153155
assert len(status)>=4,"Status must be at least 4 characters"
154156
assert int(status[:3]),"Status message must begin w/3-digit code"
155157
assert status[3]==" ", "Status message must have a space after code"
156158

157-
str_headers = []
158-
for name,val in headers:
159-
name = self._convert_string_type(name, "Header name")
160-
val = self._convert_string_type(val, "Header value")
161-
str_headers.append((name, val))
162-
assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed"
159+
if __debug__:
160+
for name, val in headers:
161+
name = self._convert_string_type(name, "Header name")
162+
val = self._convert_string_type(val, "Header value")
163+
assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed"
163164

164-
self.status = status
165-
self.headers = self.headers_class(str_headers)
166165
return self.write
167166

168167
def _convert_string_type(self, value, title):
169168
"""Convert/check value type."""
170-
if isinstance(value, str):
169+
if type(value) is str:
171170
return value
172-
assert isinstance(value, bytes), \
173-
"{0} must be a string or bytes object (not {1})".format(title, value)
174-
return str(value, "iso-8859-1")
171+
raise AssertionError(
172+
"{0} must be of type str (got {1})".format(title, repr(value))
173+
)
175174

176175
def send_preamble(self):
177176
"""Transmit version/status/date/server, via self._write()"""
178177
if self.origin_server:
179178
if self.client_is_modern():
180-
self._write('HTTP/%s %s\r\n' % (self.http_version,self.status))
179+
self._write(('HTTP/%s %s\r\n' % (self.http_version,self.status)).encode('iso-8859-1'))
181180
if 'Date' not in self.headers:
182181
self._write(
183-
'Date: %s\r\n' % format_date_time(time.time())
182+
('Date: %s\r\n' % format_date_time(time.time())).encode('iso-8859-1')
184183
)
185184
if self.server_software and 'Server' not in self.headers:
186-
self._write('Server: %s\r\n' % self.server_software)
185+
self._write(('Server: %s\r\n' % self.server_software).encode('iso-8859-1'))
187186
else:
188-
self._write('Status: %s\r\n' % self.status)
187+
self._write(('Status: %s\r\n' % self.status).encode('iso-8859-1'))
189188

190189
def write(self, data):
191-
"""'write()' callable as specified by PEP 333"""
190+
"""'write()' callable as specified by PEP 3333"""
192191

193-
assert isinstance(data, (str, bytes)), \
194-
"write() argument must be a string or bytes"
192+
assert type(data) is bytes, \
193+
"write() argument must be a bytes instance"
195194

196195
if not self.status:
197196
raise AssertionError("write() before start_response()")
@@ -256,7 +255,7 @@ def send_headers(self):
256255
self.headers_sent = True
257256
if not self.origin_server or self.client_is_modern():
258257
self.send_preamble()
259-
self._write(str(self.headers))
258+
self._write(bytes(self.headers))
260259

261260

262261
def result_is_file(self):
@@ -376,12 +375,6 @@ def add_cgi_vars(self):
376375
self.environ.update(self.base_env)
377376

378377
def _write(self,data):
379-
if isinstance(data, str):
380-
try:
381-
data = data.encode("iso-8859-1")
382-
except UnicodeEncodeError:
383-
raise ValueError("Unicode data must contain only code points"
384-
" representable in ISO-8859-1 encoding")
385378
self.stdout.write(data)
386379

387380
def _flush(self):

Lib/wsgiref/headers.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,20 @@ class Headers:
3030
"""Manage a collection of HTTP response headers"""
3131

3232
def __init__(self,headers):
33-
if not isinstance(headers, list):
33+
if type(headers) is not list:
3434
raise TypeError("Headers must be a list of name/value tuples")
35-
self._headers = []
36-
for k, v in headers:
37-
k = self._convert_string_type(k)
38-
v = self._convert_string_type(v)
39-
self._headers.append((k, v))
35+
self._headers = headers
36+
if __debug__:
37+
for k, v in headers:
38+
self._convert_string_type(k)
39+
self._convert_string_type(v)
4040

4141
def _convert_string_type(self, value):
4242
"""Convert/check value type."""
43-
if isinstance(value, str):
43+
if type(value) is str:
4444
return value
45-
assert isinstance(value, bytes), ("Header names/values must be"
46-
" a string or bytes object (not {0})".format(value))
47-
return str(value, "iso-8859-1")
45+
raise AssertionError("Header names/values must be"
46+
" of type str (got {0})".format(repr(value)))
4847

4948
def __len__(self):
5049
"""Return the total number of headers, including duplicates."""
@@ -139,6 +138,9 @@ def __str__(self):
139138
suitable for direct HTTP transmission."""
140139
return '\r\n'.join(["%s: %s" % kv for kv in self._headers]+['',''])
141140

141+
def __bytes__(self):
142+
return str(self).encode('iso-8859-1')
143+
142144
def setdefault(self,name,value):
143145
"""Return first matching header value for 'name', or 'value'
144146

Lib/wsgiref/validate.py

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,10 @@ def assert_(cond, *args):
128128
raise AssertionError(*args)
129129

130130
def check_string_type(value, title):
131-
if isinstance(value, str):
131+
if type (value) is str:
132132
return value
133-
assert isinstance(value, bytes), \
134-
"{0} must be a string or bytes object (not {1})".format(title, value)
135-
return str(value, "iso-8859-1")
133+
raise AssertionError(
134+
"{0} must be of type str (got {1})".format(title, repr(value)))
136135

137136
def validator(application):
138137

@@ -197,20 +196,21 @@ def __init__(self, wsgi_input):
197196
def read(self, *args):
198197
assert_(len(args) == 1)
199198
v = self.input.read(*args)
200-
assert_(isinstance(v, bytes))
199+
assert_(type(v) is bytes)
201200
return v
202201

203-
def readline(self):
204-
v = self.input.readline()
205-
assert_(isinstance(v, bytes))
202+
def readline(self, *args):
203+
assert_(len(args) <= 1)
204+
v = self.input.readline(*args)
205+
assert_(type(v) is bytes)
206206
return v
207207

208208
def readlines(self, *args):
209209
assert_(len(args) <= 1)
210210
lines = self.input.readlines(*args)
211-
assert_(isinstance(lines, list))
211+
assert_(type(lines) is list)
212212
for line in lines:
213-
assert_(isinstance(line, bytes))
213+
assert_(type(line) is bytes)
214214
return lines
215215

216216
def __iter__(self):
@@ -229,7 +229,7 @@ def __init__(self, wsgi_errors):
229229
self.errors = wsgi_errors
230230

231231
def write(self, s):
232-
assert_(isinstance(s, str))
232+
assert_(type(s) is str)
233233
self.errors.write(s)
234234

235235
def flush(self):
@@ -248,7 +248,7 @@ def __init__(self, wsgi_writer):
248248
self.writer = wsgi_writer
249249

250250
def __call__(self, s):
251-
assert_(isinstance(s, (str, bytes)))
251+
assert_(type(s) is bytes)
252252
self.writer(s)
253253

254254
class PartialIteratorWrapper:
@@ -275,6 +275,8 @@ def __next__(self):
275275
assert_(not self.closed,
276276
"Iterator read after closed")
277277
v = next(self.iterator)
278+
if type(v) is not bytes:
279+
assert_(False, "Iterator yielded non-bytestring (%r)" % (v,))
278280
if self.check_start_response is not None:
279281
assert_(self.check_start_response,
280282
"The application returns and we started iterating over its body, but start_response has not yet been called")
@@ -294,7 +296,7 @@ def __del__(self):
294296
"Iterator garbage collected without being closed")
295297

296298
def check_environ(environ):
297-
assert_(isinstance(environ, dict),
299+
assert_(type(environ) is dict,
298300
"Environment is not of the right type: %r (environment: %r)"
299301
% (type(environ), environ))
300302

@@ -321,11 +323,11 @@ def check_environ(environ):
321323
if '.' in key:
322324
# Extension, we don't care about its type
323325
continue
324-
assert_(isinstance(environ[key], str),
326+
assert_(type(environ[key]) is str,
325327
"Environmental variable %s is not a string: %r (value: %r)"
326328
% (key, type(environ[key]), environ[key]))
327329

328-
assert_(isinstance(environ['wsgi.version'], tuple),
330+
assert_(type(environ['wsgi.version']) is tuple,
329331
"wsgi.version should be a tuple (%r)" % (environ['wsgi.version'],))
330332
assert_(environ['wsgi.url_scheme'] in ('http', 'https'),
331333
"wsgi.url_scheme unknown: %r" % environ['wsgi.url_scheme'])
@@ -385,12 +387,12 @@ def check_status(status):
385387
% status, WSGIWarning)
386388

387389
def check_headers(headers):
388-
assert_(isinstance(headers, list),
390+
assert_(type(headers) is list,
389391
"Headers (%r) must be of type list: %r"
390392
% (headers, type(headers)))
391393
header_names = {}
392394
for item in headers:
393-
assert_(isinstance(item, tuple),
395+
assert_(type(item) is tuple,
394396
"Individual headers (%r) must be of type tuple: %r"
395397
% (item, type(item)))
396398
assert_(len(item) == 2)
@@ -428,14 +430,14 @@ def check_content_type(status, headers):
428430
assert_(0, "No Content-Type header found in headers (%s)" % headers)
429431

430432
def check_exc_info(exc_info):
431-
assert_(exc_info is None or isinstance(exc_info, tuple),
433+
assert_(exc_info is None or type(exc_info) is tuple,
432434
"exc_info (%r) is not a tuple: %r" % (exc_info, type(exc_info)))
433435
# More exc_info checks?
434436

435437
def check_iterator(iterator):
436-
# Technically a string is legal, which is why it's a really bad
438+
# Technically a bytestring is legal, which is why it's a really bad
437439
# idea, because it may cause the response to be returned
438440
# character-by-character
439441
assert_(not isinstance(iterator, (str, bytes)),
440442
"You should not return a string as your application iterator, "
441-
"instead return a single-item list containing that string.")
443+
"instead return a single-item list containing a bytestring.")

0 commit comments

Comments
 (0)