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 - dmueller@suse.com + +- 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"> +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"> +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: opensuse-commit+unsubscribe@opensuse.org For additional commands, e-mail: opensuse-commit+help@opensuse.org