[Repoze-checkins] r721 - in repoze.decsec/trunk/repoze/decsec: . etc
Chris McDonough
chrism at agendaless.com
Fri Feb 22 06:24:22 UTC 2008
Author: Chris McDonough <chrism at agendaless.com>
Date: Fri Feb 22 01:24:21 2008
New Revision: 721
Log:
Tests.
Modified:
repoze.decsec/trunk/repoze/decsec/etc/sample-config.ini
repoze.decsec/trunk/repoze/decsec/interfaces.py
repoze.decsec/trunk/repoze/decsec/middleware.py
repoze.decsec/trunk/repoze/decsec/sample.py
repoze.decsec/trunk/repoze/decsec/tests.py
Modified: repoze.decsec/trunk/repoze/decsec/etc/sample-config.ini
==============================================================================
--- repoze.decsec/trunk/repoze/decsec/etc/sample-config.ini (original)
+++ repoze.decsec/trunk/repoze/decsec/etc/sample-config.ini Fri Feb 22 01:24:21 2008
@@ -1,10 +1,20 @@
[decsec]
-# knobs
+# the key to look in the environment for to determine the REMOTE_USER
remote_user_key=REMOTE_USER
-deny_exception=Paste.httpexceptions:Unauthorized
+
+# the exception raised when a deny happens
+deny_exception=Paste.httpexceptions:HTTPForbidden
+
+# log_filename can be STDOUT, STDERR, or NONE, or a filename
log_filename=%(here)s/decsec.log
+
+# allows log at log_level "debug", denies log at log_level "info",
log_level=info
-allow_on_nomatch=false
+
+# if no acl matches the principals implied by the request, the default
+# behavior is to deny, set nomatch_allow to true to invert
+nomatch_allow=false
+
# hooks
initialize_hook=decsec.sample:initialize
before_check_hook=decsec.sample:before_check
Modified: repoze.decsec/trunk/repoze/decsec/interfaces.py
==============================================================================
--- repoze.decsec/trunk/repoze/decsec/interfaces.py (original)
+++ repoze.decsec/trunk/repoze/decsec/interfaces.py Fri Feb 22 01:24:21 2008
@@ -1,2 +1,4 @@
Everyone = 'Everyone'
Authenticated = 'Authenticated'
+Allow = 'Allow'
+Deny = 'Deny'
Modified: repoze.decsec/trunk/repoze/decsec/middleware.py
==============================================================================
--- repoze.decsec/trunk/repoze/decsec/middleware.py (original)
+++ repoze.decsec/trunk/repoze/decsec/middleware.py Fri Feb 22 01:24:21 2008
@@ -1,8 +1,15 @@
-from paste import httpexceptions
+import sys
import logging
+from paste import httpexceptions
+
from repoze.decsec.interfaces import Everyone
from repoze.decsec.interfaces import Authenticated
+from repoze.decsec.interfaces import Allow
+from repoze.decsec.interfaces import Deny
+
+_LOGTMPL = ('%(action)s:: path: %(path)s, acl: %(acl)s, '
+ 'principals: %(principals)s, permission: %(permission)s')
class DecsecMiddleware(object):
def __init__(self,
@@ -14,19 +21,26 @@
initialize = None,
before_check = None,
after_check = None,
- log_filename = 'decsec.log',
+ log_stream = None,
log_level=logging.INFO,
- allow_on_nomatch = False,
+ nomatch_allow = False,
remote_user_key = 'REMOTE_USER',
deny_exception = httpexceptions.HTTPUnauthorized,
):
self.app = app
self.remote_user_key = remote_user_key
self.deny_exception = deny_exception
- self.log_filename = log_filename
- self.log_level = log_level
- self.allow_on_nomatch = allow_on_nomatch
- self.request_prinicpals = request_principals
+ self.logger = None
+ if log_stream:
+ handler = logging.StreamHandler(log_stream)
+ fmt = '%(asctime)s:%(msecs)d %(message)s'
+ formatter = logging.Formatter(fmt)
+ handler.setFormatter(formatter)
+ self.logger = logging.Logger('repoze.decsec')
+ self.logger.addHandler(handler)
+ self.logger.setLevel(log_level)
+ self.nomatch_allow = nomatch_allow
+ self.request_principals = request_principals
self.request_acl = request_acl
self.request_permission = request_permission
self.before_check = before_check
@@ -35,40 +49,55 @@
initialize(global_conf, self)
def __call__(self, environ, start_response):
- allowed, reason = self.allowed(environ)
- if not allowed:
- raise self.deny_exception(reason)
- self.app(environ, start_response)
-
- def allowed(self, environ):
+ result = self.check(environ)
+ allowed = result['allowed']
+ if self.logger is not None:
+ result['path'] = environ.get('PATH_INFO')
+ msg = _LOGTMPL % result
+ if allowed:
+ self.logger.debug(msg)
+ else:
+ self.logger.info(msg)
+ if allowed:
+ return self.app(environ, start_response)
+ else:
+ raise self.deny_exception(result)
+
+ def check(self, environ):
if self.before_check:
self.before_check(environ)
principals = [Everyone]
- request_principals = self.request_principals(environ)
+ request_principals = flatten(self.request_principals(environ))
if request_principals:
principals.append(Authenticated)
principals.extend(request_principals)
- protected_by = self.request_permission(environ)
+ permission = self.request_permission(environ)
acl = self.request_acl(environ)
- if not self.acl_allows(acl, protected_by, principals):
- reason = u'acl: %s principals: %s' % (acl, principals)
- return False, reason
- return True, None
+ allowed = self.acl_allows(acl, permission, principals)
+ result = {'acl': acl,
+ 'principals':principals,
+ 'permission':permission,
+ 'allowed':allowed}
+ if allowed:
+ result['action'] = Allow
+ else:
+ result['action'] = Deny
if self.after_check:
self.after_check(environ)
+ return result
def acl_allows(self, acl, protected_by, principals):
for ace in acl:
- for principal in flatten(principals):
+ for principal in principals:
if ace['principal'] == principal:
permissions = flatten(ace['permission'])
if protected_by in permissions:
action = ace['action']
- if action == 'allow':
+ if action == Allow:
return True
- # if it's a deny
return False
- return False
+ # no acl matched
+ return self.nomatch_allow
def _resolve(dotted_or_ep):
""" Resolve a dotted name or setuptools entry point to a callable.
@@ -106,9 +135,9 @@
before_check_hook=None,
after_check_hook=None,
initialize_hook=None,
- log_filename='decsec.log',
+ log_filename=None,
log_level='info',
- allow_on_nomatch='false',
+ nomatch_allow='false',
remote_user_key='REMOTE_USER',
deny_exception='paste.httpexceptions:HTTPUnauthorized',
):
@@ -118,10 +147,10 @@
raise ValueError('request_acl_hook must be specified')
if request_permission_hook is None:
raise ValueError('request_permission_hook must be specified')
- if allow_on_nomatch.lower().startswith('true'):
- allow_on_nomatch = True
+ if nomatch_allow.lower().startswith('true'):
+ nomatch_allow = True
else:
- allow_on_nomatch = False
+ nomatch_allow = False
deny_exception = _resolve(deny_exception)
request_principals = _resolve(request_principals_hook)
request_acl = _resolve(request_acl_hook)
@@ -136,6 +165,14 @@
if after_check_hook is not None:
after_check = _resolve(after_check_hook)
log_level = getattr(logging, log_level.upper())
+ if log_filename == None:
+ log_stream = None
+ elif log_filename == 'STDOUT':
+ log_stream = sys.stdout
+ elif log_filename == 'STDERR':
+ log_stream = sys.stderr
+ else:
+ log_stream = open(log_filename, 'a')
return DecsecMiddleware(app,
global_conf,
request_principals = request_principals,
@@ -144,9 +181,11 @@
initialize = initialize,
before_check = before_check,
after_check = after_check,
- log_filename='decsec.log',
- log_level=logging.INFO,
- allow_on_nomatch = False,
- remote_user_key='REMOTE_USER',
- deny_exception=httpexceptions.HTTPUnauthorized,
+ log_stream = log_stream,
+ log_level = log_level,
+ nomatch_allow = nomatch_allow,
+ remote_user_key = remote_user_key,
+ deny_exception = deny_exception,
)
+
+
Modified: repoze.decsec/trunk/repoze/decsec/sample.py
==============================================================================
--- repoze.decsec/trunk/repoze/decsec/sample.py (original)
+++ repoze.decsec/trunk/repoze/decsec/sample.py Fri Feb 22 01:24:21 2008
@@ -1,5 +1,7 @@
from repoze.decsec.interfaces import Everyone
from repoze.decsec.interfaces import Authenticated
+from repoze.decsec.interfaces import Allow
+from repoze.decsec.interfaces import Deny
def initialize(global_conf, middleware):
""" Hook point: hook.initialize """
@@ -28,10 +30,10 @@
def ACE(action, principal, permission):
return {'action':action, 'principal':principal, 'permission':permission}
-allow_cynics_to_any = ACE('allow', 'cynics', any)
-allow_agendaless_to_any = ACE('allow', 'agendaless', any)
-allow_authenticated_to_write = ACE('allow', Authenticated, 'write')
-allow_everyone_to_read = ACE('allow', Everyone, 'read')
+allow_cynics_to_any = ACE(Allow, 'cynics', any)
+allow_agendaless_to_any = ACE(Allow, 'agendaless', any)
+allow_authenticated_to_write = ACE(Allow, Authenticated, 'write')
+allow_everyone_to_read = ACE(Allow, Everyone, 'read')
places = [
Place('/cynics', 'write', [allow_cynics_to_any]),
@@ -57,7 +59,7 @@
for place in places:
if path.startswith(place.path):
return place.acl
- return [{'action':'deny', 'principal':Everyone, 'permission':any}]
+ return [{'action':Deny, 'principal':Everyone, 'permission':any}]
def request_permission(environ):
path = environ['PATH_INFO']
Modified: repoze.decsec/trunk/repoze/decsec/tests.py
==============================================================================
--- repoze.decsec/trunk/repoze/decsec/tests.py (original)
+++ repoze.decsec/trunk/repoze/decsec/tests.py Fri Feb 22 01:24:21 2008
@@ -1,4 +1,10 @@
import unittest
+from repoze.decsec.interfaces import Everyone
+from repoze.decsec.interfaces import Authenticated
+from repoze.decsec.interfaces import Allow
+from repoze.decsec.interfaces import Deny
+
+_DPRINC = [Everyone, Authenticated]
class TestMiddleware(unittest.TestCase):
def _getTargetClass(self):
@@ -9,11 +15,377 @@
klass = self._getTargetClass()
return klass(*arg, **kw)
+ def _rprincipals(self, environ, principals):
+ def request_principals(environ):
+ environ['request_principals_called'] = True
+ return principals
+ return request_principals
+
+ def _rpermission(self, environ, permission):
+ def request_permission(environ):
+ environ['request_permission_called'] = True
+ return permission
+ return request_permission
+
+ def _racl(self, environ, acl):
+ def request_acl(environ):
+ environ['request_acl_called'] = True
+ return acl
+ return request_acl
+
+ def _assertRCalls(self, environ):
+ self.assertEqual(environ['request_acl_called'], True)
+ self.assertEqual(environ['request_permission_called'], True)
+ self.assertEqual(environ['request_principals_called'], True)
+
+ def _assertResult(self, result, environ, action, acl, principals,
+ permission):
+ self._assertRCalls(environ)
+ allowed = result['allowed']
+ self.assertEqual(allowed, action == Allow)
+ def sorted(L):
+ new = list(L)
+ new.sort()
+ return new
+ self.assertEqual(result['acl'], acl)
+ self.assertEqual(sorted(result['principals']), sorted(principals))
+ self.assertEqual(result['permission'], permission)
def test_ctor(self):
app = DummyApp()
- inst = self._makeOne(app, None, None, None, None)
+ d = {}
+ def initializer(conf, mw):
+ d['conf'] = conf
+ d['mw'] = mw
+ conf = {}
+ inst = self._makeOne(app, conf, None, None, None, initializer)
self.assertEqual(inst.app, app)
+ self.failUnless(d['conf'] is conf)
+ self.failUnless(d['mw'] is inst)
-class DummyApp:
+ def test_check_allow_oneacl(self):
+ environ = {}
+ principals = ['user', 'group']
+ rprinc = self._rprincipals(environ, principals)
+ acl = [ACE(Allow, Everyone, 'view')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ inst = self._makeOne(app, {}, rprinc, racl, rperm)
+ result = inst.check(environ)
+ p = principals + _DPRINC
+ self._assertResult(result, environ, Allow, acl, p, 'view')
+
+ def test_check_deny_oneacl(self):
+ environ = {}
+ principals = ['user', 'group']
+ rprinc = self._rprincipals(environ, principals)
+ acl = [ACE(Deny, Everyone, 'view')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ inst = self._makeOne(app, {}, rprinc, racl, rperm)
+ result = inst.check(environ)
+ p = principals + _DPRINC
+ self._assertResult(result, environ, Deny, acl, p, 'view')
+
+ def test_check_allow_multiacl(self):
+ environ = {}
+ principals = ['user', 'group']
+ rprinc = self._rprincipals(environ, principals)
+ acl = [ACE(Deny, Everyone, 'write'), ACE(Allow, Everyone, 'view')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ inst = self._makeOne(app, {}, rprinc, racl, rperm)
+ result = inst.check(environ)
+ p = principals + _DPRINC
+ self._assertResult(result, environ, Allow, acl, p, 'view')
+
+ def test_check_deny_multiacl(self):
+ environ = {}
+ principals = ['user', 'group']
+ rprinc = self._rprincipals(environ, principals)
+ acl = [ACE(Allow, Everyone, 'write'), ACE(Deny, Everyone, 'view')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ inst = self._makeOne(app, {}, rprinc, racl, rperm)
+ result = inst.check(environ)
+ p = principals + _DPRINC
+ self._assertResult(result, environ, Deny, acl, p, 'view')
+
+ def test_check_nomatch_deny(self):
+ environ = {}
+ principals = ['user', 'group']
+ rprinc = self._rprincipals(environ, principals)
+ acl = [ACE(Allow, 'somebody', 'view')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ inst = self._makeOne(app, {}, rprinc, racl, rperm)
+ result = inst.check(environ)
+ p = principals + _DPRINC
+ self._assertResult(result, environ, Deny, acl, p, 'view')
+
+ def test_check_nomatch_allow(self):
+ environ = {}
+ principals = ['user', 'group']
+ rprinc = self._rprincipals(environ, principals)
+ acl = [ACE(Allow, 'somebody', 'write')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ inst = self._makeOne(app, {}, rprinc, racl, rperm, nomatch_allow=True)
+ result = inst.check(environ)
+ p = principals + _DPRINC
+ self._assertResult(result, environ, Allow, acl, p, 'view')
+
+ def test_check_nested_principals_list_allow(self):
+ environ = {}
+ principals = ['user','group', ['other', ['inner'] ] ]
+ rprinc = self._rprincipals(environ, principals)
+ acl = [ACE(Allow, 'inner', 'view')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ inst = self._makeOne(app, {}, rprinc, racl, rperm)
+ result = inst.check(environ)
+ p = ['user','group','other','inner'] + _DPRINC
+ self._assertResult(result, environ, Allow, acl, p, 'view')
+
+ def test_check_nested_principals_list_deny(self):
+ environ = {}
+ principals = ['user','group', ['other', ['inner'] ] ]
+ rprinc = self._rprincipals(environ, principals)
+ acl = [ACE(Allow, 'nobody', 'view')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ inst = self._makeOne(app, {}, rprinc, racl, rperm)
+ result = inst.check(environ)
+ p = ['user','group','other','inner'] + _DPRINC
+ self._assertResult(result, environ, Deny, acl, p, 'view')
+
+ def test_check_principals_string_allow(self):
+ environ = {}
+ principals = 'user'
+ rprinc = self._rprincipals(environ, principals)
+ acl = [ACE(Allow, 'user', 'view')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ inst = self._makeOne(app, {}, rprinc, racl, rperm)
+ result = inst.check(environ)
+ p = ['user'] + _DPRINC
+ self._assertResult(result, environ, Allow, acl, p, 'view')
+
+ def test_check_principals_string_deny(self):
+ environ = {}
+ principals = 'user'
+ rprinc = self._rprincipals(environ, principals)
+ acl = [ACE(Allow, 'nobody', 'view')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ inst = self._makeOne(app, {}, rprinc, racl, rperm)
+ result = inst.check(environ)
+ p = ['user'] + _DPRINC
+ self._assertResult(result, environ, Deny, acl, p, 'view')
+
+ def test_check_nested_permissions_allow(self):
+ environ = {}
+ principals = ['user']
+ rprinc = self._rprincipals(environ, principals)
+ permissions = ['outer', ['inner', ['view']]]
+ acl = [ACE(Allow, 'user', permissions)]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ inst = self._makeOne(app, {}, rprinc, racl, rperm)
+ result = inst.check(environ)
+ p = ['user'] + _DPRINC
+ self._assertResult(result, environ, Allow, acl, p, 'view')
+
+ def test_check_nested_permissions_deny(self):
+ environ = {}
+ principals = ['user']
+ rprinc = self._rprincipals(environ, principals)
+ permissions = ['outer', ['inner', ['view']]]
+ acl = [ACE(Allow, 'nobody', permissions)]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ inst = self._makeOne(app, {}, rprinc, racl, rperm)
+ result = inst.check(environ)
+ p = principals + _DPRINC
+ self._assertResult(result, environ, Deny, acl, p, 'view')
+
+ def test_call_allow(self):
+ environ = {}
+ principals = ['user']
+ rprinc = self._rprincipals(environ, principals)
+ acl = [ACE(Allow, Everyone, 'view')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ inst = self._makeOne(app, {}, rprinc, racl, rperm)
+ appresult = inst(environ, None)
+ self.assertEqual(appresult, 'got it')
+ self.assertEqual(app.environ, environ)
+ self.assertEqual(app.start_response, None)
+ self._assertRCalls(environ)
+
+ def test_call_deny(self):
+ environ = {}
+ principals = ['user']
+ rprinc = self._rprincipals(environ, principals)
+ acl = [ACE(Allow, 'nobody', 'view')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ inst = self._makeOne(app, {}, rprinc, racl, rperm,
+ deny_exception=ValueError)
+ self.assertRaises(ValueError, inst, environ, None)
+
+ def test_call_deny_logs_at_debug(self):
+ environ = {}
+ principals = ['user']
+ rprinc = self._rprincipals(environ, principals)
+ acl = [ACE(Allow, 'nobody', 'view')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ stream = DummyStream()
+ import logging
+ inst = self._makeOne(app, {}, rprinc, racl, rperm,
+ deny_exception=ValueError,
+ log_stream=stream, log_level=logging.DEBUG)
+ self.assertRaises(ValueError, inst, environ, None)
+ self.assertEqual(len(stream.written), 1)
+
+ def test_call_deny_logs_at_info(self):
+ environ = {}
+ principals = ['user']
+ rprinc = self._rprincipals(environ,['user','group'])
+ acl = [ACE(Allow, 'nobody', 'view')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ stream = DummyStream()
+ import logging
+ inst = self._makeOne(app, {}, rprinc, racl, rperm,
+ deny_exception=ValueError,
+ log_stream=stream, log_level=logging.INFO)
+ self.assertRaises(ValueError, inst, environ, None)
+ self.assertEqual(len(stream.written), 1)
+
+ def test_call_allow_logs_at_debug(self):
+ environ = {}
+ principals = ['user']
+ rprinc = self._rprincipals(environ, principals)
+ acl = [ACE(Allow, 'user', 'view')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ stream = DummyStream()
+ import logging
+ inst = self._makeOne(app, {}, rprinc, racl, rperm,
+ deny_exception=ValueError,
+ log_stream=stream, log_level=logging.DEBUG)
+ inst(environ, None)
+ self.assertEqual(len(stream.written), 1)
+
+ def test_call_allow_doesnt_log_at_info(self):
+ environ = {}
+ principals = ['user']
+ rprinc = self._rprincipals(environ, principals)
+ acl = [ACE(Allow, 'user', 'view')]
+ racl = self._racl(environ, acl )
+ rperm = self._rpermission(environ, 'view')
+ app = DummyApp()
+ stream = DummyStream()
+ import logging
+ inst = self._makeOne(app, {}, rprinc, racl, rperm,
+ deny_exception=ValueError,
+ log_stream=stream, log_level=logging.INFO)
+ inst(environ, None)
+ self.assertEqual(len(stream.written), 0)
+
+class TestMakeMiddleware(unittest.TestCase):
+ def _getFUT(self):
+ from repoze.decsec.middleware import make_middleware
+ return make_middleware
+
+ def test_makeone(self):
+ fn = self._getFUT()
+ app = DummyApp()
+ global_conf = {}
+ me = 'repoze.decsec.tests'
+ middleware = fn(
+ app,
+ global_conf,
+ request_principals_hook='%s:dummy_request_principals' % me,
+ request_acl_hook='%s:dummy_request_acl' % me,
+ request_permission_hook='%s:dummy_request_permission' % me,
+ before_check_hook='%s:dummy_before_check' % me,
+ after_check_hook='%s:dummy_after_check' % me,
+ initialize_hook='%s:dummy_initialize' % me,
+ log_filename='thefile.log',
+ log_level='debug',
+ nomatch_allow='true',
+ remote_user_key='HALLO',
+ deny_exception='%s:DummyException' %me,
+ )
+ self.assertEqual(middleware.app, app)
+ self.assertEqual(global_conf['initialize_called'], True)
+ self.assertEqual(middleware.remote_user_key, 'HALLO')
+ self.assertEqual(middleware.deny_exception, DummyException)
+ self.failIf(middleware.logger is None)
+ self.assertEqual(middleware.nomatch_allow, True)
+ self.assertEqual(middleware.request_principals,
+ dummy_request_principals)
+ self.assertEqual(middleware.request_acl, dummy_request_acl)
+ self.assertEqual(middleware.request_permission,
+ dummy_request_permission)
+ self.assertEqual(middleware.before_check, dummy_before_check)
+ self.assertEqual(middleware.after_check, dummy_after_check)
+
+def ACE(action, principal, permission):
+ return {'action':action, 'principal':principal, 'permission':permission}
+
+class DummyException(Exception):
pass
+
+def dummy_request_principals(environ):
+ environ['request_principals_called'] = True
+
+def dummy_request_acl(environ):
+ environ['request_acl_called'] = True
+
+def dummy_request_permission(environ):
+ environ['request_permission_called'] = True
+
+def dummy_initialize(global_conf, mw):
+ global_conf['initialize_called'] = True
+
+def dummy_before_check(environ):
+ environ['before_check_called'] = True
+
+def dummy_after_check(environ):
+ environ['after_check_called'] = True
+
+class DummyApp:
+ def __call__(self, environ, start_response):
+ self.environ = environ
+ self.start_response = start_response
+ return 'got it'
+
+class DummyStream:
+ def __init__(self):
+ self.written = []
+ def write(self, msg):
+ self.written.append(msg)
+ def flush(self, *arg, **kw):
+ pass
+
More information about the Repoze-checkins
mailing list