Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

fixed handling of various request methods. updated tests.

  • Loading branch information...
commit 3be238364c0a4c0c50b89e0377e942c3b4cfe614 1 parent a43b38f
Yeeland Chen authored
Showing with 116 additions and 83 deletions.
  1. +72 −63 pyramid_routehelper/__init__.py
  2. +44 −20 pyramid_routehelper/tests.py
View
135 pyramid_routehelper/__init__.py
@@ -1,14 +1,10 @@
-from pyramid_handlers import add_handler
from pyramid.config import ConfigurationError
+import inspect
-__all__ = ['includeme', 'add_resource']
+__all__ = ['includeme', 'add_resource', 'action']
def includeme(config):
- if getattr(config, 'add_handler', None):
- config.add_directive('add_resource', add_resource)
- else:
- raise ConfigurationError("add_handler needs to be added before add_resource can be used")
-
+ config.add_directive('add_resource', add_resource)
def strip_slashes(name):
"""Remove slashes from the beginning and end of a part/URL."""
@@ -18,6 +14,31 @@ def strip_slashes(name):
name = name[:-1]
return name
+class action(object):
+ """Decorate a method for registration by
+ :func:`~pyramid_routehelper.add_resource`.
+
+ Keyword arguments are identical to :class:`~pyramid.view.view_config`, with
+ the exception to how the ``name`` argument is used.
+
+ ``alt_for``
+ Designate a method as another view for the specified action if
+ the decorated method is not the desired action name instead of registering
+ the method with an action of the same name.
+
+ ``format``
+ Specify a format for the view that this decorator describes.
+ """
+ def __init__(self, **kw):
+ self.kw = kw
+
+ def __call__(self, wrapped):
+ if hasattr(wrapped, '__exposed__'):
+ wrapped.__exposed__.append(self.kw)
+ else:
+ wrapped.__exposed__ = [self.kw]
+ return wrapped
+
# map.resource port
def add_resource(self, handler, member_name, collection_name, **kwargs):
""" Add some RESTful routes for a resource handler.
@@ -145,7 +166,26 @@ def add_resource(self, handler, member_name, collection_name, **kwargs):
>>> route_path('locations', region_id=51)
'/regions/51/locations'
"""
+ handler = self.maybe_dotted(handler)
+ action_kwargs = {}
+ for name,meth in inspect.getmembers(handler, inspect.ismethod):
+ if hasattr(meth, '__exposed__'):
+ for settings in meth.__exposed__:
+ config_settings = settings.copy()
+ action_name = config_settings.pop('alt_for', name)
+
+ # If format is not set, use the route that doesn't specify a format
+ if 'format' not in config_settings:
+ if 'default' in action_kwargs.get(action_name,{}):
+ raise ConfigurationError("Two methods have been decorated without specifying a format.")
+ else:
+ action_kwargs.setdefault(action_name, {})['default'] = config_settings
+ # Otherwise, append to the list of view config settings for formatted views
+ else:
+ config_settings['attr'] = name
+ action_kwargs.setdefault(action_name, {}).setdefault('formatted',[]).append(config_settings)
+
collection = kwargs.pop('collection', {})
member = kwargs.pop('member', {})
new = kwargs.pop('new', {})
@@ -188,86 +228,55 @@ def swap(dct, newdct):
collection_path = path
new_path = path + '/new'
member_path = path + '/:id'
-
- options = dict(
- handler = handler
- )
-
- def requirements_for(meth):
- opts = options.copy()
- if method != 'any':
- opts['request_method'] = meth.upper()
- return opts
+
+ def add_route_and_view(self, action, route_name, path, request_method='any'):
+ if request_method != 'any':
+ request_method = request_method.upper()
+ else:
+ request_method = None
+
+ self.add_route(route_name, path, **kwargs)
+ self.add_view(view=handler, attr=action, route_name=route_name, request_method=request_method, **action_kwargs.get(action, {}).get('default', {}))
+
+ for format_kwargs in action_kwargs.get(action, {}).get('formatted', []):
+ format = format_kwargs.pop('format')
+ self.add_route("%s_formatted_%s" % (format, route_name),
+ "%s.%s" % (path, format), **kwargs)
+ self.add_view(view=handler, attr=format_kwargs.pop('attr'), request_method=request_method,
+ route_name = "%s_formatted_%s" % (format, route_name), **format_kwargs)
for method, lst in collection_methods.iteritems():
primary = (method != 'GET' and lst.pop(0)) or None
- route_options = requirements_for(method)
for action in lst:
- route_options['action'] = action
- route_name = "%s%s_%s" % (name_prefix, action, collection_name)
-
- # Connect paths
- self.add_handler("formatted_" + route_name,
- "%s/%s.:format" % (collection_path, action),
- **route_options)
- self.add_handler(route_name,
- "%s/%s" % (collection_path, action),
- **route_options)
+ add_route_and_view(self, action, "%s%s_%s" % (name_prefix, action, collection_name), "%s/%s" % (collection_path,action))
if primary:
- route_options['action'] = primary
- self.add_handler("formatted_%s" % collection_name, "%s.:format" % collection_path, **route_options)
- self.add_handler(collection_name, collection_path, **route_options)
-
- self.add_handler("formatted_" + name_prefix + collection_name,
- collection_path + ".:format",
- handler,
- action='index',
- request_method='GET')
- self.add_handler(name_prefix + collection_name,
- collection_path,
- handler,
- action='index',
- request_method='GET')
+ add_route_and_view(self, primary, name_prefix + collection_name, collection_path, method)
+
+ # Add route and view for collection
+ add_route_and_view(self, 'index', name_prefix + collection_name, collection_path, 'GET')
for method, lst in new_methods.iteritems():
- route_options = requirements_for(method)
for action in lst:
path = (action == 'new' and new_path) or "%s/%s" % (new_path, action)
name = "new_" + member_name
if action != 'new':
name = action + "_" + name
- route_options['action'] = action
formatted_path = (action == 'new' and new_path + '.:format') or "%s/%s.:format" % (new_path, action)
- self.add_handler('formatted_' + name_prefix + name, formatted_path, **route_options)
- self.add_handler(name_prefix + name, path, **route_options)
-
- requirements_regexp = '[^\/]+'
+ add_route_and_view(self, action, name_prefix + name, path, method)
for method, lst in member_methods.iteritems():
- route_options = requirements_for(method)
if method not in ['POST', 'GET', 'any']:
primary = lst.pop(0)
else:
primary = None
for action in lst:
- route_options['action'] = action
- self.add_handler('formatted_%s%s_%s' % (name_prefix, action, member_name),
- '%s/%s.:format' % (member_path, action), **route_options)
- self.add_handler('%s%s_%s' % (name_prefix, action, member_name),
- '%s/%s' % (member_path, action), **route_options)
+ add_route_and_view(self, action, '%s%s_%s' % (name_prefix, action, member_name), '%s/%s' % (member_path, action))
if primary:
- route_options['action'] = primary
- self.add_handler("formatted_" + member_name,
- "%s.:format" % member_path, **route_options)
- self.add_handler(member_name, member_path, **route_options)
+ add_route_and_view(self, primary, name_prefix + member_name, member_path, method)
- route_options = requirements_for('GET')
- route_options['action'] = 'show'
- self.add_handler('formatted_' + name_prefix + member_name,
- member_path + ".:format", **route_options)
- self.add_handler(name_prefix + member_name, member_path, **route_options)
+ add_route_and_view(self, 'show', name_prefix + member_name, member_path, method)
# Submapper support
View
64 pyramid_routehelper/tests.py
@@ -1,16 +1,14 @@
import unittest
from pyramid import testing
-from pyramid_handlers import includeme as handlers_includeme, action
from pyramid.config import Configurator
-from pyramid_routehelper import includeme as routehelper_includeme, add_resource
+from pyramid_routehelper import includeme, add_resource, action, ConfigurationError
from pyramid.url import route_path
class TestResourceGeneration_add_resource(unittest.TestCase):
def _create_config(self, autocommit=True):
config = Configurator(autocommit=autocommit)
- handlers_includeme(config)
- routehelper_includeme(config)
+ includeme(config)
return config
def setUp(self):
@@ -25,39 +23,39 @@ def test_basic_resources(self):
self.config.add_resource('pyramid_routehelper.tests:DummyCrudHandler', 'message', 'messages')
assert route_path('messages', testing.DummyRequest()) == '/messages'
- assert route_path('formatted_messages', testing.DummyRequest(), format='html') == '/messages.html'
+ assert route_path('json_formatted_messages', testing.DummyRequest()) == '/messages.json'
assert route_path('new_message', testing.DummyRequest()) == '/messages/new'
- assert route_path('formatted_new_message', testing.DummyRequest(), format='html') == '/messages/new.html'
+ assert route_path('json_formatted_new_message', testing.DummyRequest()) == '/messages/new.json'
- assert route_path('formatted_message', testing.DummyRequest(), id=1, format='html') == '/messages/1.html'
+ assert route_path('json_formatted_message', testing.DummyRequest(), id=1) == '/messages/1.json'
assert route_path('message', testing.DummyRequest(), id=1) == '/messages/1'
- assert route_path('formatted_edit_message', testing.DummyRequest(), id=1, format='html') == '/messages/1/edit.html'
+ assert route_path('json_formatted_edit_message', testing.DummyRequest(), id=1) == '/messages/1/edit.json'
assert route_path('edit_message', testing.DummyRequest(), id=1) == '/messages/1/edit'
def test_resources_with_path_prefix(self):
self.config.add_resource('pyramid_routehelper.tests:DummyCrudHandler', 'message', 'messages', path_prefix='/category/:category_id')
assert route_path('messages', testing.DummyRequest(), category_id=2) == '/category/2/messages'
- assert route_path('formatted_messages', testing.DummyRequest(), format='html', category_id=2) == '/category/2/messages.html'
+ assert route_path('json_formatted_messages', testing.DummyRequest(), category_id=2) == '/category/2/messages.json'
assert route_path('new_message', testing.DummyRequest(), category_id=2) == '/category/2/messages/new'
- assert route_path('formatted_new_message', testing.DummyRequest(), format='html', category_id=2) == '/category/2/messages/new.html'
+ assert route_path('json_formatted_new_message', testing.DummyRequest(), category_id=2) == '/category/2/messages/new.json'
- assert route_path('formatted_message', testing.DummyRequest(), id=1, format='html', category_id=2) == '/category/2/messages/1.html'
+ assert route_path('json_formatted_message', testing.DummyRequest(), id=1, category_id=2) == '/category/2/messages/1.json'
assert route_path('message', testing.DummyRequest(), id=1, category_id=2) == '/category/2/messages/1'
- assert route_path('formatted_edit_message', testing.DummyRequest(), id=1, format='html', category_id=2) == '/category/2/messages/1/edit.html'
+ assert route_path('json_formatted_edit_message', testing.DummyRequest(), id=1, category_id=2) == '/category/2/messages/1/edit.json'
assert route_path('edit_message', testing.DummyRequest(), id=1, category_id=2) == '/category/2/messages/1/edit'
def test_resources_with_path_prefix_with_trailing_slash(self):
self.config.add_resource('pyramid_routehelper.tests:DummyCrudHandler', 'message', 'messages', path_prefix='/category/:category_id/')
assert route_path('messages', testing.DummyRequest(), category_id=2) == '/category/2/messages'
- assert route_path('formatted_messages', testing.DummyRequest(), format='html', category_id=2) == '/category/2/messages.html'
+ assert route_path('json_formatted_messages', testing.DummyRequest(), category_id=2) == '/category/2/messages.json'
assert route_path('new_message', testing.DummyRequest(), category_id=2) == '/category/2/messages/new'
- assert route_path('formatted_new_message', testing.DummyRequest(), format='html', category_id=2) == '/category/2/messages/new.html'
+ assert route_path('json_formatted_new_message', testing.DummyRequest(), category_id=2) == '/category/2/messages/new.json'
- assert route_path('formatted_message', testing.DummyRequest(), id=1, format='html', category_id=2) == '/category/2/messages/1.html'
+ assert route_path('json_formatted_message', testing.DummyRequest(), id=1, category_id=2) == '/category/2/messages/1.json'
assert route_path('message', testing.DummyRequest(), id=1, category_id=2) == '/category/2/messages/1'
- assert route_path('formatted_edit_message', testing.DummyRequest(), id=1, format='html', category_id=2) == '/category/2/messages/1/edit.html'
+ assert route_path('json_formatted_edit_message', testing.DummyRequest(), id=1, category_id=2) == '/category/2/messages/1/edit.json'
assert route_path('edit_message', testing.DummyRequest(), id=1, category_id=2) == '/category/2/messages/1/edit'
def test_resources_with_collection_action(self):
@@ -105,12 +103,23 @@ def test_resources_with_parent_resource_override_name_prefix(self):
assert route_path('messages', testing.DummyRequest(), category_id=2) == '/categories/2/messages'
assert route_path('message', testing.DummyRequest(), category_id=2, id=1) == '/categories/2/messages/1'
+
+ def test_resources_with_double_default_views(self):
+ class MessedUpHandler(object):
+ @action(renderer='json')
+ @action(renderer='template.mak')
+ def index(self):
+ return {}
+
+ try:
+ self.config.add_resource(MessedUpHandler, 'message', 'messages')
+ except ConfigurationError, e:
+ assert str(e) == "Two methods have been decorated without specifying a format."
class TestResourceRecognition(unittest.TestCase):
def _create_config(self, autocommit=True):
config = Configurator(autocommit=autocommit)
- handlers_includeme(config)
- routehelper_includeme(config)
+ includeme(config)
return config
def setUp(self):
@@ -153,6 +162,10 @@ def test_get_collection(self):
result = self._get('/messages')
assert result == '"index"'
+ def test_get_formatted_collection(self):
+ result = self._get('/messages.json')
+ assert result == '{"format": "json"}'
+
def test_post_collection(self):
result = self._post('/messages')
assert result == '"create"'
@@ -180,8 +193,7 @@ def test_edit_member(self):
class Test_includeme(unittest.TestCase):
def test_includme(self):
config = Configurator(autocommit=True)
- handlers_includeme(config)
- routehelper_includeme(config)
+ includeme(config)
assert config.add_resource.im_func.__docobj__ is add_resource
class DummyCrudHandler(object):
@@ -192,10 +204,16 @@ def __init__(self, request):
def index(self):
return "index"
+ @action(alt_for='index', renderer='xml', xhr=True, format='xml')
+ @action(alt_for='index', renderer='json', format='json')
+ def api_index(self):
+ return {'format':'json'}
+
@action(renderer='json')
def create(self):
return "create"
+ @action(renderer='json', format='json')
@action(renderer='json')
def show(self):
return "show"
@@ -208,10 +226,16 @@ def update(self):
def delete(self):
return "delete"
+ @action(renderer='json', format='json')
@action(renderer='json')
def new(self):
return "new"
+ @action(renderer='json', format='json')
@action(renderer='json')
def edit(self):
return "edit"
+
+ @action(renderer='json')
+ def sorted(self):
+ return "sorted"
Please sign in to comment.
Something went wrong with that request. Please try again.