Hello community,

here is the log from the commit of package python-pecan for openSUSE:Factory 
checked in at 2014-09-25 08:43:05
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pecan (Old)
 and      /work/SRC/openSUSE:Factory/.python-pecan.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pecan"

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pecan/python-pecan.changes        
2014-09-03 20:55:08.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.python-pecan.new/python-pecan.changes   
2014-09-25 08:43:07.000000000 +0200
@@ -1,0 +2,19 @@
+Tue Sep 23 18:56:49 UTC 2014 - [email protected]
+
+- update to 0.7.0:
+  * Fixed an edge case in RestController routing which should have
+  returned an HTTP 400 but was instead raising an exception
+  (and thus, HTTP 500).
+  * Fixed an incorrect root logger configuration for
+  quickstarted pecan projects.
+  * Added pecan.state.arguments, a new feature for inspecting
+  controller call arguments.
+  * Fixed an infinite recursion error in PecanHook application.
+  Subclassing both rest.RestController and hooks.HookController 
+  resulted in an infinite recursion error in hook application
+  (which prevented applications from starting).
+  * Pecan’s tests are now included in its source distribution.
+- remove-logutils.diff: drop, logutils is needed now
+- reenable testsuite run during build again
+
+-------------------------------------------------------------------

Old:
----
  pecan-0.6.1.tar.gz
  remove-logutils.diff

New:
----
  pecan-0.7.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pecan.spec ++++++
--- /var/tmp/diff_new_pack.TQML4g/_old  2014-09-25 08:43:08.000000000 +0200
+++ /var/tmp/diff_new_pack.TQML4g/_new  2014-09-25 08:43:08.000000000 +0200
@@ -17,14 +17,13 @@
 
 
 Name:           python-pecan
-Version:        0.6.1
+Version:        0.7.0
 Release:        0
 Summary:        A WSGI object-dispatching web framework, designed to be lean 
and fast
 License:        BSD-3-Clause
 Group:          Development/Languages/Python
 Url:            http://github.com/dreamhost/pecan
 Source:         
https://pypi.python.org/packages/source/p/pecan/pecan-%{version}.tar.gz
-Patch0:         remove-logutils.diff
 BuildRequires:  python-devel
 BuildRequires:  python-setuptools
 # Test requirements:
@@ -36,17 +35,16 @@
 BuildRequires:  python-SQLAlchemy
 BuildRequires:  python-WebTest >= 1.3.1
 BuildRequires:  python-gunicorn
+BuildRequires:  python-logutils
 BuildRequires:  python-mock
 BuildRequires:  python-singledispatch
 BuildRequires:  python-virtualenv
 %endif
 BuildRequires:  python-six
-%if 0%{?suse_version} && 0%{?suse_version} <= 1110
-Requires:       python-logutils
-%endif
 Requires:       python-Mako >= 0.4.0
 Requires:       python-WebOb >= 1.2dev
 Requires:       python-WebTest >= 1.3.1
+Requires:       python-logutils
 Requires:       python-singledispatch
 Requires:       python-six
 Suggests:       python-Jinja2
@@ -64,7 +62,6 @@
 
 %prep
 %setup -q -n pecan-%{version}
-%patch0
 # Let's not depend on Kajiki, a Genshi clone. Genshi should be enough:
 sed -i "/'Kajiki',/d" setup.py
 
@@ -74,11 +71,10 @@
 %install
 python setup.py install --prefix=%{_prefix} --root=%{buildroot}
 
-# disable testsuite for now - upstream tarball doesn't include all needed files
-#%if 0%{?suse_version} >= 1230
-#%check
-#python setup.py test
-#%endif
+%if 0%{?suse_version} >= 1230
+%check
+python setup.py test
+%endif
 
 %files
 %defattr(-,root,root,-)

