Using a View Mapper to Pass Query Parameters as Keyword Arguments

Pyramid supports a concept of a "view mapper". See Using a View Mapper for general information about view mappers. You can use a view mapper to support an alternate convenience calling convention in which you allow view callables to name extra required and optional arguments which are taken from the request.params dictionary. So, for example, instead of:

1@view_config()
2def aview(request):
3    name = request.params['name']
4    value = request.params.get('value', 'default')
5    ...

With a special view mapper you can define this as:

@view_config(mapper=MapplyViewMapper)
def aview(request, name, value='default'):
    ...

The below code implements the MapplyViewMapper. It works as a mapper for function view callables and method view callables:

  1import inspect
  2import sys
  3
  4from pyramid.view import view_config
  5from pyramid.response import Response
  6from pyramid.config import Configurator
  7from waitress import serve
  8
  9PY3 = sys.version_info[0] == 3
 10
 11if PY3:
 12    im_func = '__func__'
 13    func_defaults = '__defaults__'
 14    func_code = '__code__'
 15else:
 16    im_func = 'im_func'
 17    func_defaults = 'func_defaults'
 18    func_code = 'func_code'
 19
 20def mapply(ob, positional, keyword):
 21
 22    f = ob
 23    im = False
 24
 25    if hasattr(f, im_func):
 26        im = True
 27
 28    if im:
 29        f = getattr(f, im_func)
 30        c = getattr(f, func_code)
 31        defaults = getattr(f, func_defaults)
 32        names = c.co_varnames[1:c.co_argcount]
 33    else:
 34        defaults = getattr(f, func_defaults)
 35        c = getattr(f, func_code)
 36        names = c.co_varnames[:c.co_argcount]
 37
 38    nargs = len(names)
 39    args = []
 40    if positional:
 41        positional = list(positional)
 42        if len(positional) > nargs:
 43            raise TypeError('too many arguments')
 44        args = positional
 45
 46    get = keyword.get
 47    nrequired = len(names) - (len(defaults or ()))
 48    for index in range(len(args), len(names)):
 49        name = names[index]
 50        v = get(name, args)
 51        if v is args:
 52            if index < nrequired:
 53                raise TypeError('argument %s was omitted' % name)
 54            else:
 55                v = defaults[index-nrequired]
 56        args.append(v)
 57
 58    args = tuple(args)
 59    return ob(*args)
 60
 61
 62class MapplyViewMapper(object):
 63    def __init__(self, **kw):
 64        self.attr = kw.get('attr')
 65
 66    def __call__(self, view):
 67        def wrapper(context, request):
 68            keywords = dict(request.params.items())
 69            if inspect.isclass(view):
 70                inst = view(request)
 71                meth = getattr(inst, self.attr)
 72                response = mapply(meth, (), keywords)
 73            else:
 74                # it's a function
 75                response = mapply(view, (request,), keywords)
 76            return response
 77
 78        return wrapper
 79
 80@view_config(name='function', mapper=MapplyViewMapper)
 81def view_function(request, one, two=False):
 82    return Response('one: %s, two: %s' % (one, two))
 83
 84class ViewClass(object):
 85    __view_mapper__ = MapplyViewMapper
 86    def __init__(self, request):
 87        self.request = request
 88
 89    @view_config(name='method')
 90    def view_method(self, one, two=False):
 91        return Response('one: %s, two: %s' % (one, two))
 92
 93if __name__ == '__main__':
 94    config = Configurator()
 95    config.scan('.')
 96    app = config.make_wsgi_app()
 97    serve(app)
 98
 99# http://localhost:8080/function --> (exception; no "one" arg supplied)
100
101# http://localhost:8080/function?one=1 --> one: '1', two: False
102
103# http://localhost:8080/function?one=1&two=2 --> one: '1', two: '2'
104
105# http://localhost:8080/method --> (exception; no "one" arg supplied)
106
107# http://localhost:8080/method?one=1 --> one: '1', two: False
108
109# http://localhost:8080/method?one=1&two=2 --> one: '1', two: '2'