Tres Seaver
2011-02-01 b01f4465c387fd76247ecfe97eb0601c0c9c22ac
Ensure that the middleware calls `close()` on the returned app_iterable.

Required by PEP 333.

Closes http://bugs.repoze.org/issue174

3 files modified
75 ■■■■■ changed files
CHANGES.txt 4 ●●●● patch | view | raw | blame | history
repoze/who/middleware.py 8 ●●●●● patch | view | raw | blame | history
repoze/who/tests/test_middleware.py 63 ●●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -4,6 +4,10 @@
After 2.0a3 (unreleased)
------------------------
- Ensure that the middleware calls ``close()`` (if it exists) on the
  iterable returned from thw wrapped application, as required by PEP 333.
  http://bugs.repoze.org/issue174
- Make ``make_api_factory_with_config`` tolerant of invalid filenames /
  content for the config file:  in such cases, the API factory will have
  *no* configured plugins or policies:  it will only be useful for retrieving
repoze/who/middleware.py
@@ -102,6 +102,10 @@
                logger and logger.info('executing challenge app')
                if app_iter:
                    list(app_iter) # unwind the original app iterator
                # PEP 333 requires that we call the original iterator's
                # 'close' method, if it exists, before releasing it.
                close = getattr(app_iter, 'close', lambda: None)
                close()
                # replace the downstream app with the challenge app
                app_iter = challenge_app(environ, start_response)
            else:
@@ -122,6 +126,9 @@
    caches it to trigger any immediate side effects (in a WSGI world, this
    ensures start_response is called).
    """
    # PEP 333 requires that we call the original iterator's
    # 'close' method, if it exists, before releasing it.
    close = getattr(result, 'close', lambda: None)
    # Neat trick to pull the first iteration only. We need to do this outside
    # of the generator function to ensure it is called.
    for iter in result:
@@ -135,6 +142,7 @@
        for iter in result:
            # We'll let result's StopIteration bubble up directly.
            yield iter
        close()
    return wrapper()
class StartResponseWrapper(object):
repoze/who/tests/test_middleware.py
@@ -422,6 +422,30 @@
        # metadata
        self.assertEqual(environ['repoze.who.identity']['foo'], 'bar')
    def test_call_w_challenge_closes_iterable(self):
        from paste.httpexceptions import HTTPUnauthorized
        environ = self._makeEnviron()
        headers = [('a', '1')]
        app = DummyIterableWithCloseApp('401 Unauthorized', headers)
        challenge_app = HTTPUnauthorized()
        challenge = DummyChallenger(challenge_app)
        challengers = [ ('challenge', challenge) ]
        credentials = {'login':'chris', 'password':'password'}
        identifier = DummyIdentifier(credentials)
        identifiers = [ ('identifier', identifier) ]
        authenticator = DummyAuthenticator()
        authenticators = [ ('authenticator', authenticator) ]
        mdprovider = DummyMDProvider({'foo':'bar'})
        mdproviders = [ ('mdprovider', mdprovider) ]
        mw = self._makeOne(app=app, challengers=challengers,
                           identifiers=identifiers,
                           authenticators=authenticators,
                           mdproviders=mdproviders)
        start_response = DummyStartResponse()
        result = mw(environ, start_response)
        self.failUnless(result[0].startswith('401 Unauthorized\r\n'))
        self.failUnless(app._iterable._closed)
    # XXX need more call tests:
    #  - auth_id sorting
@@ -472,20 +496,29 @@
class WrapGeneratorTests(unittest.TestCase):
    def _getFUT(self):
    def _callFUT(self, iterable):
        from repoze.who.middleware import wrap_generator
        return wrap_generator
        return wrap_generator(iterable)
    def test_it(self):
    def test_w_generator(self):
        L = []
        def gen(L=L):
            L.append('yo!')
            yield 'a'
            yield 'b'
        wrap_generator = self._getFUT()
        newgen = wrap_generator(gen())
        newgen = self._callFUT(gen())
        self.assertEqual(L, ['yo!'])
        self.assertEqual(list(newgen), ['a', 'b'])
    def test_w_iterator_having_close(self):
        def gen():
            yield 'a'
            yield 'b'
        iterable = DummyIterableWithClose(gen())
        newgen = self._callFUT(iterable)
        self.failIf(iterable._closed)
        self.assertEqual(list(newgen), ['a', 'b'])
        self.failUnless(iterable._closed)
class TestMakeTestMiddleware(unittest.TestCase):
@@ -555,6 +588,26 @@
            yield 'body'
        return gen()
class DummyIterableWithClose:
    _closed = False
    def __init__(self, iterable):
        self._iterable = iterable
    def __iter__(self):
        return iter(self._iterable)
    def close(self):
        self._closed = True
class DummyIterableWithCloseApp:
    def __init__(self, status, headers):
        self.status = status
        self.headers = headers
        self._iterable = DummyIterableWithClose(['body'])
    def __call__(self, environ, start_response):
        self.environ = environ
        start_response(self.status, self.headers)
        return self._iterable
class DummyIdentityResetApp:
    def __init__(self, status, headers, new_identity):
        self.status = status