Skip to content
New issue

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

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a hook between route lookup and traversal #1875

Closed
dstufft opened this issue Aug 23, 2015 · 15 comments
Closed

Provide a hook between route lookup and traversal #1875

dstufft opened this issue Aug 23, 2015 · 15 comments

Comments

@dstufft
Copy link
Contributor

dstufft commented Aug 23, 2015

Right now immediately after the route has been found the Router starts to do traversal. I would like to have a hook in between these events that would let me modify the request based on which route was select. My use case here is that I want to be able to select different transaction isolation levels on different routes, however you can only change the transaction isolation level on a database transaction before you've made any other queries, so if traversal (or the security framework) triggers any database queries then it's too late to change the level.

I'm not sure if a RouteFound event would let me modify the request or not, but I assume it would, would a PR like that be acceptable?

@digitalresistor
Copy link
Member

Not sure if you are using Context's, or if your Context is doing database transactions, but ContextFound exists and could possibly be used as a stop-gap measure.

@dstufft
Copy link
Contributor Author

dstufft commented Aug 23, 2015

I am using a Context and that is pulling from the database so ContextFound is too late.

@mmerickel
Copy link
Member

As @tseaver said in #1876 the root factory is the appropriate place for per-route configuration. I can only see this being useful over that option if you wanted to apply some global policy. Thoughts?

def ReadOnlyFactoryWrapper(factory):
    def wrapper(request):
        request.db.execute(
            """ SET TRANSACTION
                ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE
            """
        )
        return factory(request)
    return wrapper

config.add_route(..., factory=ReadOnlyFactoryWrapper(UserFactory))

@dstufft
Copy link
Contributor Author

dstufft commented Aug 24, 2015

@mmerickel I guess if I wanted to use it on something that didn't have a factory set I would just do something like:

from pyramid.traversal import DefaultRootFactory

config.add_route(..., factory=ReadOnlyFactoryWrapper(DefaultRootFactory))

That's certainly doable, though it feels uglier than being able to use a predicate for it. If that's the correct way of doing it I can certainly do that though!

@dstufft
Copy link
Contributor Author

dstufft commented Aug 24, 2015

Although, that assumes that a different default root factory hadn't been already set (which may or may not be the cause, in my situation it's not the case so the above will work fine) but it might not in other people's? I don't know if I'm a weirdo for wanting to do this or not though.

@dstufft
Copy link
Contributor Author

dstufft commented Aug 24, 2015

Oh, and of course you can't pass a string to ReadOnlyFactoryWrapper.

@digitalresistor
Copy link
Member

@dstufft:

from pyramid.path import DottedNameResolver, caller_package
factory = DottedNameResolver(caller_package()).maybe_resolve(factory)

Now you can easily pass it a string too. This is pretty much what config.maybe_dotted() does, which you could also use directly:

config.add_route(..., factory=ReadOnlyFactoryWrapper(config.maybe_dotted(DefaultRootFactory))

will work without issues.

@mmerickel
Copy link
Member

The DefaultRootFactory just returns None so you don't need to use that api.

Also as @bertjwregeer just said you can use config.maybe_dotted() to load strings.

@dstufft
Copy link
Contributor Author

dstufft commented Aug 24, 2015

Ok, so I'm got that working in my app (still need to write tests). Do y'all want to consider this feature request rejected since I don't need it, or do you think it's still ultimately useful? I can try to finish up my PR incase it's useful otherwise, but if not I'll just close it.

@mmerickel
Copy link
Member

I'm not rejecting it outright but I need a use case I guess? Routes are completely configurable using their current APIs (that I can see) so a RouteFound handler doesn't seem useful to me.

@sontek
Copy link
Member

sontek commented Aug 24, 2015

I think using the factory for this use case is an awkward API because the factory is supposed to be for generating your context and this itself just feels wrong:

 factory=ReadOnlyFactoryWrapper(config.maybe_dotted(DefaultRootFactory))

Because the use case is not to override the default context, its to run additional code when those routes are executed. Ideally we would introduce something that can be viewed within the request diagram:

http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/router.html

Only thing I dislike about @dstufft's implementation is that it moves some of the route configuration out into another place but I prefer it over hooking it into something that happens to work but not the right place to do it.

@dstufft
Copy link
Contributor Author

dstufft commented Aug 24, 2015

So, I agree that the factory felt like the wrong place to me, which is why i added the event instead of using the factory (although I had noticed that I could have used the factory at first).

If it helps at all, I've now implemented my "mark a route as read only" thing using both methods:

That might give some insight into what it looks like to use each particular method in a real world use case?

@dstufft
Copy link
Contributor Author

dstufft commented Aug 24, 2015

Oh, and to the request diagram, the RoundFound event would of course happen between the traversal and the URL Dispatch/route predicates.

@mmerickel
Copy link
Member

Maybe it belongs in a separate issue at this point but some sort of BeforeTraversal event (more generic but in the same spot as RouteFound) seems to make sense for hooking into the pipeline immediately after route processing is completed. BeforeTraversal vs route/root factory is analogous to BeforeRender versus calling render explicitly and passing in parameters. Some of these are global and some are per-call.

Also it's likely significantly more useful to people than NewRequest which is called prior to even tweens being invoked (way too early in the pipeline for many decisions to be made).

Anyway I've convinced myself it's at least interesting. ;-)

@digitalresistor
Copy link
Member

This was added to Pyramid 1.7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants