diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py index 32ef0ccf4e638d..c64cf0bb76c454 100644 --- a/Lib/test/test_wsgiref.py +++ b/Lib/test/test_wsgiref.py @@ -189,9 +189,52 @@ def bad_app(e,s): b"A server error occurred. Please contact the administrator." ) self.assertEqual( - err.splitlines()[-2], "AssertionError" + err.splitlines()[-2], + "AssertionError: wsgi.input.read() takes exactly one argument" ) + def test_wsgi_input_readline_type(self): + def bad_app(e, s): + # Monkey-patch the underlying input to return wrong type + class BadInput: + def read(self, size): return b"" + def readline(self, *args): return "not bytes" + def readlines(self, *args): return [] + def __iter__(self): return self + e["wsgi.input"].input = BadInput() + e["wsgi.input"].readline() + s("200 OK", [("Content-Type", "text/plain; charset=utf-8")]) + return [b"data"] + out, err = run_amock(validator(bad_app)) + self.assertIn("wsgi.input.readline() must return bytes", + err.splitlines()[-2]) + + def test_wsgi_errors_write_type(self): + def bad_app(e, s): + e["wsgi.errors"].write(b"not a string") + s("200 OK", [("Content-Type", "text/plain; charset=utf-8")]) + return [b"data"] + out, err = run_amock(validator(bad_app)) + self.assertIn("wsgi.errors.write() requires a str argument", + err.splitlines()[-2]) + + def test_wsgi_write_wrapper_type(self): + def bad_app(e, s): + write = s("200 OK", [("Content-Type", "text/plain; charset=utf-8")]) + write("not bytes") + return [b"data"] + out, err = run_amock(validator(bad_app)) + self.assertIn("write() argument must be a bytes instance", + err.splitlines()[-2]) + + def test_headers_tuple_length(self): + def bad_app(e, s): + s("200 OK", [("Content-Type",)]) + return [b"data"] + out, err = run_amock(validator(bad_app)) + self.assertIn("Individual headers must be 2-item tuples", + err.splitlines()[-2]) + @force_not_colorized def test_bytes_validation(self): def app(e, s): diff --git a/Lib/wsgiref/validate.py b/Lib/wsgiref/validate.py index 1a1853cd63a0d2..8dbbc0f05da717 100644 --- a/Lib/wsgiref/validate.py +++ b/Lib/wsgiref/validate.py @@ -194,23 +194,32 @@ def __init__(self, wsgi_input): self.input = wsgi_input def read(self, *args): - assert_(len(args) == 1) + assert_(len(args) == 1, + "wsgi.input.read() takes exactly one argument") v = self.input.read(*args) - assert_(type(v) is bytes) + assert_(type(v) is bytes, + "wsgi.input.read() must return bytes, got %s" % type(v)) return v def readline(self, *args): - assert_(len(args) <= 1) + assert_(len(args) <= 1, + "wsgi.input.readline() takes at most one argument") v = self.input.readline(*args) - assert_(type(v) is bytes) + assert_(type(v) is bytes, + "wsgi.input.readline() must return bytes, got %s" % type(v)) return v def readlines(self, *args): - assert_(len(args) <= 1) + assert_(len(args) <= 1, + "wsgi.input.readlines() takes at most one argument") lines = self.input.readlines(*args) - assert_(type(lines) is list) + assert_(type(lines) is list, + "wsgi.input.readlines() must return a list, got %s" + % type(lines)) for line in lines: - assert_(type(line) is bytes) + assert_(type(line) is bytes, + "wsgi.input.readlines() must yield bytes, got %s" + % type(line)) return lines def __iter__(self): @@ -226,7 +235,9 @@ def __init__(self, wsgi_errors): self.errors = wsgi_errors def write(self, s): - assert_(type(s) is str) + assert_(type(s) is str, + "wsgi.errors.write() requires a str argument, got %s" + % type(s)) self.errors.write(s) def flush(self): @@ -245,7 +256,9 @@ def __init__(self, wsgi_writer): self.writer = wsgi_writer def __call__(self, s): - assert_(type(s) is bytes) + assert_(type(s) is bytes, + "write() argument must be a bytes instance, got %s" + % type(s)) self.writer(s) class PartialIteratorWrapper: @@ -391,7 +404,8 @@ def check_headers(headers): assert_(type(item) is tuple, "Individual headers (%r) must be of type tuple: %r" % (item, type(item))) - assert_(len(item) == 2) + assert_(len(item) == 2, + "Individual headers must be 2-item tuples, got %r" % (item,)) name, value = item name = check_string_type(name, "Header name") value = check_string_type(value, "Header value") diff --git a/Misc/NEWS.d/next/Library/2026-04-09-12-56-34.gh-issue-58857.Kw9mPx.rst b/Misc/NEWS.d/next/Library/2026-04-09-12-56-34.gh-issue-58857.Kw9mPx.rst new file mode 100644 index 00000000000000..fe46851ce7e5a6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-09-12-56-34.gh-issue-58857.Kw9mPx.rst @@ -0,0 +1,3 @@ +Add descriptive error messages to all bare assertions in +:mod:`wsgiref.validate` middleware, making it easier to diagnose WSGI +compliance issues.