Contents

Decorating a Pyramid view function

Imagine you have some view functions in Pyramid and want to decorate them with your decorator, something like this:

1
2
3
4
5
6
7
@my_decorator
def get_users(request):  # using only request
    ...

@my_decorator
def delete_user(context, request):  # using context and request
    ...

Notice the difference in the signatures of the two functions. In the first one we pass only the request argument while in the second one we pass both the context and request arguments.

From Pyramid docs:

Usually view callables are defined to accept only a single argument: request. However, a view callable may alternately be defined as any class, function, or callable that accepts two positional arguments: a context resource as the first argument and a request as the second argument.

Using pure Python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from functools import wraps
from inspect import getfullargspec

def my_decorator(func):
    @wraps
    def wrapper(context, request):
        # do stuff before..

        func_args = getfullargspec(func).args
        if len(func_args) == 1:
            response = func(request)
        else:
            response = func(context, request)

        # do stuff after..

        return response
    return wrapper

There is one caveat though, notice the if/else statement. We need to inspect the signature of the view function to determine if it uses either both the context and the request arguments or only the request argument.

Of course Pyramid provides ways to decorate a view function without the need of inspecting it.

Using the decorator keyword argument when configuring the view

1
2
3
4
5
6
7
@view_config(decorator=my_decorator)
def get_users(request):  # using only request
    ...

@view_config(decorator=my_decorator)
def delete_user(context, request):  # using context and request
    ...

..and of course removing the inspection code as there is no need for it any more but we need to accept in the wrapper both the context and request as well as passing them again when we call the view:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from functools import wraps

def my_decorator(func):
    @wraps
    def wrapper(context, request):

        # do stuff before..
        response = func(context, request)
        # do stuff after..

        return response
    return wrapper

Dropping in python’s pdb built-in module just before we return the call of the view function we see that we wrap a function called: render_view.<locals>.viewresult_to_response

1
2
3
4
5
6
7
8
(Pdb)
  9         def wrapper(context, request):
  10
  11             # do stuff here
  12             import pdb; pdb.set_trace()
  13  ->         response = func(context, request)
(Pdb) func
<function rendered_view.<locals>.viewresult_to_response at 0x7f686d44dbf8>

Wait, this is not any of our functions!

The reason for defining the wrapper with both arguments context, request, is that we do not wrap the actual view function but something earlier in the process of calling the actual view. That makes sense because something has to do the inspection before calling the actual view and this is not us but Pyramid of course.

Doing the same with the pure python way we get: <function get_users at 0x7f5a9e452ea0>, which is the actual view function.

More on decorators.

There is also a third way, by creating a custom Pyramid view deriver

A view deriver avoids the need of importing the decorator in places that needs to be used and makes it more declarative.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def my_deriver(func, info):
    if info.options.get('my_decorator'):  # not all views will use it
        def wrapper(context, request):

            # do stuff before..
            response = func(context, request)
            # do stuff after..

            return response
        return wrapper
    return func

# Make `my_decorator` a valid keyword argument in `view_config` so
# it doesn't get applied in all views
my_deriver.options = ('my_decorator', )

Again we have to pass both context and request, Pyramid will pass only the needed arguments, so no need for inspecting the function.

1
2
3
4
5
6
7
@view_config(my_decorator=True)  # Deriver will be applied
def get_users(request):
    ...

@view_config()  # Deriver will not be applied
def delete_user(context, request):
    ...

More on view derivers.

Big thanks to raydeo and the rest of the #pyramid members who are always helpful!