Inconsistent arguments passed to pyramid view function depending on wrapper signature

Jérôme :

I'm trying to understand the arguments that are passed to a pyramid view function.

The following example demonstrates a function wrapped with two different wrapppers. The only difference between the two wrappers is the signature. In the first wrapper, the first positional argument (obj) is explicit. In the second, it is included in *args.

import functools
from pyramid.config import Configurator
import webtest

def decorator_1(func):
    @functools.wraps(func)
    def wrapper(obj, *args, **kwargs):  # <- obj
        print('decorator_1')
        print(type(obj), obj)
        print(args)
        print(kwargs)
        return func(obj, *args, **kwargs)  # <- obj
    wrapper.__wrapped__ = func
    return wrapper

def decorator_2(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('decorator_2')
        print(args)
        print(kwargs)
        return func(*args, **kwargs)
    wrapper.__wrapped__ = func
    return wrapper

@decorator_1
def func_1(request):
    return {'func': 'func_1'}

@decorator_2
def func_2(request):
    return {'func': 'func_2'}

I would expect both wrapepd method to behave the same.

In decorator_1, I expect obj to be a request object and indeed it is.

In decorator_2, I would expect args[0] to be the same request object but it is not. It appears an additional first positional argument is passed before the request object.

def add_route(config, route, view, renderer="json"):
    """Helper for adding a new route-view pair."""
    route_name = view.__name__
    config.add_route(route_name, route)
    config.add_view(view, route_name=route_name, renderer=renderer)

config = Configurator()
add_route(config, "/func_1", func_1)
add_route(config, "/func_2", func_2)

app = config.make_wsgi_app()

testapp = webtest.TestApp(app)

testapp.get("/func_1")
testapp.get("/func_2")

Output:

decorator_1
<class 'pyramid.request.Request'> GET /func_1 HTTP/1.0
Host: localhost:80
()
{}
decorator_2
(<pyramid.traversal.DefaultRootFactory object at 0x7f981da2ee48>, <Request at 0x7f981da2ea20 GET http://localhost/func_2>)
{}

Consequently, func_2 crashes because it receives a DefaultRootFactory object it does not expect.

I'd like to understand this discrepancy. How come the signature of the wrapper changes what pyramid passes to the wrapped function?

There is a mechanism at stake I don't understand, and I suspect it might be in Pyramid's logic.

sirosen :

I shared my findings in the webargs issue where this came up, but just in case anyone comes across this here:

Pyramid lets you write a view function with either of these signatures

def view(request):
    ...
def view(context, request):
    ...

The second calling convention is the original one, and the first is newer. So even though it is called an "alternate" in the pyramid docs, it is the default.

They use inspect.getfullargspec to see if the view takes a single positional parameter, and if so, wrap it to match the second convention. If the view doesn't match the first convention, it is assumed to match the second convention (which is false in this case).

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=12452&siteId=1