++++++ pecan-0.6.1.tar.gz -> pecan-0.7.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/MANIFEST.in new/pecan-0.7.0/MANIFEST.in
--- old/pecan-0.6.1/MANIFEST.in 2014-07-10 17:33:20.000000000 +0200
+++ new/pecan-0.7.0/MANIFEST.in 2014-08-29 14:51:05.000000000 +0200
@@ -4,3 +4,4 @@
 include pecan/scaffolds/rest-api/*
 include pecan/middleware/resources/*
 include LICENSE README.rst requirements.txt
+recursive-include pecan/tests *
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/PKG-INFO new/pecan-0.7.0/PKG-INFO
--- old/pecan-0.6.1/PKG-INFO    2014-07-10 17:33:30.000000000 +0200
+++ new/pecan-0.7.0/PKG-INFO    2014-08-29 14:51:16.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pecan
-Version: 0.6.1
+Version: 0.7.0
 Summary: A WSGI object-dispatching web framework, designed to be lean and 
fast, with few dependancies.
 Home-page: http://github.com/stackforge/pecan
 Author: Jonathan LaCour
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/core.py 
new/pecan-0.7.0/pecan/core.py
--- old/pecan-0.6.1/pecan/core.py       2014-07-10 17:33:20.000000000 +0200
+++ new/pecan-0.7.0/pecan/core.py       2014-08-29 14:51:05.000000000 +0200
@@ -2,6 +2,7 @@
     from simplejson import dumps, loads
 except ImportError:  # pragma: no cover
     from json import dumps, loads  # noqa
+from inspect import Arguments
 from itertools import chain, tee
 from mimetypes import guess_type, add_type
 from os.path import splitext
@@ -31,12 +32,14 @@
 
 class RoutingState(object):
 
-    def __init__(self, request, response, app, hooks=[], controller=None):
+    def __init__(self, request, response, app, hooks=[], controller=None,
+                 arguments=None):
         self.request = request
         self.response = response
         self.app = app
         self.hooks = hooks
         self.controller = controller
+        self.arguments = arguments
 
 
 class Request(WebObRequest):
@@ -326,6 +329,7 @@
         passed the argument specification for the controller.
         '''
         args = []
+        varargs = []
         kwargs = dict()
         valid_args = argspec.args[1:]  # pop off `self`
         pecan_state = state.request.pecan
@@ -354,7 +358,7 @@
         if [i for i in remainder if i]:
             if not argspec[1]:
                 abort(404)
-            args.extend(remainder)
+            varargs.extend(remainder)
 
         # get the default positional arguments
         if argspec[3]:
@@ -377,7 +381,7 @@
                 if name not in argspec[0]:
                     kwargs[encode_if_needed(name)] = value
 
-        return args, kwargs
+        return args, varargs, kwargs
 
     def render(self, template, namespace):
         renderer = self.renderers.get(
@@ -492,9 +496,6 @@
             )
             raise exc.HTTPNotFound
 
-        # handle "before" hooks
-        self.handle_hooks(self.determine_hooks(controller), 'before', state)
-
         # fetch any parameters
         if req.method == 'GET':
             params = dict(req.GET)
@@ -502,15 +503,19 @@
             params = dict(req.params)
 
         # fetch the arguments for the controller
-        args, kwargs = self.get_args(
+        args, varargs, kwargs = self.get_args(
             state,
             params,
             remainder,
             cfg['argspec'],
             im_self
         )
+        state.arguments = Arguments(args, varargs, kwargs)
 
-        return controller, args, kwargs
+        # handle "before" hooks
+        self.handle_hooks(self.determine_hooks(controller), 'before', state)
+
+        return controller, args+varargs, kwargs
 
     def invoke_controller(self, controller, args, kwargs, state):
         '''
@@ -566,33 +571,37 @@
             resp.text = result
         elif result:
             resp.body = result
-        elif response.status_int == 200:
+
+        if pecan_state['content_type']:
+            # set the content type
+            resp.content_type = pecan_state['content_type']
+
+    def _handle_empty_response_body(self, state):
+        # Enforce HTTP 204 for responses which contain no body
+        if state.response.status_int == 200:
             # If the response is a generator...
-            if isinstance(response.app_iter, types.GeneratorType):
+            if isinstance(state.response.app_iter, types.GeneratorType):
                 # Split the generator into two so we can peek at one of them
                 # and determine if there is any response body content
-                a, b = tee(response.app_iter)
+                a, b = tee(state.response.app_iter)
                 try:
                     next(a)
                 except StopIteration:
                     # If we hit StopIteration, the body is empty
-                    resp.status = 204
+                    state.response.status = 204
                 finally:
-                    resp.app_iter = b
+                    state.response.app_iter = b
             else:
                 text = None
-                if response.charset:
+                if state.response.charset:
                     # `response.text` cannot be accessed without a charset
                     # (because we don't know which encoding to use)
-                    text = response.text
-                if not any((response.body, text)):
-                    resp.status = 204
-
-        if resp.status_int in (204, 304):
-            resp.content_type = None
-        elif pecan_state['content_type']:
-            # set the content type
-            resp.content_type = pecan_state['content_type']
+                    text = state.response.text
+                if not any((state.response.body, text)):
+                    state.response.status = 204
+
+        if state.response.status_int in (204, 304):
+            state.response.content_type = None
 
     def __call__(self, environ, start_response):
         '''
@@ -663,6 +672,8 @@
                 self.determine_hooks(state.controller), 'after', state
             )
 
+        self._handle_empty_response_body(state)
+
         # get the response
         return state.response(environ, start_response)
 
@@ -691,11 +702,11 @@
         except IndexError:
             raise signature_error
 
-        args, kwargs = super(ExplicitPecan, self).get_args(
+        args, varargs, kwargs = super(ExplicitPecan, self).get_args(
             state, all_params, remainder, argspec, im_self
         )
         args = [state.request, state.response] + args
-        return args, kwargs
+        return args, varargs, kwargs
 
 
 class Pecan(PecanBase):
@@ -747,12 +758,14 @@
             state.hooks = []
             state.app = self
             state.controller = None
+            state.arguments = None
             return super(Pecan, self).__call__(environ, start_response)
         finally:
             del state.hooks
             del state.request
             del state.response
             del state.controller
+            del state.arguments
             del state.app
 
     def init_context_local(self, local_factory):
@@ -766,6 +779,7 @@
         state.response = _state.response
         controller, args, kw = super(Pecan, self).find_controller(_state)
         state.controller = controller
+        state.arguments = _state.arguments
         return controller, args, kw
 
     def handle_hooks(self, hooks, *args, **kw):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/hooks.py 
new/pecan-0.7.0/pecan/hooks.py
--- old/pecan-0.6.1/pecan/hooks.py      2014-07-10 17:33:20.000000000 +0200
+++ new/pecan-0.7.0/pecan/hooks.py      2014-08-29 14:51:05.000000000 +0200
@@ -1,3 +1,4 @@
+import types
 import sys
 from inspect import getmembers
 
@@ -27,7 +28,15 @@
                 for hook in hooks:
                     value._pecan.setdefault('hooks', set()).add(hook)
             elif hasattr(value, '__class__'):
-                if name.startswith('__') and name.endswith('__'):
+                # Skip non-exposed methods that are defined in parent classes;
+                # they're internal implementation details of that class, and
+                # not actual routable controllers, so we shouldn't bother
+                # assigning hooks to them.
+                if (
+                    isinstance(value, types.MethodType) and
+                    any(filter(lambda c: value.__func__ in c.__dict__.values(),
+                               value.im_class.mro()[1:]))
+                ):
                     continue
                 walk_controller(root_class, value, hooks)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/rest.py 
new/pecan-0.7.0/pecan/rest.py
--- old/pecan-0.6.1/pecan/rest.py       2014-07-10 17:33:20.000000000 +0200
+++ new/pecan-0.7.0/pecan/rest.py       2014-08-29 14:51:05.000000000 +0200
@@ -43,6 +43,19 @@
             return argspec.args[3:]
         return argspec.args[1:]
 
+    def _handle_bad_rest_arguments(self, controller, remainder, request):
+        """
+        Ensure that the argspec for a discovered controller actually matched
+        the positional arguments in the request path.  If not, raise
+        a webob.exc.HTTPBadRequest.
+        """
+        argspec = self._get_args_for_controller(controller)
+        fixed_args = len(argspec) - len(
+            request.pecan.get('routing_args', [])
+        )
+        if len(remainder) < fixed_args:
+            abort(400)
+
     @expose()
     def _route(self, args, request=None):
         '''
@@ -89,10 +102,10 @@
                 _lookup_result = self._handle_lookup(args, request)
                 if _lookup_result:
                     return _lookup_result
-        except exc.HTTPNotFound:
+        except (exc.HTTPClientError, exc.HTTPNotFound):
             #
-            # If the matching handler results in a 404, attempt to handle
-            # a _lookup method (if it exists)
+            # If the matching handler results in a 400 or 404, attempt to
+            # handle a _lookup method (if it exists)
             #
             _lookup_result = self._handle_lookup(args, request)
             if _lookup_result:
@@ -201,14 +214,10 @@
 
         # route to a get_all or get if no additional parts are available
         if not remainder or remainder == ['']:
+            remainder = list(six.moves.filter(bool, remainder))
             controller = self._find_controller('get_all', 'get')
             if controller:
-                argspec = self._get_args_for_controller(controller)
-                fixed_args = len(argspec) - len(
-                    request.pecan.get('routing_args', [])
-                )
-                if len(remainder) < fixed_args:
-                    abort(404)
+                self._handle_bad_rest_arguments(controller, remainder, request)
                 return controller, []
             abort(404)
 
@@ -232,6 +241,7 @@
         # finally, check for the regular get_one/get requests
         controller = self._find_controller('get_one', 'get')
         if controller:
+            self._handle_bad_rest_arguments(controller, remainder, request)
             return controller, remainder
 
         abort(404)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/scaffolds/base/config.py_tmpl 
new/pecan-0.7.0/pecan/scaffolds/base/config.py_tmpl
--- old/pecan-0.6.1/pecan/scaffolds/base/config.py_tmpl 2014-07-10 
17:33:20.000000000 +0200
+++ new/pecan-0.7.0/pecan/scaffolds/base/config.py_tmpl 2014-08-29 
14:51:05.000000000 +0200
@@ -18,8 +18,8 @@
 }
 
 logging = {
+    'root': {'level': 'INFO', 'handlers': ['console']},
     'loggers': {
-        'root': {'level': 'INFO', 'handlers': ['console']},
         '${package}': {'level': 'DEBUG', 'handlers': ['console']},
         'pecan.commands.serve': {'level': 'DEBUG', 'handlers': ['console']},
         'py.warnings': {'handlers': ['console']},
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/scaffolds/rest-api/config.py_tmpl 
new/pecan-0.7.0/pecan/scaffolds/rest-api/config.py_tmpl
--- old/pecan-0.6.1/pecan/scaffolds/rest-api/config.py_tmpl     2014-07-10 
17:33:20.000000000 +0200
+++ new/pecan-0.7.0/pecan/scaffolds/rest-api/config.py_tmpl     2014-08-29 
14:51:05.000000000 +0200
@@ -12,8 +12,8 @@
 }
 
 logging = {
+    'root': {'level': 'INFO', 'handlers': ['console']},
     'loggers': {
-        'root': {'level': 'INFO', 'handlers': ['console']},
         '${package}': {'level': 'DEBUG', 'handlers': ['console']},
         'pecan.commands.serve': {'level': 'DEBUG', 'handlers': ['console']},
         'py.warnings': {'handlers': ['console']},
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pecan-0.6.1/pecan/tests/config_fixtures/bad/importerror.py 
new/pecan-0.7.0/pecan/tests/config_fixtures/bad/importerror.py
--- old/pecan-0.6.1/pecan/tests/config_fixtures/bad/importerror.py      
1970-01-01 01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/config_fixtures/bad/importerror.py      
2014-08-29 14:51:05.000000000 +0200
@@ -0,0 +1 @@
+import pecan.thismoduledoesnotexist
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pecan-0.6.1/pecan/tests/config_fixtures/bad/module_and_underscore.py 
new/pecan-0.7.0/pecan/tests/config_fixtures/bad/module_and_underscore.py
--- old/pecan-0.6.1/pecan/tests/config_fixtures/bad/module_and_underscore.py    
1970-01-01 01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/config_fixtures/bad/module_and_underscore.py    
2014-08-29 14:51:05.000000000 +0200
@@ -0,0 +1,4 @@
+import sys
+
+__badattr__ = True
+moduleattr = sys
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/config_fixtures/config.py 
new/pecan-0.7.0/pecan/tests/config_fixtures/config.py
--- old/pecan-0.6.1/pecan/tests/config_fixtures/config.py       1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/config_fixtures/config.py       2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1,22 @@
+
+
+# Server Specific Configurations
+server = {
+    'port': '8081',
+    'host': '1.1.1.1',
+    'hostport': '{pecan.conf.server.host}:{pecan.conf.server.port}'
+}
+
+# Pecan Application Configurations
+app = {
+    'static_root': 'public',
+    'template_path': 'myproject/templates',
+    'debug': True
+}
+
+# Custom Configurations must be in Python dictionary format::
+#
+# foo = {'bar':'baz'}
+#
+# All configurations are accessible at::
+# pecan.conf
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/config_fixtures/empty.py 
new/pecan-0.7.0/pecan/tests/config_fixtures/empty.py
--- old/pecan-0.6.1/pecan/tests/config_fixtures/empty.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/config_fixtures/empty.py        2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1,2 @@
+app = {}
+server = {}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/config_fixtures/foobar.py 
new/pecan-0.7.0/pecan/tests/config_fixtures/foobar.py
--- old/pecan-0.6.1/pecan/tests/config_fixtures/foobar.py       1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/config_fixtures/foobar.py       2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1 @@
+foo = "bar"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/config_fixtures/forcedict.py 
new/pecan-0.7.0/pecan/tests/config_fixtures/forcedict.py
--- old/pecan-0.6.1/pecan/tests/config_fixtures/forcedict.py    1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/config_fixtures/forcedict.py    2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1,14 @@
+# Pecan Application Configurations
+beaker = {
+    'session.key': 'key',
+    'session.type': 'cookie',
+    'session.validate_key': '1a971a7df182df3e1dec0af7c6913ec7',
+    '__force_dict__': True
+}
+
+# Custom Configurations must be in Python dictionary format::
+#
+# foo = {'bar':'baz'}
+#
+# All configurations are accessible at::
+# pecan.conf
Files old/pecan-0.6.1/pecan/tests/middleware/static_fixtures/self.png and 
new/pecan-0.7.0/pecan/tests/middleware/static_fixtures/self.png differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pecan-0.6.1/pecan/tests/middleware/static_fixtures/text.txt 
new/pecan-0.7.0/pecan/tests/middleware/static_fixtures/text.txt
--- old/pecan-0.6.1/pecan/tests/middleware/static_fixtures/text.txt     
1970-01-01 01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/middleware/static_fixtures/text.txt     
2014-08-29 14:51:05.000000000 +0200
@@ -0,0 +1,9 @@
+This is a test text file.
+
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
+veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
+commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
+velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
+occaecat cupidatat non proident, sunt in culpa qui officia deserunt
+mollit anim id est laborum.
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pecan-0.6.1/pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl 
new/pecan-0.7.0/pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl
--- old/pecan-0.6.1/pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl 
1970-01-01 01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl 
2014-08-29 14:51:05.000000000 +0200
@@ -0,0 +1 @@
+Pecan ${package}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pecan-0.6.1/pecan/tests/scaffold_fixtures/content_sub/foo_tmpl 
new/pecan-0.7.0/pecan/tests/scaffold_fixtures/content_sub/foo_tmpl
--- old/pecan-0.6.1/pecan/tests/scaffold_fixtures/content_sub/foo_tmpl  
1970-01-01 01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/scaffold_fixtures/content_sub/foo_tmpl  
2014-08-29 14:51:05.000000000 +0200
@@ -0,0 +1 @@
+YAR ${package}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pecan-0.6.1/pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt 
new/pecan-0.7.0/pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt
--- 
old/pecan-0.6.1/pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt   
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/pecan-0.7.0/pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt   
    2014-08-29 14:51:05.000000000 +0200
@@ -0,0 +1 @@
+Pecan
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pecan-0.6.1/pecan/tests/scaffold_fixtures/file_sub/foo_+package+ 
new/pecan-0.7.0/pecan/tests/scaffold_fixtures/file_sub/foo_+package+
--- old/pecan-0.6.1/pecan/tests/scaffold_fixtures/file_sub/foo_+package+        
1970-01-01 01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/scaffold_fixtures/file_sub/foo_+package+        
2014-08-29 14:51:05.000000000 +0200
@@ -0,0 +1 @@
+YAR
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pecan-0.6.1/pecan/tests/scaffold_fixtures/simple/bar/spam.txt 
new/pecan-0.7.0/pecan/tests/scaffold_fixtures/simple/bar/spam.txt
--- old/pecan-0.6.1/pecan/tests/scaffold_fixtures/simple/bar/spam.txt   
1970-01-01 01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/scaffold_fixtures/simple/bar/spam.txt   
2014-08-29 14:51:05.000000000 +0200
@@ -0,0 +1 @@
+Pecan
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/scaffold_fixtures/simple/foo 
new/pecan-0.7.0/pecan/tests/scaffold_fixtures/simple/foo
--- old/pecan-0.6.1/pecan/tests/scaffold_fixtures/simple/foo    1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/scaffold_fixtures/simple/foo    2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1 @@
+YAR
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/templates/form_colors.html 
new/pecan-0.7.0/pecan/tests/templates/form_colors.html
--- old/pecan-0.6.1/pecan/tests/templates/form_colors.html      1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/form_colors.html      2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1,2 @@
+<input type="text" id="colors-0" name="colors-0" value="${data['colors'][0] if 
data else 'A color'}" />
+<input type="text" id="colors-1" name="colors-1" value="${data['colors'][1] if 
data else 'A color'}" />
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pecan-0.6.1/pecan/tests/templates/form_colors_invalid.html 
new/pecan-0.7.0/pecan/tests/templates/form_colors_invalid.html
--- old/pecan-0.6.1/pecan/tests/templates/form_colors_invalid.html      
1970-01-01 01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/form_colors_invalid.html      
2014-08-29 14:51:05.000000000 +0200
@@ -0,0 +1,2 @@
+<input type="text" id="colors-0" name="colors-0" value="blue" />
+<input type="text" id="colors-1" name="colors-1" value="" class="error" />
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pecan-0.6.1/pecan/tests/templates/form_colors_valid.html 
new/pecan-0.7.0/pecan/tests/templates/form_colors_valid.html
--- old/pecan-0.6.1/pecan/tests/templates/form_colors_valid.html        
1970-01-01 01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/form_colors_valid.html        
2014-08-29 14:51:05.000000000 +0200
@@ -0,0 +1,2 @@
+<input type="text" id="colors-0" name="colors-0" value="blue" />
+<input type="text" id="colors-1" name="colors-1" value="red" />
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pecan-0.6.1/pecan/tests/templates/form_login_invalid.html 
new/pecan-0.7.0/pecan/tests/templates/form_login_invalid.html
--- old/pecan-0.6.1/pecan/tests/templates/form_login_invalid.html       
1970-01-01 01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/form_login_invalid.html       
2014-08-29 14:51:05.000000000 +0200
@@ -0,0 +1,2 @@
+<input type="text" id="username" name="username" value="ryan" />
+<input type="password" id="password" name="password" value="" class="error" />
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pecan-0.6.1/pecan/tests/templates/form_login_valid.html 
new/pecan-0.7.0/pecan/tests/templates/form_login_valid.html
--- old/pecan-0.6.1/pecan/tests/templates/form_login_valid.html 1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/form_login_valid.html 2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1,2 @@
+<input type="text" id="username" name="username" />
+<input type="password" id="password" name="password" value="" />
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/templates/form_name.html 
new/pecan-0.7.0/pecan/tests/templates/form_name.html
--- old/pecan-0.6.1/pecan/tests/templates/form_name.html        1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/form_name.html        2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1 @@
+<input type="text" id="name" name="name" value="${name if name else ''}" />
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pecan-0.6.1/pecan/tests/templates/form_name_invalid.html 
new/pecan-0.7.0/pecan/tests/templates/form_name_invalid.html
--- old/pecan-0.6.1/pecan/tests/templates/form_name_invalid.html        
1970-01-01 01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/form_name_invalid.html        
2014-08-29 14:51:05.000000000 +0200
@@ -0,0 +1,3 @@
+<!-- for: name -->
+<span class="error-message">Please enter a value</span><br />
+<input type="text" id="name" name="name" value="" class="error" />
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pecan-0.6.1/pecan/tests/templates/form_name_invalid_custom.html 
new/pecan-0.7.0/pecan/tests/templates/form_name_invalid_custom.html
--- old/pecan-0.6.1/pecan/tests/templates/form_name_invalid_custom.html 
1970-01-01 01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/form_name_invalid_custom.html 
2014-08-29 14:51:05.000000000 +0200
@@ -0,0 +1,3 @@
+<!-- for: name -->
+<span class="error-message">Names must be unique</span><br />
+<input type="text" id="name" name="name" value="Yoann" class="error" />
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pecan-0.6.1/pecan/tests/templates/form_name_valid.html 
new/pecan-0.7.0/pecan/tests/templates/form_name_valid.html
--- old/pecan-0.6.1/pecan/tests/templates/form_name_valid.html  1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/form_name_valid.html  2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1 @@
+<input type="text" id="name" name="name" value="Yoann" />
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/templates/genshi.html 
new/pecan-0.7.0/pecan/tests/templates/genshi.html
--- old/pecan-0.6.1/pecan/tests/templates/genshi.html   1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/genshi.html   2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1,16 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
+                      
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd";>
+<html xmlns="http://www.w3.org/1999/xhtml";
+      xmlns:py="http://genshi.edgewall.org/";
+      xmlns:xi="http://www.w3.org/2001/XInclude";>
+
+<head>
+  <meta content="text/html; charset=UTF-8" http-equiv="content-type" 
py:replace="''"/>
+  <title>Hello, ${name}!</title>       
+</head>
+
+<body>
+  <h1>Hello, ${name}!</h1>
+</body>
+
+</html>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/templates/genshi_bad.html 
new/pecan-0.7.0/pecan/tests/templates/genshi_bad.html
--- old/pecan-0.6.1/pecan/tests/templates/genshi_bad.html       1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/genshi_bad.html       2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1,18 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
+                      
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd";>
+<html xmlns="http://www.w3.org/1999/xhtml";
+      xmlns:py="http://genshi.edgewall.org/";
+      xmlns:xi="http://www.w3.org/2001/XInclude";>
+
+<head>
+  <meta content="text/html; charset=UTF-8" http-equiv="content-type" 
py:replace="''"/>
+  <title>Hello, ${name}!</title>       
+<!-- comment out close tag to cause error
+</head>
+-->
+
+<body>
+  <h1>Hello, ${name}!</h1>
+</body>
+
+</html>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/templates/jinja.html 
new/pecan-0.7.0/pecan/tests/templates/jinja.html
--- old/pecan-0.6.1/pecan/tests/templates/jinja.html    1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/jinja.html    2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1,11 @@
+<html>
+
+<head>
+    <title>Hello, {{name}}!</title>    
+</head>
+
+<body>
+    <h1>Hello, {{name}}!</h1>
+</body>
+
+</html>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/templates/jinja_bad.html 
new/pecan-0.7.0/pecan/tests/templates/jinja_bad.html
--- old/pecan-0.6.1/pecan/tests/templates/jinja_bad.html        1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/jinja_bad.html        2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1,13 @@
+<html>
+
+<head>
+    <title>Hello, {{name}}!</title>    
+</head>
+
+<body>
+    <h1>Hello, {{name}}!</h1>
+</body>
+{# open a block without and name #}
+{% block %}
+
+</html>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/templates/kajiki.html 
new/pecan-0.7.0/pecan/tests/templates/kajiki.html
--- old/pecan-0.6.1/pecan/tests/templates/kajiki.html   1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/kajiki.html   2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1,11 @@
+<html>
+
+<head>
+  <title>Hello, ${name}!</title>       
+</head>
+
+<body>
+  <h1>Hello, ${name}!</h1>
+</body>
+
+</html>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/templates/mako.html 
new/pecan-0.7.0/pecan/tests/templates/mako.html
--- old/pecan-0.6.1/pecan/tests/templates/mako.html     1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/mako.html     2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1,11 @@
+<html>
+
+<head>
+  <title>Hello, ${name}!</title>       
+</head>
+
+<body>
+  <h1>Hello, ${name}!</h1>
+</body>
+
+</html>
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/templates/mako_bad.html 
new/pecan-0.7.0/pecan/tests/templates/mako_bad.html
--- old/pecan-0.6.1/pecan/tests/templates/mako_bad.html 1970-01-01 
01:00:00.000000000 +0100
+++ new/pecan-0.7.0/pecan/tests/templates/mako_bad.html 2014-08-29 
14:51:05.000000000 +0200
@@ -0,0 +1,6 @@
+<% 
+
+    def bad_indentation:
+return None
+
+%>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/test_hooks.py 
new/pecan-0.7.0/pecan/tests/test_hooks.py
--- old/pecan-0.6.1/pecan/tests/test_hooks.py   2014-07-10 17:33:20.000000000 
+0200
+++ new/pecan-0.7.0/pecan/tests/test_hooks.py   2014-08-29 14:51:05.000000000 
+0200
@@ -1,11 +1,13 @@
+import inspect
+import operator
+
 from webtest import TestApp
+from six import PY3
 from six import b as b_
 from six import u as u_
 from six.moves import cStringIO as StringIO
 
-from webob import Response
-
-from pecan import make_app, expose, redirect, abort
+from pecan import make_app, expose, redirect, abort, rest, Request, Response
 from pecan.hooks import (
     PecanHook, TransactionHook, HookController, RequestViewerHook
 )
@@ -13,6 +15,9 @@
 from pecan.decorators import transactional, after_commit, after_rollback
 from pecan.tests import PecanTestCase
 
+# The `inspect.Arguments` namedtuple is different between PY2/3
+kwargs = operator.attrgetter('varkw' if PY3 else 'keywords')
+
 
 class TestHooks(PecanTestCase):
 
@@ -412,6 +417,364 @@
         assert run_hook[3] == 'last - before hook', run_hook[3]
 
 
+class TestStateAccess(PecanTestCase):
+
+    def setUp(self):
+        super(TestStateAccess, self).setUp()
+        self.args = None
+
+        class RootController(object):
+            @expose()
+            def index(self):
+                return 'Hello, World!'
+
+            @expose()
+            def greet(self, name):
+                return 'Hello, %s!' % name
+
+            @expose()
+            def greetmore(self, *args):
+                return 'Hello, %s!' % args[0]
+
+            @expose()
+            def kwargs(self, **kw):
+                return 'Hello, %s!' % kw['name']
+
+            @expose()
+            def mixed(self, first, second, *args):
+                return 'Mixed'
+
+        class SimpleHook(PecanHook):
+            def before(inself, state):
+                self.args = (state.controller, state.arguments)
+
+        self.root = RootController()
+        self.app = TestApp(make_app(self.root, hooks=[SimpleHook()]))
+
+    def test_no_args(self):
+        self.app.get('/')
+        assert self.args[0] == self.root.index
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == []
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {}
+
+    def test_single_arg(self):
+        self.app.get('/greet/joe')
+        assert self.args[0] == self.root.greet
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == ['joe']
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {}
+
+    def test_single_vararg(self):
+        self.app.get('/greetmore/joe')
+        assert self.args[0] == self.root.greetmore
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == []
+        assert self.args[1].varargs == ['joe']
+        assert kwargs(self.args[1]) == {}
+
+    def test_single_kw(self):
+        self.app.get('/kwargs/?name=joe')
+        assert self.args[0] == self.root.kwargs
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == []
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {'name': 'joe'}
+
+    def test_single_kw_post(self):
+        self.app.post('/kwargs/', params={'name': 'joe'})
+        assert self.args[0] == self.root.kwargs
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == []
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {'name': 'joe'}
+
+    def test_mixed_args(self):
+        self.app.get('/mixed/foo/bar/spam/eggs')
+        assert self.args[0] == self.root.mixed
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == ['foo', 'bar']
+        assert self.args[1].varargs == ['spam', 'eggs']
+
+
+class TestStateAccessWithoutThreadLocals(PecanTestCase):
+
+    def setUp(self):
+        super(TestStateAccessWithoutThreadLocals, self).setUp()
+        self.args = None
+
+        class RootController(object):
+            @expose()
+            def index(self, req, resp):
+                return 'Hello, World!'
+
+            @expose()
+            def greet(self, req, resp, name):
+                return 'Hello, %s!' % name
+
+            @expose()
+            def greetmore(self, req, resp, *args):
+                return 'Hello, %s!' % args[0]
+
+            @expose()
+            def kwargs(self, req, resp, **kw):
+                return 'Hello, %s!' % kw['name']
+
+            @expose()
+            def mixed(self, req, resp, first, second, *args):
+                return 'Mixed'
+
+        class SimpleHook(PecanHook):
+            def before(inself, state):
+                self.args = (state.controller, state.arguments)
+
+        self.root = RootController()
+        self.app = TestApp(make_app(
+            self.root,
+            hooks=[SimpleHook()],
+            use_context_locals=False
+        ))
+
+    def test_no_args(self):
+        self.app.get('/')
+        assert self.args[0] == self.root.index
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert len(self.args[1].args) == 2
+        assert isinstance(self.args[1].args[0], Request)
+        assert isinstance(self.args[1].args[1], Response)
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {}
+
+    def test_single_arg(self):
+        self.app.get('/greet/joe')
+        assert self.args[0] == self.root.greet
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert len(self.args[1].args) == 3
+        assert isinstance(self.args[1].args[0], Request)
+        assert isinstance(self.args[1].args[1], Response)
+        assert self.args[1].args[2] == 'joe'
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {}
+
+    def test_single_vararg(self):
+        self.app.get('/greetmore/joe')
+        assert self.args[0] == self.root.greetmore
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert len(self.args[1].args) == 2
+        assert isinstance(self.args[1].args[0], Request)
+        assert isinstance(self.args[1].args[1], Response)
+        assert self.args[1].varargs == ['joe']
+        assert kwargs(self.args[1]) == {}
+
+    def test_single_kw(self):
+        self.app.get('/kwargs/?name=joe')
+        assert self.args[0] == self.root.kwargs
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert len(self.args[1].args) == 2
+        assert isinstance(self.args[1].args[0], Request)
+        assert isinstance(self.args[1].args[1], Response)
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {'name': 'joe'}
+
+    def test_single_kw_post(self):
+        self.app.post('/kwargs/', params={'name': 'joe'})
+        assert self.args[0] == self.root.kwargs
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert len(self.args[1].args) == 2
+        assert isinstance(self.args[1].args[0], Request)
+        assert isinstance(self.args[1].args[1], Response)
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {'name': 'joe'}
+
+    def test_mixed_args(self):
+        self.app.get('/mixed/foo/bar/spam/eggs')
+        assert self.args[0] == self.root.mixed
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert len(self.args[1].args) == 4
+        assert isinstance(self.args[1].args[0], Request)
+        assert isinstance(self.args[1].args[1], Response)
+        assert self.args[1].args[2:] == ['foo', 'bar']
+        assert self.args[1].varargs == ['spam', 'eggs']
+
+
+class TestRestControllerStateAccess(PecanTestCase):
+
+    def setUp(self):
+        super(TestRestControllerStateAccess, self).setUp()
+        self.args = None
+
+        class RootController(rest.RestController):
+
+            @expose()
+            def _default(self, _id, *args, **kw):
+                return 'Default'
+
+            @expose()
+            def get_all(self, **kw):
+                return 'All'
+
+            @expose()
+            def get_one(self, _id, *args, **kw):
+                return 'One'
+
+            @expose()
+            def post(self, *args, **kw):
+                return 'POST'
+
+            @expose()
+            def put(self, _id, *args, **kw):
+                return 'PUT'
+
+            @expose()
+            def delete(self, _id, *args, **kw):
+                return 'DELETE'
+
+        class SimpleHook(PecanHook):
+            def before(inself, state):
+                self.args = (state.controller, state.arguments)
+
+        self.root = RootController()
+        self.app = TestApp(make_app(self.root, hooks=[SimpleHook()]))
+
+    def test_get_all(self):
+        self.app.get('/')
+        assert self.args[0] == self.root.get_all
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == []
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {}
+
+    def test_get_all_with_kwargs(self):
+        self.app.get('/?foo=bar')
+        assert self.args[0] == self.root.get_all
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == []
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {'foo': 'bar'}
+
+    def test_get_one(self):
+        self.app.get('/1')
+        assert self.args[0] == self.root.get_one
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == ['1']
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {}
+
+    def test_get_one_with_varargs(self):
+        self.app.get('/1/2/3')
+        assert self.args[0] == self.root.get_one
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == ['1']
+        assert self.args[1].varargs == ['2', '3']
+        assert kwargs(self.args[1]) == {}
+
+    def test_get_one_with_kwargs(self):
+        self.app.get('/1?foo=bar')
+        assert self.args[0] == self.root.get_one
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == ['1']
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {'foo': 'bar'}
+
+    def test_post(self):
+        self.app.post('/')
+        assert self.args[0] == self.root.post
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == []
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {}
+
+    def test_post_with_varargs(self):
+        self.app.post('/foo/bar')
+        assert self.args[0] == self.root.post
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == []
+        assert self.args[1].varargs == ['foo', 'bar']
+        assert kwargs(self.args[1]) == {}
+
+    def test_post_with_kwargs(self):
+        self.app.post('/', params={'foo': 'bar'})
+        assert self.args[0] == self.root.post
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == []
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {'foo': 'bar'}
+
+    def test_put(self):
+        self.app.put('/1')
+        assert self.args[0] == self.root.put
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == ['1']
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {}
+
+    def test_put_with_method_argument(self):
+        self.app.post('/1?_method=put')
+        assert self.args[0] == self.root.put
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == ['1']
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {'_method': 'put'}
+
+    def test_put_with_varargs(self):
+        self.app.put('/1/2/3')
+        assert self.args[0] == self.root.put
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == ['1']
+        assert self.args[1].varargs == ['2', '3']
+        assert kwargs(self.args[1]) == {}
+
+    def test_put_with_kwargs(self):
+        self.app.put('/1?foo=bar')
+        assert self.args[0] == self.root.put
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == ['1']
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {'foo': 'bar'}
+
+    def test_delete(self):
+        self.app.delete('/1')
+        assert self.args[0] == self.root.delete
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == ['1']
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {}
+
+    def test_delete_with_method_argument(self):
+        self.app.post('/1?_method=delete')
+        assert self.args[0] == self.root.delete
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == ['1']
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {'_method': 'delete'}
+
+    def test_delete_with_varargs(self):
+        self.app.delete('/1/2/3')
+        assert self.args[0] == self.root.delete
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == ['1']
+        assert self.args[1].varargs == ['2', '3']
+        assert kwargs(self.args[1]) == {}
+
+    def test_delete_with_kwargs(self):
+        self.app.delete('/1?foo=bar')
+        assert self.args[0] == self.root.delete
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == ['1']
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {'foo': 'bar'}
+
+    def test_post_with_invalid_method_kwarg(self):
+        self.app.post('/1?_method=invalid')
+        assert self.args[0] == self.root._default
+        assert isinstance(self.args[1], inspect.Arguments)
+        assert self.args[1].args == ['1']
+        assert self.args[1].varargs == []
+        assert kwargs(self.args[1]) == {'_method': 'invalid'}
+
+
 class TestTransactionHook(PecanTestCase):
     def test_transaction_hook(self):
         run_hook = []
@@ -1293,3 +1656,43 @@
         viewer = RequestViewerHook(conf)
 
         assert viewer.items == ['url']
+
+
+class TestRestControllerWithHooks(PecanTestCase):
+
+    def test_restcontroller_with_hooks(self):
+
+        class SomeHook(PecanHook):
+
+            def before(self, state):
+                state.response.headers['X-Testing'] = 'XYZ'
+
+        class BaseController(rest.RestController):
+
+            @expose()
+            def delete(self, _id):
+                return 'Deleting %s' % _id
+
+        class RootController(BaseController, HookController):
+
+            __hooks__ = [SomeHook()]
+
+            @expose()
+            def get_all(self):
+                return 'Hello, World!'
+
+        app = TestApp(
+            make_app(
+                RootController()
+            )
+        )
+
+        response = app.get('/')
+        assert response.status_int == 200
+        assert response.body == b_('Hello, World!')
+        assert response.headers['X-Testing'] == 'XYZ'
+
+        response = app.delete('/100/')
+        assert response.status_int == 200
+        assert response.body == b_('Deleting 100')
+        assert response.headers['X-Testing'] == 'XYZ'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan/tests/test_rest.py 
new/pecan-0.7.0/pecan/tests/test_rest.py
--- old/pecan-0.6.1/pecan/tests/test_rest.py    2014-07-10 17:33:20.000000000 
+0200
+++ new/pecan-0.7.0/pecan/tests/test_rest.py    2014-08-29 14:51:05.000000000 
+0200
@@ -7,7 +7,7 @@
 
 from six import b as b_
 
-from pecan import abort, expose, make_app, response
+from pecan import abort, expose, make_app, response, redirect
 from pecan.rest import RestController
 from pecan.tests import PecanTestCase
 
@@ -681,6 +681,117 @@
         assert r.status_int == 200
         assert len(loads(r.body.decode())['items']) == 1
 
+    def test_nested_get_all(self):
+
+        class BarsController(RestController):
+
+            @expose()
+            def get_one(self, foo_id, id):
+                return '4'
+
+            @expose()
+            def get_all(self, foo_id):
+                return '3'
+
+        class FoosController(RestController):
+
+            bars = BarsController()
+
+            @expose()
+            def get_one(self, id):
+                return '2'
+
+            @expose()
+            def get_all(self):
+                return '1'
+
+        class RootController(object):
+            foos = FoosController()
+
+        # create the app
+        app = TestApp(make_app(RootController()))
+
+        r = app.get('/foos/')
+        assert r.status_int == 200
+        assert r.body == b_('1')
+
+        r = app.get('/foos/1/')
+        assert r.status_int == 200
+        assert r.body == b_('2')
+
+        r = app.get('/foos/1/bars/')
+        assert r.status_int == 200
+        assert r.body == b_('3')
+
+        r = app.get('/foos/1/bars/2/')
+        assert r.status_int == 200
+        assert r.body == b_('4')
+
+        r = app.get('/foos/bars/', status=400)
+        assert r.status_int == 400
+
+        r = app.get('/foos/bars/1', status=400)
+        assert r.status_int == 400
+
+    def test_nested_get_all_with_lookup(self):
+
+        class BarsController(RestController):
+
+            @expose()
+            def get_one(self, foo_id, id):
+                return '4'
+
+            @expose()
+            def get_all(self, foo_id):
+                return '3'
+
+            @expose('json')
+            def _lookup(self, id, *remainder):
+                redirect('/lookup-hit/')
+
+        class FoosController(RestController):
+
+            bars = BarsController()
+
+            @expose()
+            def get_one(self, id):
+                return '2'
+
+            @expose()
+            def get_all(self):
+                return '1'
+
+        class RootController(object):
+            foos = FoosController()
+
+        # create the app
+        app = TestApp(make_app(RootController()))
+
+        r = app.get('/foos/')
+        assert r.status_int == 200
+        assert r.body == b_('1')
+
+        r = app.get('/foos/1/')
+        assert r.status_int == 200
+        assert r.body == b_('2')
+
+        r = app.get('/foos/1/bars/')
+        assert r.status_int == 200
+        assert r.body == b_('3')
+
+        r = app.get('/foos/1/bars/2/')
+        assert r.status_int == 200
+        assert r.body == b_('4')
+
+        r = app.get('/foos/bars/', status=400)
+        assert r.status_int == 400
+
+        r = app.get('/foos/bars/', status=400)
+
+        r = app.get('/foos/bars/1')
+        assert r.status_int == 302
+        assert r.headers['Location'].endswith('/lookup-hit/')
+
     def test_bad_rest(self):
 
         class ThingsController(RestController):
@@ -773,16 +884,16 @@
 
         # test get_all
         r = app.get('/foos')
-        assert r.status_int == 200
-        assert r.body == b_(dumps(dict(items=FoosController.data)))
+        self.assertEqual(r.status_int, 200)
+        self.assertEqual(r.body, b_(dumps(dict(items=FoosController.data))))
 
         # test nested get_all
         r = app.get('/foos/1/bars')
-        assert r.status_int == 200
-        assert r.body == b_(dumps(dict(items=BarsController.data[1])))
+        self.assertEqual(r.status_int, 200)
+        self.assertEqual(r.body, b_(dumps(dict(items=BarsController.data[1]))))
 
         r = app.get('/foos/bars', expect_errors=True)
-        assert r.status_int == 404
+        self.assertEqual(r.status_int, 400)
 
     def test_custom_with_trailing_slash(self):
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan.egg-info/PKG-INFO 
new/pecan-0.7.0/pecan.egg-info/PKG-INFO
--- old/pecan-0.6.1/pecan.egg-info/PKG-INFO     2014-07-10 17:33:30.000000000 
+0200
+++ new/pecan-0.7.0/pecan.egg-info/PKG-INFO     2014-08-29 14:51:16.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pecan
-Version: 0.6.1
+Version: 0.7.0
 Summary: A WSGI object-dispatching web framework, designed to be lean and 
fast, with few dependancies.
 Home-page: http://github.com/stackforge/pecan
 Author: Jonathan LaCour
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/pecan.egg-info/SOURCES.txt 
new/pecan-0.7.0/pecan.egg-info/SOURCES.txt
--- old/pecan-0.6.1/pecan.egg-info/SOURCES.txt  2014-07-10 17:33:30.000000000 
+0200
+++ new/pecan-0.7.0/pecan.egg-info/SOURCES.txt  2014-08-29 14:51:16.000000000 
+0200
@@ -91,10 +91,40 @@
 pecan/tests/test_scaffolds.py
 pecan/tests/test_secure.py
 pecan/tests/test_templating.py
+pecan/tests/config_fixtures/config.py
+pecan/tests/config_fixtures/empty.py
+pecan/tests/config_fixtures/foobar.py
+pecan/tests/config_fixtures/forcedict.py
+pecan/tests/config_fixtures/bad/importerror.py
+pecan/tests/config_fixtures/bad/module_and_underscore.py
 pecan/tests/middleware/__init__.py
 pecan/tests/middleware/test_debug.py
 pecan/tests/middleware/test_errordocument.py
 pecan/tests/middleware/test_recursive.py
 pecan/tests/middleware/test_static.py
+pecan/tests/middleware/static_fixtures/self.png
+pecan/tests/middleware/static_fixtures/text.txt
 pecan/tests/scaffold_fixtures/__init__.py
-pecan/tests/templates/__init__.py
\ No newline at end of file
+pecan/tests/scaffold_fixtures/content_sub/foo_tmpl
+pecan/tests/scaffold_fixtures/content_sub/bar/spam.txt_tmpl
+pecan/tests/scaffold_fixtures/file_sub/foo_+package+
+pecan/tests/scaffold_fixtures/file_sub/bar_+package+/spam.txt
+pecan/tests/scaffold_fixtures/simple/foo
+pecan/tests/scaffold_fixtures/simple/bar/spam.txt
+pecan/tests/templates/__init__.py
+pecan/tests/templates/form_colors.html
+pecan/tests/templates/form_colors_invalid.html
+pecan/tests/templates/form_colors_valid.html
+pecan/tests/templates/form_login_invalid.html
+pecan/tests/templates/form_login_valid.html
+pecan/tests/templates/form_name.html
+pecan/tests/templates/form_name_invalid.html
+pecan/tests/templates/form_name_invalid_custom.html
+pecan/tests/templates/form_name_valid.html
+pecan/tests/templates/genshi.html
+pecan/tests/templates/genshi_bad.html
+pecan/tests/templates/jinja.html
+pecan/tests/templates/jinja_bad.html
+pecan/tests/templates/kajiki.html
+pecan/tests/templates/mako.html
+pecan/tests/templates/mako_bad.html
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/setup.cfg new/pecan-0.7.0/setup.cfg
--- old/pecan-0.6.1/setup.cfg   2014-07-10 17:33:30.000000000 +0200
+++ new/pecan-0.7.0/setup.cfg   2014-08-29 14:51:16.000000000 +0200
@@ -6,10 +6,10 @@
 cover-erase = 1
 
 [pytest]
-norecursedirs = +package+
+norecursedirs = +package+ config_fixtures docs .git *.egg .tox
 
 [egg_info]
-tag_build = 
-tag_date = 0
 tag_svn_revision = 0
+tag_date = 0
+tag_build = 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pecan-0.6.1/setup.py new/pecan-0.7.0/setup.py
--- old/pecan-0.6.1/setup.py    2014-07-10 17:33:21.000000000 +0200
+++ new/pecan-0.7.0/setup.py    2014-08-29 14:51:06.000000000 +0200
@@ -3,7 +3,7 @@
 
 from setuptools import setup, find_packages
 
-version = '0.6.1'
+version = '0.7.0'
 
 #
 # determine requirements

-- 
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to