Find file
Fetching contributors…
Cannot retrieve contributors at this time
295 lines (231 sloc) 12 KB
from pyramid.config import ConfigurationError
import inspect
__all__ = ['includeme', 'add_resource', 'action']
def includeme(config):
config.add_directive('add_resource', add_resource)
def strip_slashes(name):
"""Remove slashes from the beginning and end of a part/URL."""
if name.startswith('/'):
name = name[1:]
if name.endswith('/'):
name = name[:-1]
return name
class action(object):
"""Decorate a method for registration by
Keyword arguments are identical to :class:`~pyramid.view.view_config`, with
the exception to how the ``name`` argument is used.
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.
Specify a format for the view that this decorator describes.
def __init__(self, **kw): = kw
def __call__(self, wrapped):
if hasattr(wrapped, '__exposed__'):
wrapped.__exposed__ = []
return wrapped
# map.resource port
def add_resource(self, handler, member_name, collection_name, **kwargs):
""" Add some RESTful routes for a resource handler.
This function should never be called directly; instead the
``pyramid_routehelper.includeme`` function should be used to include this
function into an application; the function will thereafter be available
as a method of the resulting configurator.
The concept of a web resource maps somewhat directly to 'CRUD'
operations. The overlying things to keep in mind is that
adding a resource handler is about handling creating, viewing, and
editing that resource.
``handler`` is a dotted name of (or direct reference to) a
Python handler class,
e.g. ``'my.package.handlers.MyHandler'``.
``member_name`` should be the appropriate singular version of the resource
given your locale and used with members of the collection.
``collection_name`` will be used to refer to the resource collection methods
and should be a plural version of the member_name argument.
All keyword arguments are optional.
Additional action mappings used to manipulate/view the
entire set of resources provided by the handler.
config.add_resource('myproject.handlers:MessageHandler', 'message', 'messages', collection={'rss':'GET'})
# GET /messages/rss (maps to the rss action)
# also adds named route "rss_message"
Additional action mappings used to access an individual
'member' of this handler's resources.
config.add_resource('myproject.handlers:MessageHandler', 'message', 'messages', member={'mark':'POST'})
# POST /messages/1/mark (maps to the mark action)
# also adds named route "mark_message"
Action mappings that involve dealing with a new member in
the controller resources.
config.add_resource('myproject.handlers:MessageHandler', 'message', 'messages', new={'preview':'POST'})
# POST /messages/new/preview (maps to the preview action)
# also adds a url named "preview_new_message"
Prepends the URL path for the Route with the path_prefix
given. This is most useful for cases where you want to mix
resources or relations between resources.
Perpends the route names that are generated with the
name_prefix given. Combined with the path_prefix option,
it's easy to generate route names and paths that represent
resources that are in relations.
config.add_resource('myproject.handlers:CategoryHandler', 'message', 'messages',
# GET /category/7/messages/1
# has named route "category_message"
A ``dict`` containing information about the parent
resource, for creating a nested resource. It should contain
the ``member_name`` and ``collection_name`` of the parent
If ``parent_resource`` is supplied and ``path_prefix``
isn't, ``path_prefix`` will be generated from
``parent_resource`` as
"<parent collection name>/:<parent member name>_id".
If ``parent_resource`` is supplied and ``name_prefix``
isn't, ``name_prefix`` will be generated from
``parent_resource`` as "<parent member name>_".
>>> from pyramid.url import route_path
>>> config.add_resource('myproject.handlers:LocationHandler', 'location', 'locations',
... parent_resource=dict(member_name='region',
... collection_name='regions'))
>>> # path_prefix is "regions/:region_id"
>>> # name prefix is "region_"
>>> route_path('region_locations', region_id=13)
>>> route_path('region_new_location', region_id=13)
>>> route_path('region_location', region_id=13, id=60)
>>> route_path('region_edit_location', region_id=13, id=60)
Overriding generated ``path_prefix``::
>>> config.add_resource('myproject.handlers:LocationHandler', 'location', 'locations',
... parent_resource=dict(member_name='region',
... collection_name='regions'),
... path_prefix='areas/:area_id')
>>> # name prefix is "region_"
>>> route_path('region_locations', area_id=51)
Overriding generated ``name_prefix``::
>>> config.add_resource('myproject.handlers:LocationHandler', 'location', 'locations',
... parent_resource=dict(member_name='region',
... collection_name='regions'),
... name_prefix='')
>>> # path_prefix is "regions/:region_id"
>>> route_path('locations', region_id=51)
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.")
action_kwargs.setdefault(action_name, {})['default'] = config_settings
# Otherwise, append to the list of view config settings for formatted views
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', {})
path_prefix = kwargs.pop('path_prefix', None)
name_prefix = kwargs.pop('name_prefix', None)
parent_resource = kwargs.pop('parent_resource', None)
if parent_resource is not None:
if path_prefix is None:
path_prefix = '%s/:%s_id' % (parent_resource['collection_name'], parent_resource['member_name'])
if name_prefix is None:
name_prefix = '%s_' % parent_resource['member_name']
if path_prefix is None: path_prefix = ''
if name_prefix is None: name_prefix = ''
member['edit'] = 'GET'
new['new'] = 'GET'
def swap(dct, newdct):
map(lambda (key,value): newdct.setdefault(value.upper(), []).append(key), dct.items())
return newdct
collection_methods = swap(collection, {})
member_methods = swap(member, {})
new_methods = swap(new, {})
collection_methods.setdefault('POST', []).insert(0, 'create')
member_methods.setdefault('PUT', []).insert(0, 'update')
member_methods.setdefault('DELETE', []).insert(0, 'delete')
# Continue porting code
controller = strip_slashes(collection_name)
path_prefix = strip_slashes(path_prefix)
path_prefix = '/' + path_prefix
if path_prefix and path_prefix != '/':
path = path_prefix + '/' + controller
path = '/' + controller
collection_path = path
new_path = path + '/new'
member_path = path + '/:id'
added_route_names = {}
def add_route_if_new(self, route_name, path, **kwargs):
if route_name not in added_route_names:
self.add_route(route_name, path, **kwargs)
added_route_names[route_name] = path
def add_route_and_view(self, action, route_name, path, request_method='any'):
if request_method != 'any':
request_method = request_method.upper()
request_method = None
add_route_if_new(self, 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')
formatted_route_name = "%s_formatted_%s" % (format, route_name)
add_route_if_new(self, formatted_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
for action in lst:
add_route_and_view(self, action, "%s%s_%s" % (name_prefix, action, collection_name), "%s/%s" % (collection_path,action))
if primary:
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():
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
formatted_path = (action == 'new' and new_path + '.:format') or "%s/%s.:format" % (new_path, action)
add_route_and_view(self, action, name_prefix + name, path, method)
for method, lst in member_methods.iteritems():
if method not in ['POST', 'GET', 'any']:
primary = lst.pop(0)
primary = None
for action in lst:
add_route_and_view(self, action, '%s%s_%s' % (name_prefix, action, member_name), '%s/%s' % (member_path, action))
if primary:
add_route_and_view(self, primary, name_prefix + member_name, member_path, method)
add_route_and_view(self, 'show', name_prefix + member_name, member_path, method)
# Submapper support
# Sub_domain option
# Converters??