Ticket #7993: wsgi-python3-7993-3.patch

File wsgi-python3-7993-3.patch, 42.9 KB (added by Gavin Panella, 4 years ago)
  • twisted/web/test/test_wsgi.py

    diff --git a/twisted/web/test/test_wsgi.py b/twisted/web/test/test_wsgi.py
    index 4e902e5..e7a003b 100644
    a b Tests for L{twisted.web.wsgi}. 
    88__metaclass__ = type
    99
    1010from sys import exc_info
    11 from urllib import quote
    12 from thread import get_ident
    13 import StringIO, cStringIO, tempfile
     11import tempfile
     12import traceback
    1413
    1514from zope.interface.verify import verifyObject
    1615
     16from twisted.python.compat import intToBytes, urlquote
    1717from twisted.python.log import addObserver, removeObserver, err
    1818from twisted.python.failure import Failure
     19from twisted.python.threadable import getThreadID
    1920from twisted.python.threadpool import ThreadPool
    2021from twisted.internet.defer import Deferred, gatherResults
    2122from twisted.internet import reactor
    2223from twisted.internet.error import ConnectionLost
    23 from twisted.trial.unittest import TestCase
     24from twisted.trial.unittest import TestCase, SkipTest
    2425from twisted.web import http
    2526from twisted.web.resource import IResource, Resource
    2627from twisted.web.server import Request, Site, version
    from twisted.web.wsgi import WSGIResource 
    2829from twisted.web.test.test_web import DummyChannel
    2930
    3031
     32
    3133class SynchronousThreadPool:
    3234    """
    3335    A single-threaded implementation of part of the L{ThreadPool} interface.
    class WSGIResourceTests(TestCase): 
    9698        self.assertRaises(
    9799            RuntimeError,
    98100            self.resource.getChildWithDefault,
    99             "foo", Request(DummyChannel(), False))
     101            b"foo", Request(DummyChannel(), False))
    100102        self.assertRaises(
    101103            RuntimeError,
    102104            self.resource.putChild,
    103             "foo", Resource())
     105            b"foo", Resource())
    104106
    105107
    106108class WSGITestsMixin:
    class WSGITestsMixin: 
    146148            object for this configuration and request (ie, the environment and
    147149            start_response callable).
    148150        """
     151        def _toByteString(string):
     152            # Twisted's HTTP implementation prefers byte strings. As a
     153            # convenience for tests, string arguments are encoded to an
     154            # ISO-8859-1 byte string (if not already) before being passed on.
     155            if isinstance(string, bytes):
     156                return string
     157            else:
     158                return string.encode('iso-8859-1')
     159
    149160        root = WSGIResource(
    150161            self.reactor, self.threadpool, applicationFactory())
    151162        resourceSegments.reverse()
    152163        for seg in resourceSegments:
    153164            tmp = Resource()
    154             tmp.putChild(seg, root)
     165            tmp.putChild(_toByteString(seg), root)
    155166            root = tmp
    156167
    157168        channel = channelFactory()
    158169        channel.site = Site(root)
    159170        request = requestFactory(channel, False)
    160171        for k, v in headers:
    161             request.requestHeaders.addRawHeader(k, v)
     172            request.requestHeaders.addRawHeader(
     173                _toByteString(k), _toByteString(v))
    162174        request.gotLength(0)
    163175        if body:
    164176            request.content.write(body)
    165177            request.content.seek(0)
    166         uri = '/' + '/'.join([quote(seg, safe) for seg in requestSegments])
     178        uri = '/' + '/'.join([urlquote(seg, safe) for seg in requestSegments])
    167179        if query is not None:
    168             uri += '?' + '&'.join(['='.join([quote(k, safe), quote(v, safe)])
     180            uri += '?' + '&'.join(['='.join([urlquote(k, safe), urlquote(v, safe)])
    169181                                   for (k, v) in query])
    170         request.requestReceived(method, uri, 'HTTP/' + version)
     182        request.requestReceived(
     183            _toByteString(method), _toByteString(uri),
     184            b'HTTP/' + _toByteString(version))
    171185        return request
    172186
    173187
    class WSGITestsMixin: 
    199213
    200214
    201215    def getContentFromResponse(self, response):
    202         return response.split('\r\n\r\n', 1)[1]
     216        return response.split(b'\r\n\r\n', 1)[1]
    203217
    204218
    205219
    class EnvironTests(WSGITestsMixin, TestCase): 
    209223    object by L{twisted.web.wsgi.WSGIResource}.
    210224    """
    211225    def environKeyEqual(self, key, value):
    212         def assertEnvironKeyEqual((environ, startResponse)):
     226        def assertEnvironKeyEqual(result):
     227            environ, startResponse = result
    213228            self.assertEqual(environ[key], value)
    214229        return assertEnvironKeyEqual
    215230
    class EnvironTests(WSGITestsMixin, TestCase): 
    220235        parameter which is exactly of type C{dict}.
    221236        """
    222237        d = self.render('GET', '1.1', [], [''])
    223         def cbRendered((environ, startResponse)):
     238        def cbRendered(result):
     239            environ, startResponse = result
    224240            self.assertIdentical(type(environ), dict)
    225241        d.addCallback(cbRendered)
    226242        return d
    class EnvironTests(WSGITestsMixin, TestCase): 
    265281        internal.addCallback(self.environKeyEqual('SCRIPT_NAME', '/foo'))
    266282
    267283        unencoded = self.render(
    268             'GET', '1.1', ['foo', '/', 'bar\xff'], ['foo', '/', 'bar\xff'])
     284            'GET', '1.1', ['foo', '/', b'bar\xff'], ['foo', '/', b'bar\xff'])
    269285        # The RFC says "(not URL-encoded)", even though that makes
    270286        # interpretation of SCRIPT_NAME ambiguous.
    271287        unencoded.addCallback(
    class EnvironTests(WSGITestsMixin, TestCase): 
    302318        internalContainer = self.render('GET', '1.1', ['foo'], ['foo', ''])
    303319        internalContainer.addCallback(self.environKeyEqual('PATH_INFO', '/'))
    304320
    305         unencoded = self.render('GET', '1.1', [], ['foo', '/', 'bar\xff'])
     321        unencoded = self.render('GET', '1.1', [], ['foo', '/', b'bar\xff'])
    306322        unencoded.addCallback(
    307323            self.environKeyEqual('PATH_INFO', '/foo///bar\xff'))
    308324
    class EnvironTests(WSGITestsMixin, TestCase): 
    444460        """
    445461        singleValue = self.render(
    446462            'GET', '1.1', [], [''], None, [('foo', 'bar'), ('baz', 'quux')])
    447         def cbRendered((environ, startResponse)):
     463        def cbRendered(result):
     464            environ, startResponse = result
    448465            self.assertEqual(environ['HTTP_FOO'], 'bar')
    449466            self.assertEqual(environ['HTTP_BAZ'], 'quux')
    450467        singleValue.addCallback(cbRendered)
    class EnvironTests(WSGITestsMixin, TestCase): 
    541558        self.addCleanup(removeObserver, events.append)
    542559
    543560        errors = self.render('GET', '1.1', [], [''])
    544         def cbErrors((environ, startApplication)):
     561        def cbErrors(result):
     562            environ, startApplication = result
    545563            errors = environ['wsgi.errors']
    546564            errors.write('some message\n')
    547565            errors.writelines(['another\nmessage\n'])
    class InputStreamTestMixin(WSGITestsMixin): 
    600618        Calling L{_InputStream.read} with no arguments returns the entire input
    601619        stream.
    602620        """
    603         bytes = "some bytes are here"
     621        bytes = b"some bytes are here"
    604622        d = self._renderAndReturnReaderResult(lambda input: input.read(), bytes)
    605623        d.addCallback(self.assertEqual, bytes)
    606624        return d
    class InputStreamTestMixin(WSGITestsMixin): 
    612630        from the input stream, as long as it is less than or equal to the total
    613631        number of bytes available.
    614632        """
    615         bytes = "hello, world."
     633        bytes = b"hello, world."
    616634        d = self._renderAndReturnReaderResult(lambda input: input.read(3), bytes)
    617         d.addCallback(self.assertEqual, "hel")
     635        d.addCallback(self.assertEqual, b"hel")
    618636        return d
    619637
    620638
    class InputStreamTestMixin(WSGITestsMixin): 
    624642        total number of bytes in the input stream returns all bytes in the
    625643        input stream.
    626644        """
    627         bytes = "some bytes are here"
     645        bytes = b"some bytes are here"
    628646        d = self._renderAndReturnReaderResult(
    629647            lambda input: input.read(len(bytes) + 3), bytes)
    630648        d.addCallback(self.assertEqual, bytes)
    class InputStreamTestMixin(WSGITestsMixin): 
    636654        Calling L{_InputStream.read} a second time returns bytes starting from
    637655        the position after the last byte returned by the previous read.
    638656        """
    639         bytes = "some bytes, hello"
     657        bytes = b"some bytes, hello"
    640658        def read(input):
    641659            input.read(3)
    642660            return input.read()
    class InputStreamTestMixin(WSGITestsMixin): 
    650668        Calling L{_InputStream.read} with C{None} as an argument returns all
    651669        bytes in the input stream.
    652670        """
    653         bytes = "the entire stream"
     671        bytes = b"the entire stream"
    654672        d = self._renderAndReturnReaderResult(
    655673            lambda input: input.read(None), bytes)
    656674        d.addCallback(self.assertEqual, bytes)
    class InputStreamTestMixin(WSGITestsMixin): 
    662680        Calling L{_InputStream.read} with a negative integer as an argument
    663681        returns all bytes in the input stream.
    664682        """
    665         bytes = "all of the input"
     683        bytes = b"all of the input"
    666684        d = self._renderAndReturnReaderResult(
    667685            lambda input: input.read(-1), bytes)
    668686        d.addCallback(self.assertEqual, bytes)
    class InputStreamTestMixin(WSGITestsMixin): 
    674692        Calling L{_InputStream.readline} with no argument returns one line from
    675693        the input stream.
    676694        """
    677         bytes = "hello\nworld"
     695        bytes = b"hello\nworld"
    678696        d = self._renderAndReturnReaderResult(
    679697            lambda input: input.readline(), bytes)
    680         d.addCallback(self.assertEqual, "hello\n")
     698        d.addCallback(self.assertEqual, b"hello\n")
    681699        return d
    682700
    683701
    class InputStreamTestMixin(WSGITestsMixin): 
    692710        supports readline with a size argument. If you use it, be aware your
    693711        application may not be portable to other conformant WSGI servers.
    694712        """
    695         bytes = "goodbye\nworld"
     713        bytes = b"goodbye\nworld"
    696714        d = self._renderAndReturnReaderResult(
    697715            lambda input: input.readline(3), bytes)
    698         d.addCallback(self.assertEqual, "goo")
     716        d.addCallback(self.assertEqual, b"goo")
    699717        return d
    700718
    701719
    class InputStreamTestMixin(WSGITestsMixin): 
    704722        Calling L{_InputStream.readline} with an integer which is greater than
    705723        the number of bytes in the next line returns only the next line.
    706724        """
    707         bytes = "some lines\nof text"
     725        bytes = b"some lines\nof text"
    708726        d = self._renderAndReturnReaderResult(
    709727            lambda input: input.readline(20), bytes)
    710         d.addCallback(self.assertEqual, "some lines\n")
     728        d.addCallback(self.assertEqual, b"some lines\n")
    711729        return d
    712730
    713731
    class InputStreamTestMixin(WSGITestsMixin): 
    716734        Calling L{_InputStream.readline} a second time returns the line
    717735        following the line returned by the first call.
    718736        """
    719         bytes = "first line\nsecond line\nlast line"
     737        bytes = b"first line\nsecond line\nlast line"
    720738        def readline(input):
    721739            input.readline()
    722740            return input.readline()
    723741        d = self._renderAndReturnReaderResult(readline, bytes)
    724         d.addCallback(self.assertEqual, "second line\n")
     742        d.addCallback(self.assertEqual, b"second line\n")
    725743        return d
    726744
    727745
    class InputStreamTestMixin(WSGITestsMixin): 
    730748        Calling L{_InputStream.readline} with C{None} as an argument returns
    731749        one line from the input stream.
    732750        """
    733         bytes = "this is one line\nthis is another line"
     751        bytes = b"this is one line\nthis is another line"
    734752        d = self._renderAndReturnReaderResult(
    735753            lambda input: input.readline(None), bytes)
    736         d.addCallback(self.assertEqual, "this is one line\n")
     754        d.addCallback(self.assertEqual, b"this is one line\n")
    737755        return d
    738756
    739757
    class InputStreamTestMixin(WSGITestsMixin): 
    742760        Calling L{_InputStream.readline} with a negative integer as an argument
    743761        returns one line from the input stream.
    744762        """
    745         bytes = "input stream line one\nline two"
     763        bytes = b"input stream line one\nline two"
    746764        d = self._renderAndReturnReaderResult(
    747765            lambda input: input.readline(-1), bytes)
    748         d.addCallback(self.assertEqual, "input stream line one\n")
     766        d.addCallback(self.assertEqual, b"input stream line one\n")
    749767        return d
    750768
    751769
    class InputStreamTestMixin(WSGITestsMixin): 
    754772        Calling L{_InputStream.readlines} with no arguments returns a list of
    755773        all lines from the input stream.
    756774        """
    757         bytes = "alice\nbob\ncarol"
     775        bytes = b"alice\nbob\ncarol"
    758776        d = self._renderAndReturnReaderResult(
    759777            lambda input: input.readlines(), bytes)
    760         d.addCallback(self.assertEqual, ["alice\n", "bob\n", "carol"])
     778        d.addCallback(self.assertEqual, [b"alice\n", b"bob\n", b"carol"])
    761779        return d
    762780
    763781
    class InputStreamTestMixin(WSGITestsMixin): 
    767785        returns a list of lines from the input stream with the argument serving
    768786        as an approximate bound on the total number of bytes to read.
    769787        """
    770         bytes = "123\n456\n789\n0"
     788        bytes = b"123\n456\n789\n0"
    771789        d = self._renderAndReturnReaderResult(
    772790            lambda input: input.readlines(5), bytes)
    773791        def cbLines(lines):
    774792            # Make sure we got enough lines to make 5 bytes.  Anything beyond
    775793            # that is fine too.
    776             self.assertEqual(lines[:2], ["123\n", "456\n"])
     794            self.assertEqual(lines[:2], [b"123\n", b"456\n"])
    777795        d.addCallback(cbLines)
    778796        return d
    779797
    class InputStreamTestMixin(WSGITestsMixin): 
    784802        the total number of bytes in the input stream returns a list of all
    785803        lines from the input.
    786804        """
    787         bytes = "one potato\ntwo potato\nthree potato"
     805        bytes = b"one potato\ntwo potato\nthree potato"
    788806        d = self._renderAndReturnReaderResult(
    789807            lambda input: input.readlines(100), bytes)
    790808        d.addCallback(
    791809            self.assertEqual,
    792             ["one potato\n", "two potato\n", "three potato"])
     810            [b"one potato\n", b"two potato\n", b"three potato"])
    793811        return d
    794812
    795813
    class InputStreamTestMixin(WSGITestsMixin): 
    799817        returns lines starting at the byte after the last byte returned by the
    800818        C{read} call.
    801819        """
    802         bytes = "hello\nworld\nfoo"
     820        bytes = b"hello\nworld\nfoo"
    803821        def readlines(input):
    804822            input.read(7)
    805823            return input.readlines()
    806824        d = self._renderAndReturnReaderResult(readlines, bytes)
    807         d.addCallback(self.assertEqual, ["orld\n", "foo"])
     825        d.addCallback(self.assertEqual, [b"orld\n", b"foo"])
    808826        return d
    809827
    810828
    class InputStreamTestMixin(WSGITestsMixin): 
    813831        Calling L{_InputStream.readlines} with C{None} as an argument returns
    814832        all lines from the input.
    815833        """
    816         bytes = "one fish\ntwo fish\n"
     834        bytes = b"one fish\ntwo fish\n"
    817835        d = self._renderAndReturnReaderResult(
    818836            lambda input: input.readlines(None), bytes)
    819         d.addCallback(self.assertEqual, ["one fish\n", "two fish\n"])
     837        d.addCallback(self.assertEqual, [b"one fish\n", b"two fish\n"])
    820838        return d
    821839
    822840
    class InputStreamTestMixin(WSGITestsMixin): 
    825843        Calling L{_InputStream.readlines} with a negative integer as an
    826844        argument returns a list of all lines from the input.
    827845        """
    828         bytes = "red fish\nblue fish\n"
     846        bytes = b"red fish\nblue fish\n"
    829847        d = self._renderAndReturnReaderResult(
    830848            lambda input: input.readlines(-1), bytes)
    831         d.addCallback(self.assertEqual, ["red fish\n", "blue fish\n"])
     849        d.addCallback(self.assertEqual, [b"red fish\n", b"blue fish\n"])
    832850        return d
    833851
    834852
    class InputStreamTestMixin(WSGITestsMixin): 
    836854        """
    837855        Iterating over L{_InputStream} produces lines from the input stream.
    838856        """
    839         bytes = "green eggs\nand ham\n"
     857        bytes = b"green eggs\nand ham\n"
    840858        d = self._renderAndReturnReaderResult(lambda input: list(input), bytes)
    841         d.addCallback(self.assertEqual, ["green eggs\n", "and ham\n"])
     859        d.addCallback(self.assertEqual, [b"green eggs\n", b"and ham\n"])
    842860        return d
    843861
    844862
    class InputStreamTestMixin(WSGITestsMixin): 
    848866        produces lines from the input stream starting from the first byte after
    849867        the last byte returned by the C{read} call.
    850868        """
    851         bytes = "green eggs\nand ham\n"
     869        bytes = b"green eggs\nand ham\n"
    852870        def iterate(input):
    853871            input.read(3)
    854872            return list(input)
    855873        d = self._renderAndReturnReaderResult(iterate, bytes)
    856         d.addCallback(self.assertEqual, ["en eggs\n", "and ham\n"])
     874        d.addCallback(self.assertEqual, [b"en eggs\n", b"and ham\n"])
    857875        return d
    858876
    859877
    860878
    861879class InputStreamStringIOTests(InputStreamTestMixin, TestCase):
    862880    """
    863     Tests for L{_InputStream} when it is wrapped around a L{StringIO.StringIO}.
     881    Tests for L{_InputStream} when it is wrapped around a
     882    L{StringIO.StringIO}.
     883
     884    This is only available in Python 2.
    864885    """
    865886    def getFileType(self):
    866         return StringIO.StringIO
     887        try:
     888            from StringIO import StringIO
     889        except ImportError:
     890            raise SkipTest("StringIO.StringIO is not available.")
     891        else:
     892            return StringIO
    867893
    868894
    869895
    class InputStreamCStringIOTests(InputStreamTestMixin, TestCase): 
    871897    """
    872898    Tests for L{_InputStream} when it is wrapped around a
    873899    L{cStringIO.StringIO}.
     900
     901    This is only available in Python 2.
    874902    """
    875903    def getFileType(self):
    876         return cStringIO.StringIO
     904        try:
     905            from cStringIO import StringIO
     906        except ImportError:
     907            raise SkipTest("cStringIO.StringIO is not available.")
     908        else:
     909            return StringIO
     910
     911
     912
     913class InputStreamBytesIOTests(InputStreamTestMixin, TestCase):
     914    """
     915    Tests for L{_InputStream} when it is wrapped around an L{io.BytesIO}.
     916    """
     917    def getFileType(self):
     918        from io import BytesIO
     919        return BytesIO
    877920
    878921
    879922
    class StartResponseTests(WSGITestsMixin, TestCase): 
    908951        def cbRendered(ignored):
    909952            self.assertTrue(
    910953                channel.transport.written.getvalue().startswith(
    911                     'HTTP/1.1 107 Strange message'))
     954                    b'HTTP/1.1 107 Strange message'))
    912955        d.addCallback(cbRendered)
    913956
    914957        self.lowLevelRender(
    class StartResponseTests(WSGITestsMixin, TestCase): 
    939982        d, requestFactory = self.requestFactoryFactory()
    940983        def cbRendered(ignored):
    941984            response = channel.transport.written.getvalue()
    942             headers, rest = response.split('\r\n\r\n', 1)
    943             headerLines = headers.split('\r\n')[1:]
     985            headers, rest = response.split(b'\r\n\r\n', 1)
     986            headerLines = headers.split(b'\r\n')[1:]
    944987            headerLines.sort()
    945988            allExpectedHeaders = expectedHeaders + [
    946                 'Date: Tuesday',
    947                 'Server: ' + version,
    948                 'Transfer-Encoding: chunked']
     989                b'Date: Tuesday',
     990                b'Server: ' + version,
     991                b'Transfer-Encoding: chunked']
    949992            allExpectedHeaders.sort()
    950993            self.assertEqual(headerLines, allExpectedHeaders)
    951994
    class StartResponseTests(WSGITestsMixin, TestCase): 
    9651008        """
    9661009        return self._headersTest(
    9671010            [('foo', 'bar'), ('baz', 'quux')],
    968             ['Baz: quux', 'Foo: bar'])
     1011            [b'Baz: quux', b'Foo: bar'])
    9691012
    9701013
    9711014    def test_applicationProvidedContentType(self):
    class StartResponseTests(WSGITestsMixin, TestCase): 
    9761019        """
    9771020        return self._headersTest(
    9781021            [('content-type', 'monkeys are great')],
    979             ['Content-Type: monkeys are great'])
     1022            [b'Content-Type: monkeys are great'])
    9801023
    9811024
    9821025    def test_applicationProvidedServerAndDate(self):
    class StartResponseTests(WSGITestsMixin, TestCase): 
    10061049        def applicationFactory():
    10071050            def application(environ, startResponse):
    10081051                startResponse('200 OK', [('foo', 'bar'), ('baz', 'quux')])
    1009                 yield ''
     1052                yield b''
    10101053                record()
    10111054            return application
    10121055
    10131056        d, requestFactory = self.requestFactoryFactory()
    10141057        def cbRendered(ignored):
    1015             self.assertEqual(intermediateValues, [''])
     1058            self.assertEqual(intermediateValues, [b''])
    10161059        d.addCallback(cbRendered)
    10171060
    10181061        self.lowLevelRender(
    class StartResponseTests(WSGITestsMixin, TestCase): 
    10381081        def applicationFactory():
    10391082            def application(environ, startResponse):
    10401083                startResponse('200 OK', [('foo', 'bar')])
    1041                 yield ''
     1084                yield b''
    10421085                record()
    1043                 yield 'foo'
     1086                yield b'foo'
    10441087                record()
    10451088            return application
    10461089
    class StartResponseTests(WSGITestsMixin, TestCase): 
    10711114        def applicationFactory():
    10721115            def application(environ, startResponse):
    10731116                startResponse('200 OK', [('content-length', '6')])
    1074                 yield 'foo'
     1117                yield b'foo'
    10751118                record()
    1076                 yield 'bar'
     1119                yield b'bar'
    10771120                record()
    10781121            return application
    10791122
    class StartResponseTests(WSGITestsMixin, TestCase): 
    10811124        def cbRendered(ignored):
    10821125            self.assertEqual(
    10831126                self.getContentFromResponse(intermediateValues[0]),
    1084                 'foo')
     1127                b'foo')
    10851128            self.assertEqual(
    10861129                self.getContentFromResponse(intermediateValues[1]),
    1087                 'foobar')
     1130                b'foobar')
    10881131        d.addCallback(cbRendered)
    10891132
    10901133        self.lowLevelRender(
    class StartResponseTests(WSGITestsMixin, TestCase): 
    11131156        def cbRendered(ignored):
    11141157            self.assertTrue(
    11151158                channel.transport.written.getvalue().startswith(
    1116                     'HTTP/1.1 200 Bar\r\n'))
     1159                    b'HTTP/1.1 200 Bar\r\n'))
    11171160        d.addCallback(cbRendered)
    11181161
    11191162        self.lowLevelRender(
    class StartResponseTests(WSGITestsMixin, TestCase): 
    11411184        def cbRendered(ignored):
    11421185            self.assertTrue(
    11431186                channel.transport.written.getvalue().startswith(
    1144                     'HTTP/1.1 100 Foo\r\n'))
     1187                    b'HTTP/1.1 100 Foo\r\n'))
    11451188        d.addCallback(cbRendered)
    11461189
    11471190        self.lowLevelRender(
    class StartResponseTests(WSGITestsMixin, TestCase): 
    11731216        def applicationFactory():
    11741217            def application(environ, startResponse):
    11751218                startResponse('200 OK', [])
    1176                 yield 'foo'
     1219                yield b'foo'
    11771220                try:
    11781221                    startResponse('500 ERR', [], excInfo)
    11791222                except:
    class StartResponseTests(WSGITestsMixin, TestCase): 
    11841227        def cbRendered(ignored):
    11851228            self.assertTrue(
    11861229                channel.transport.written.getvalue().startswith(
    1187                     'HTTP/1.1 200 OK\r\n'))
     1230                    b'HTTP/1.1 200 OK\r\n'))
    11881231            self.assertEqual(reraised[0][0], excInfo[0])
    11891232            self.assertEqual(reraised[0][1], excInfo[1])
    1190             self.assertEqual(reraised[0][2].tb_next, excInfo[2])
     1233
     1234            # Show that the tracebacks end with the same stack frames.
     1235            tb1 = reraised[0][2].tb_next
     1236            tb2 = excInfo[2]
     1237            self.assertEqual(
     1238                # On Python 2 (str is bytes) we need to move back only one
     1239                # stack frame to skip. On Python 3 we need to move two frames.
     1240                traceback.extract_tb(tb1)[1 if str is bytes else 2],
     1241                traceback.extract_tb(tb2)[0]
     1242            )
    11911243
    11921244        d.addCallback(cbRendered)
    11931245
    class StartResponseTests(WSGITestsMixin, TestCase): 
    12121264        def applicationFactory():
    12131265            def application(environ, startResponse):
    12141266                write = startResponse('100 Foo', [('content-length', '6')])
    1215                 write('foo')
     1267                write(b'foo')
    12161268                record()
    1217                 write('bar')
     1269                write(b'bar')
    12181270                record()
    12191271                return iter(())
    12201272            return application
    class StartResponseTests(WSGITestsMixin, TestCase): 
    12231275        def cbRendered(ignored):
    12241276            self.assertEqual(
    12251277                self.getContentFromResponse(intermediateValues[0]),
    1226                 'foo')
     1278                b'foo')
    12271279            self.assertEqual(
    12281280                self.getContentFromResponse(intermediateValues[1]),
    1229                 'foobar')
     1281                b'foobar')
    12301282        d.addCallback(cbRendered)
    12311283
    12321284        self.lowLevelRender(
    class ApplicationTests(WSGITestsMixin, TestCase): 
    12631315            def __iter__(self):
    12641316                for i in range(3):
    12651317                    if self.open:
    1266                         yield str(i)
     1318                        yield intToBytes(i)
    12671319
    12681320            def close(self):
    12691321                self.open = False
    class ApplicationTests(WSGITestsMixin, TestCase): 
    12801332            self.assertEqual(
    12811333                self.getContentFromResponse(
    12821334                    channel.transport.written.getvalue()),
    1283                 '012')
     1335                b'012')
    12841336            self.assertFalse(result.open)
    12851337        d.addCallback(cbRendered)
    12861338
    class ApplicationTests(WSGITestsMixin, TestCase): 
    13031355            def application(environ, startResponse):
    13041356                def result():
    13051357                    for i in range(3):
    1306                         invoked.append(get_ident())
    1307                         yield str(i)
    1308                 invoked.append(get_ident())
     1358                        invoked.append(getThreadID())
     1359                        yield intToBytes(i)
     1360                invoked.append(getThreadID())
    13091361                startResponse('200 OK', [('content-length', '3')])
    13101362                return result()
    13111363            return application
    13121364
    13131365        d, requestFactory = self.requestFactoryFactory()
    13141366        def cbRendered(ignored):
    1315             self.assertNotIn(get_ident(), invoked)
     1367            self.assertNotIn(getThreadID(), invoked)
    13161368            self.assertEqual(len(set(invoked)), 1)
    13171369        d.addCallback(cbRendered)
    13181370
    class ApplicationTests(WSGITestsMixin, TestCase): 
    13331385
    13341386        class ThreadVerifier(Request):
    13351387            def write(self, bytes):
    1336                 invoked.append(get_ident())
     1388                invoked.append(getThreadID())
    13371389                return Request.write(self, bytes)
    13381390
    13391391        def applicationFactory():
    13401392            def application(environ, startResponse):
    13411393                write = startResponse('200 OK', [])
    1342                 write('foo')
     1394                write(b'foo')
    13431395                return iter(())
    13441396            return application
    13451397
    13461398        d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
    13471399        def cbRendered(ignored):
    1348             self.assertEqual(set(invoked), set([get_ident()]))
     1400            self.assertEqual(set(invoked), set([getThreadID()]))
    13491401        d.addCallback(cbRendered)
    13501402
    13511403        self.lowLevelRender(
    class ApplicationTests(WSGITestsMixin, TestCase): 
    13651417
    13661418        class ThreadVerifier(Request):
    13671419            def write(self, bytes):
    1368                 invoked.append(get_ident())
     1420                invoked.append(getThreadID())
    13691421                return Request.write(self, bytes)
    13701422
    13711423        def applicationFactory():
    13721424            def application(environ, startResponse):
    13731425                startResponse('200 OK', [])
    1374                 yield 'foo'
     1426                yield b'foo'
    13751427            return application
    13761428
    13771429        d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
    13781430        def cbRendered(ignored):
    1379             self.assertEqual(set(invoked), set([get_ident()]))
     1431            self.assertEqual(set(invoked), set([getThreadID()]))
    13801432        d.addCallback(cbRendered)
    13811433
    13821434        self.lowLevelRender(
    class ApplicationTests(WSGITestsMixin, TestCase): 
    13951447
    13961448        class ThreadVerifier(Request):
    13971449            def setResponseCode(self, code, message):
    1398                 invoked.append(get_ident())
     1450                invoked.append(getThreadID())
    13991451                return Request.setResponseCode(self, code, message)
    14001452
    14011453        def applicationFactory():
    class ApplicationTests(WSGITestsMixin, TestCase): 
    14061458
    14071459        d, requestFactory = self.requestFactoryFactory(ThreadVerifier)
    14081460        def cbRendered(ignored):
    1409             self.assertEqual(set(invoked), set([get_ident()]))
     1461            self.assertEqual(set(invoked), set([getThreadID()]))
    14101462        d.addCallback(cbRendered)
    14111463
    14121464        self.lowLevelRender(
    class ApplicationTests(WSGITestsMixin, TestCase): 
    14311483
    14321484        self.badIter = False
    14331485        def appIter():
    1434             yield "foo"
     1486            yield b"foo"
    14351487            self.badIter = True
    14361488            raise Exception("Should not have gotten here")
    14371489
    class ApplicationTests(WSGITestsMixin, TestCase): 
    14661518
    14671519            self.assertTrue(
    14681520                channel.transport.written.getvalue().startswith(
    1469                     'HTTP/1.1 500 Internal Server Error'))
     1521                    b'HTTP/1.1 500 Internal Server Error'))
    14701522        d.addCallback(cbRendered)
    14711523
    14721524        self.lowLevelRender(
    class ApplicationTests(WSGITestsMixin, TestCase): 
    15171569            self.assertEqual(len(errors), 1)
    15181570
    15191571            response = channel.transport.written.getvalue()
    1520             self.assertTrue(response.startswith('HTTP/1.1 200 OK'))
     1572            self.assertTrue(response.startswith(b'HTTP/1.1 200 OK'))
    15211573            # Chunked transfer-encoding makes this a little messy.
    15221574            self.assertIn(responseContent, response)
    15231575        d.addErrback(ebRendered)
    class ApplicationTests(WSGITestsMixin, TestCase): 
    15411593        logged.
    15421594        """
    15431595        responseContent = (
    1544             'Some bytes, triggering the server to start sending the response')
     1596            b'Some bytes, triggering the server to start sending the response')
    15451597
    15461598        def application(environ, startResponse):
    15471599            startResponse('200 OK', [])
    class ApplicationTests(WSGITestsMixin, TestCase): 
    15561608        raises an exception when called then the connection is still closed and
    15571609        the exception is logged.
    15581610        """
    1559         responseContent = 'foo'
     1611        responseContent = b'foo'
    15601612
    15611613        class Application(object):
    15621614            def __init__(self, environ, startResponse):
  • new file twisted/web/topfiles/7993.feature

    diff --git a/twisted/web/topfiles/7993.feature b/twisted/web/topfiles/7993.feature
    new file mode 100644
    index 0000000..48c9b06
    - +  
     1twisted.web.wsgi has been ported to Python 3.
  • twisted/web/wsgi.py

    diff --git a/twisted/web/wsgi.py b/twisted/web/wsgi.py
    index 0918c4d..5d8fac2 100644
    a b  
    33
    44"""
    55An implementation of
    6 U{Web Resource Gateway Interface<http://www.python.org/dev/peps/pep-0333/>}.
     6U{Python Web Server Gateway Interface v1.0.1<http://www.python.org/dev/peps/pep-3333/>}.
    77"""
    88
    99__metaclass__ = type
    1010
    1111from sys import exc_info
    1212
    13 from zope.interface import implements
     13from zope.interface import implementer
    1414
     15from twisted.python.compat import reraise
    1516from twisted.python.log import msg, err
    1617from twisted.python.failure import Failure
    1718from twisted.web.resource import IResource
    from twisted.web.server import NOT_DONE_YET 
    1920from twisted.web.http import INTERNAL_SERVER_ERROR
    2021
    2122
     23
     24# PEP-3333 -- which has superseded PEP-333 -- states that, in both Python 2
     25# and Python 3, text strings MUST be represented using the platform's native
     26# string type, limited to characters defined in ISO-8859-1. Byte strings are
     27# used only for values read from wsgi.input, passed to write() or yielded by
     28# the application.
     29#
     30# Put another way:
     31#
     32# - In Python 2, all text strings and binary data are of type str/bytes and
     33#   NEVER of type unicode. Whether the strings contain binary data or
     34#   ISO-8859-1 text depends on context.
     35#
     36# - In Python 3, all text strings are of type str, and all binary data are of
     37#   type bytes. Text MUST always be limited to that which can be encoded as
     38#   ISO-8859-1, U+0000 to U+00FF inclusive.
     39#
     40# The following pair of functions -- _wsgiString() and _wsgiStringToBytes() --
     41# are used to make Twisted's WSGI support compliant with the standard.
     42if str is bytes:
     43    def _wsgiString(string):  # Python 2.
     44        """
     45        Convert C{string} to an ISO-8859-1 byte string, if it is not already.
     46
     47        @type string: C{str}/C{bytes} or C{unicode}
     48        @rtype: C{str}/C{bytes}
     49
     50        @raise UnicodeEncodeError: If C{string} contains non-ISO-8859-1 chars.
     51        """
     52        if isinstance(string, str):
     53            return string
     54        else:
     55            return string.encode('iso-8859-1')
     56
     57    def _wsgiStringToBytes(string):  # Python 2.
     58        """
     59        Return C{string} if it is a byte string.
     60
     61        @type string: C{str}/C{bytes}
     62        @rtype: C{str}/C{bytes}
     63
     64        @raise TypeError: If C{string} is not a byte string.
     65        """
     66        if isinstance(string, str):
     67            return string
     68        else:
     69            raise TypeError(
     70                "string must be str/bytes, not %r (%s)"
     71                % (string, type(string).__name__))
     72
     73else:
     74    def _wsgiString(string):  # Python 3.
     75        """
     76        Convert C{string} to a WSGI "bytes-as-unicode" string.
     77
     78        If it's a byte string, decode as ISO-8859-1. If it's a Unicode string,
     79        round-trip it to bytes and back using ISO-8859-1 as the encoding.
     80
     81        @type string: C{str} or C{bytes}
     82        @rtype: str
     83
     84        @raise UnicodeEncodeError: If C{string} contains non-ISO-8859-1 chars.
     85        """
     86        if isinstance(string, str):
     87            return string.encode("iso-8859-1").decode('iso-8859-1')
     88        else:
     89            return string.decode("iso-8859-1")
     90
     91    def _wsgiStringToBytes(string):  # Python 3.
     92        """
     93        Convert C{string} from a WSGI "bytes-as-unicode" string to an
     94        ISO-8859-1 byte string.
     95
     96        @type string: C{str}
     97        @rtype: bytes
     98
     99        @raise UnicodeEncodeError: If C{string} contains non-ISO-8859-1 chars.
     100        @raise TypeError: If C{string} is not a byte string.
     101        """
     102        if isinstance(string, str):
     103            return string.encode("iso-8859-1")
     104        else:
     105            raise TypeError(
     106                "string must be str, not %r (%s)"
     107                % (string, type(string).__name__))
     108
     109
     110
    22111class _ErrorStream:
    23112    """
    24113    File-like object instances of which are used as the value for the
    class _ErrorStream: 
    30119    to expose more information in the events it logs, such as the application
    31120    object which generated the message.
    32121    """
    33     def write(self, bytes):
     122
     123    def write(self, data):
    34124        """
    35125        Generate an event for the logging system with the given bytes as the
    36126        message.
    37127
    38128        This is called in a WSGI application thread, not the I/O thread.
     129
     130        @type data: str
     131
     132        @raise TypeError: If C{data} is not a native string.
    39133        """
    40         msg(bytes, system='wsgi', isError=True)
     134        if not isinstance(data, str):
     135            raise TypeError(
     136                "write() argument must be str, not %r (%s)"
     137                % (data, type(data).__name__))
     138        msg(data, system='wsgi', isError=True)
    41139
    42140
    43141    def writelines(self, iovec):
    class _ErrorStream: 
    49147
    50148        @param iovec: A C{list} of C{'\\n'}-terminated C{str} which will be
    51149            logged.
     150
     151        @raise TypeError: If C{iovec} contains any non-native strings.
    52152        """
    53153        self.write(''.join(iovec))
    54154
    class _WSGIResponse: 
    174274        self.request.notifyFinish().addBoth(self._finished)
    175275
    176276        if request.prepath:
    177             scriptName = '/' + '/'.join(request.prepath)
     277            scriptName = b'/' + b'/'.join(request.prepath)
    178278        else:
    179             scriptName = ''
     279            scriptName = b''
    180280
    181281        if request.postpath:
    182             pathInfo = '/' + '/'.join(request.postpath)
     282            pathInfo = b'/' + b'/'.join(request.postpath)
    183283        else:
    184             pathInfo = ''
     284            pathInfo = b''
    185285
    186         parts = request.uri.split('?', 1)
     286        parts = request.uri.split(b'?', 1)
    187287        if len(parts) == 1:
    188             queryString = ''
     288            queryString = b''
    189289        else:
    190290            queryString = parts[1]
    191291
     292        # All keys and values need to be native strings, i.e. of type str in
     293        # *both* Python 2 and Python 3, so says PEP-3333.
    192294        self.environ = {
    193             'REQUEST_METHOD': request.method,
    194             'REMOTE_ADDR': request.getClientIP(),
    195             'SCRIPT_NAME': scriptName,
    196             'PATH_INFO': pathInfo,
    197             'QUERY_STRING': queryString,
    198             'CONTENT_TYPE': request.getHeader('content-type') or '',
    199             'CONTENT_LENGTH': request.getHeader('content-length') or '',
    200             'SERVER_NAME': request.getRequestHostname(),
    201             'SERVER_PORT': str(request.getHost().port),
    202             'SERVER_PROTOCOL': request.clientproto}
    203 
     295            'REQUEST_METHOD': _wsgiString(request.method),
     296            'REMOTE_ADDR': _wsgiString(request.getClientIP()),
     297            'SCRIPT_NAME': _wsgiString(scriptName),
     298            'PATH_INFO': _wsgiString(pathInfo),
     299            'QUERY_STRING': _wsgiString(queryString),
     300            'CONTENT_TYPE': _wsgiString(
     301                request.getHeader(b'content-type') or ''),
     302            'CONTENT_LENGTH': _wsgiString(
     303                request.getHeader(b'content-length') or ''),
     304            'SERVER_NAME': _wsgiString(request.getRequestHostname()),
     305            'SERVER_PORT': _wsgiString(str(request.getHost().port)),
     306            'SERVER_PROTOCOL': _wsgiString(request.clientproto)}
    204307
    205308        # The application object is entirely in control of response headers;
    206309        # disable the default Content-Type value normally provided by
    class _WSGIResponse: 
    208311        self.request.defaultContentType = None
    209312
    210313        for name, values in request.requestHeaders.getAllRawHeaders():
    211             name = 'HTTP_' + name.upper().replace('-', '_')
     314            name = 'HTTP_' + _wsgiString(name).upper().replace('-', '_')
    212315            # It might be preferable for http.HTTPChannel to clear out
    213316            # newlines.
    214             self.environ[name] = ','.join([
    215                     v.replace('\n', ' ') for v in values])
     317            self.environ[name] = _wsgiString(
     318                b','.join(values)).replace('\n', ' ')
    216319
    217320        self.environ.update({
    218321                'wsgi.version': (1, 0),
    class _WSGIResponse: 
    254357        This will be called in a non-I/O thread.
    255358        """
    256359        if self.started and excInfo is not None:
    257             raise excInfo[0], excInfo[1], excInfo[2]
     360            reraise(excInfo[1], excInfo[2])
     361
     362        # PEP-3333 mandates that status should be a native string.
     363        if not isinstance(status, str):
     364            raise TypeError(
     365                "status must be str, not %r (%s)"
     366                % (status, type(status).__name__))
     367
     368        # PEP-3333 mandates a plain list.
     369        if not isinstance(headers, list):
     370            raise TypeError(
     371                "headers must be a list, not %r (%s)"
     372                % (status, type(status).__name__))
     373
     374        # PEP-3333 mandates that each header should be a (str, str) tuple.
     375        for header in headers:
     376            is_okay = (
     377                isinstance(header, tuple) and len(header) == 2 and
     378                isinstance(header[0], str) and isinstance(header[1], str)
     379            )
     380            if not is_okay:
     381                raise TypeError(
     382                    "header must be (str, str) tuple, not %r"
     383                    % (status, type(status).__name__))
     384
    258385        self.status = status
    259386        self.headers = headers
    260387        return self.write
    261388
    262389
    263     def write(self, bytes):
     390    def write(self, data):
    264391        """
    265392        The WSGI I{write} callable returned by the I{start_response} callable.
    266393        The given bytes will be written to the response body, possibly flushing
    267394        the status and headers first.
    268395
    269396        This will be called in a non-I/O thread.
     397
     398        @raise TypeError: If C{data} is not a byte string.
    270399        """
     400        # Check that `data` is bytes now because we will not get any feedback
     401        # from callFromThread() later on.
     402        if not isinstance(data, bytes):
     403            raise TypeError(
     404                "write() argument must be bytes, not %r (%s)"
     405                % (data, type(data).__name__))
     406
    271407        def wsgiWrite(started):
    272408            if not started:
    273409                self._sendResponseHeaders()
    274             self.request.write(bytes)
     410            self.request.write(data)
     411
     412        # PEP-3333 states:
     413        #
     414        #   The server or gateway must transmit the yielded bytestrings to the
     415        #   client in an unbuffered fashion, completing the transmission of
     416        #   each bytestring before requesting another one.
     417        #
     418        # This write() method is used for the imperative and (indirectly) for
     419        # the more familiar iterable-of-bytestrings WSGI mechanism, but offers
     420        # no back-pressure, and so violates this part of PEP-3333.
     421        #
     422        # PEP-3333 also says that a server may:
     423        #
     424        #   Use a different thread to ensure that the block continues to be
     425        #   transmitted while the application produces the next block.
     426        #
     427        # Which suggests that this is actually compliant with PEP-3333,
     428        # because writes are done in the reactor thread.
     429        #
     430        # However, providing some back-pressure may nevertheless be a Good
     431        # Thing at some point in the future.
    275432        self.reactor.callFromThread(wsgiWrite, self.started)
    276433        self.started = True
    277434
    class _WSGIResponse: 
    287444        """
    288445        code, message = self.status.split(None, 1)
    289446        code = int(code)
    290         self.request.setResponseCode(code, message)
     447        self.request.setResponseCode(code, _wsgiStringToBytes(message))
    291448
    292449        for name, value in self.headers:
    293450            # Don't allow the application to control these required headers.
    294451            if name.lower() not in ('server', 'date'):
    295                 self.request.responseHeaders.addRawHeader(name, value)
     452                self.request.responseHeaders.addRawHeader(
     453                    _wsgiStringToBytes(name), _wsgiStringToBytes(value))
    296454
    297455
    298456    def start(self):
    class _WSGIResponse: 
    341499
    342500
    343501
     502@implementer(IResource)
    344503class WSGIResource:
    345504    """
    346505    An L{IResource} implementation which delegates responsibility for all
    class WSGIResource: 
    354513
    355514    @ivar _application: The WSGI application object.
    356515    """
    357     implements(IResource)
    358516
    359517    # Further resource segments are left up to the WSGI application object to
    360518    # handle.