Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

View lookup only checks the views registered for the first matched interface implemented by context #409

Closed
sergeyv opened this Issue Jan 20, 2012 · 10 comments

Comments

Projects
None yet
3 participants

sergeyv commented Jan 20, 2012

Basically, I have a resource tree, nodes of which can be either read-only, write-only, or read-write. Readable nodes implement IReader, writeable nodes implement IWriter and the read-write ones implement both interfaces.

The Pyramid documentation on view lookup states that

... All predicates must match for the associated view to be called.

This does not mean however, that Pyramid “stops looking” when it finds a view registration with 
predicates that don’t match. If one set of view predicates does not match, the “next most specific” 
view (if any) is consulted for predicates, and so on, until a view is found, or no view can be matched 
up with the request. The first view with a set of predicates all of which match the request environment 
will be invoked.

However, I'm finding that in case of interfaces, Pyramid seems to first find the first matched interface, then narrow the selection down for the views which match that interface only. If no views matched during that process, no further checking is made and a "predicate mismatch" error is raised.

Here's a simple application to illustrate the issue::

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from zope.interface import Interface, implements

class IReader(Interface):
    pass


class IWriter(Interface):
    pass


class RootResource(object):
    implements(IReader, IWriter)


def root_factory(request):
    return RootResource()


def read(context, request):
    return Response('<form action="" method="POST">'
       '<input type="submit" value="Send a POST request" />'
       '</form>'
    )


def write(context, request):
    return Response("WRITE! (going to fail)")


if __name__ == '__main__':
    config = Configurator(
        root_factory=root_factory,
    )
    config.add_view(read, context=IReader, request_method="GET")
    config.add_view(write, context=IWriter, request_method="POST")
    app = config.make_wsgi_app()
    server = make_server('0.0.0.0', 8080, app)
    server.serve_forever()

in this application, a GET request succeeds, but POST request results in a "predicate mismatch" error. However, if we swap the order of interface declarations:

class RootResource(object):
    implements(IWriter, IReader)

then the GET request will fail and the POST request will succeed.

I'm finding that the behaviour is the same for any other predicates specified in combination with context=ISomeInterface

Interestingly, "name" predicate seems to take precedence over "context", so the following registrations do work as expected:

config.add_view(read, context=IReader, name="read")
config.add_view(write, context=IWriter, name="write")
Owner

mcdonc commented Jan 20, 2012

This is true. Here's what's going on under the hood:

from zope.interface.registry import Components
from zope.interface import Interface
from zope.interface import implementer
from zope.interface import providedBy

class IReader(Interface):
    pass

class IWriter(Interface):
    pass

class IRequest(Interface):
    pass

class IView(Interface):
    pass

def read(context, request):
    pass

def write(context, request):
    pass

@implementer(IReader, IWriter)
class Resource1(object):
    pass

@implementer(IWriter, IReader)
class Resource2(object):
    pass

if __name__ == '__main__':
    c = Components()
    c.registerAdapter(read, (IRequest, IReader), IView)
    c.registerAdapter(write, (IRequest, IWriter), IView)

    resource1 = Resource1()
    p = providedBy(resource1)
    print c.adapters.lookup((IRequest, p), IView)

    resource2 = Resource2()
    p = providedBy(resource2)
    print c.adapters.lookup((IRequest, p), IView)

This will print:

<function read at 0x7f64b49aa410>
<function write at 0x7f64b49aa398>

The context predicate is actually more binding than any other predicate, because it's used to narrow the set of candidate views to a particular view or "multiview", which is actually where the other predicates are executed. This is mostly a performance optimization and, for better or worse, Zope heritage (Zope only allows you to look up a view based on its primary request interface, its primary context interface and a name; it has no other predicates).

To work around this, you might instead use containment= which works as a multiview predicate.

config.add_view(read, containment=IReader, request_method="GET")
config.add_view(write, containment=IWriter, request_method="POST")

Alternately, you could write a custom predicate that indeed checked the context. e.g.:

def contextpred(typ):
    def contextpred(context, request):
        return typ.providedBy(context)
    return contextpred

config.add_view(read, custom_predicates=(contextpred(IReader),), request_method="GET")
config.add_view(write, custom_predicates=(contextpred(IWriter),), request_method="POST")

@mcdonc mcdonc closed this Jan 20, 2012

Contributor

merwok commented Jun 14, 2013

I have an issue where views defined for some class are not matched for subclasses of said class. Could a kind soul tell me if inheritance is not supported in view dispatch, if it’s the same bug as this one (I’m on 1.4.2) or another one?

Owner

mcdonc commented Jun 14, 2013

By "defined for" do you mean you're registering a view configuration with a context of Foo and when the context is an instance that is a subclass of Foo, your view isn't called? The original question is a little vague.

Contributor

merwok commented Jun 14, 2013

Sorry for the lack or precision; you are right. I have Base and Inbox resources, some class-based views defined for Base using view_defaults and view_config, and they are not called for Inbox.

Contributor

merwok commented Jun 27, 2013

My issue is #1004. I’ll try to work around by using interfaces instead of base classes, given that #786 fixes this issue for interfaces.

Contributor

merwok commented Jun 27, 2013

Ah, I noticed that 1.4.2 does not contain #786. Is this planned for 1.5 or a 1.4.x release?

Owner

mcdonc commented Jun 27, 2013

1.5 almost certainly.

Contributor

merwok commented Jun 28, 2013

Is there a planned release date for this version?

Owner

mcdonc commented Jun 28, 2013

Not yet, sorry.

Contributor

merwok commented Jun 28, 2013

Okay, thanks for all the replies!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment