Here's a test case:
def test_overload_dynamic_routes():
app = Sanic('test_dynamic_route')
@app.route('/overload/<param>', methods=['GET'])
async def handler1(request, param):
return text('OK1: ' + str(param))
@app.route('/overload/<param>', methods=['POST', 'PUT'])
async def handler2(request):
return text('OK2: ' + str(param))
request, response = sanic_endpoint_test(app, 'get', uri='/overload/bla')
assert response.text == 'OK1: bla'
request, response = sanic_endpoint_test(app, 'post', uri='/overload/bla')
assert response.text == 'OK2: bla'
request, response = sanic_endpoint_test(app, 'put', uri='/overload/bla')
assert response.text == 'OK2: bla'
request, response = sanic_endpoint_test(app, 'delete', uri='/overload')
assert response.status == 405
with pytest.raises(RouteExists):
@app.route('/overload', methods=['PUT', 'DELETE'])
async def handler3(request):
return text('Duplicated')
The reason is, that while a route gets replaced with a CompositionView in routes_static and routes_all, it still lives in routes_dynamic next to the CompositionView.
@youknowone , since you implemented that feature you're probably most familiar with that section of the code. Would you mind fixing this issue?
I found the reason and I tried an ad-hoc fix but It is very unreliable.
I think using werkzeug solves the problem in the easier way, which is used by flask.
Ya, that's what I thought first when I saw that Sanic implements its own routing. werkzeug routing is very powerful and the library also offers other very helpful and well tested stuff. The only thing i don't like about it is, that it has some focus on WSGI. However, that's not the biggest part.
@seemethere @r0fls @channelcat What do you think about replacing Sanic's routing with werkzeug?
I think sanic should do. And more.
If I have right of voting for future of sanic, I believe sanic should import designs and codes of flask as much as possible. The are maintained and tested. Both projects have compatible licenses.
I suggest the strategy:
Routing is on the critical path for performance. Another project to look at for inspiration is the Falcon framework which, also being performance focused, has chosen to compile the route logic:
https://github.com/falconry/falcon/tree/master/falcon/routing
I tried werkzeug a little: https://github.com/youknowone/sanic/commit/0c636a5c36f611e301fe9f43e90f19f85889b164
It doesn't look very easy but possible to have werkzeug router as an option.
@youknowone How does doing that affect performance?
@seemethere I didn't test performance. I tested its working and it even is not working that well yet. It would be almost same unless router hits LRU cache well. (But it seems I am expecting another side of performance in sanic, so I am actually less interesting for the routing cost)
I think having werkzeug router not as a full replacement of sanic router but as an optional replacement will be a good choice regardless of its performance.
I profiled average route search time with 650 generated routes in Werkzeug and Sanic using the following script: https://gist.github.com/subyraman/8704f9a168d8261c0dde815f0068c224
average werkzeug time to find route: 0.0004255075638110821
average sanic time to find route: 0.0002654151733104999
But in a real world scenario there could be more efficient caching of the werkzeug router call.
Since the original bug has been fixed and the discussion went into another direction I changed the title. Could someone remove the bug label?
So.. what are the pros for using werkzeug? I think we should stick with the Sanic routing:
@r0fls I think the argument is that it's more battle-tested than sanic's but imo I don't know if the performance drop-off would be worth it
@r0fls How is the routing async? werkzeug is tested very much and also it has all features developers needed for years. I think we have still a lot of work before us with Sanic, we can be glad about anything we don't have to implement.
Pulling in the behemoth that is Werkzeug (an entire WSGI Compliance Framework) to use it only for routing is not a great idea in my opinion. Sanic currently has only a few very-needed dependencies, and is very lightweight because of that. Pulling in stuff is how Django became Django.
Other downsides would include it being completely WSGI centric (ASGI is not even out yet, focusing on these things right now is kinda pointless), the apparent performance dip, it's extremely verbose and annoying to use, and on a more personal level, among all the reasons I have for my growing dislike of Flask, Werkzeug is ranking high up on that list.
Current routing works fine, I'd say keep it.
Sanic's routing isn't async per se, it loops through registered routes and
does regex matching against each (probably some optimization can be made
there). It's not blocking, but neither is werkzeug ;)
The Sanic router is okay, and definitely more easy to understand than
Werkzeug. But Werkzeug is definitely very mature in my opinion and accounts
for a ton of edge cases which will eventually hit Sanic. Like I'm working
on a 'url_for' method right now for Sanic and I can already see the issues
that could crop up in routing.
I would lean towards not including Werkzeug, but maybe a case can be made
for forking the basic router and optimizing it.
On Wed, Feb 1, 2017, 5:56 PM Luiz Berti notifications@github.com wrote:
Pulling in the behemoth that is Werkzeug (an entire WSGI Compliance
Framework) to use it only for routing is not a great idea in my opinion.
Sanic currently has only a few very-needed dependencies, and is very
lightweight because of that.Other downsides would include it being completely WSGI centric (ASGI is
not even out yet, focusing on these things right now is kinda pointless),
the apparent performance dip, it's extremely verbose and annoying to use,
and on a more personal level, among all the reasons I have for my growing
dislike of Flask, Werkzeug is ranking high up on that list.Current routing works fine, I'd say keep it.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/channelcat/sanic/issues/353#issuecomment-276810390,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AD2voJsCG_HOBEVDZ5ONI9EsN6YzvO3hks5rYQ2pgaJpZM4Lvk1L
.
I can agree both for using werkzeug or not, but I feel awkward about a few reasons of them.
Before starting, I upvote for @luizberti's comment's bottom half and @subyraman's comment for reasonability. (Edit: Also a big upvote for @luizberti's comment below)
Sanic's router is not async and it shouldn't be async. async is not a boost-up magic but it consume additional operational cost. We can take advantage from it only when it has waiting time due to IO (or something similar waiting like that). To make it clear: routing is not an IO job.
Generally speaking, we shouldn't forget 2 factors in performance engineering
I am not sure this is also true for others, but at least for me, routing cost was never a big deal for me because the major performance drop usually is coming from the user code. When it actually makes problems, it would be not resolvable by sanic's router (because it's routing is not optimized for that kind of flood of connections - of course same for werkzeug too), but it is better to write specialized routing on a request middleware.
Will sanic's router be light as well as now? If we keep it with these poor features, yes. Otherwise, not sure. Generally, when you implement more features in sanic's router, they usually drops the performance (which still is not very big part of the performance, though). Will sanic always have the poor features? I didn't expect it when I read "Sanic is a Flask-like" on the README. Will sanic improve the routing without performance drop? We can start it from better code base. @subyraman made a nice about it.
Routing costs cheap and sanic's goal is async rather than routing. Every project consumes human resources for features. Investing them to problems which already solved will not be as good as enhancing async server itself.
I didn't get the point why werkzeug is not a "lightweight".
For installation point of view - it is 300kb and nothing other dependency.
For performance point of view - it asks reasonable cost for the features.(I agree it still can be expensive for a few extreme cases) About WSGI features, it will not affect your performance when you don't use it. "loading modules" itself doesn't load big stuffs.
For the package size, it still can be splited. (But I don't think it is worth to do for a few kb by paying additional cost for management)
OK, agreed I misspoke saying it was async. It is apparently faster though. It is also targeted at python 3 (werkzeug still supports ancient python 2 versions iirc).
I would never advocate for use refusing to implement any decent feature in the name of sheer performance alone (unless the hit is too big of course), my only point here is to avoid feature and dependency creep.
In my opinion Flask overshoots it's feature set, and I would suggest us not borrowing absolutely everything we can from it as you seemed to suggest. Flask is terribly inconsistent in a lot of ways, and is not a "role-model project" by any means. Flask is widely used because all you have to do is pop a few @app.routes here and there and you'll have a working prototype, the 0-60 time is very fast. To that extent, which is Flask's most important aspect, I'd say Sanic can absolutely call itself Flask-like, cause the interface is very similar, and it's equally as easy to pull something out of nothing with it.
Flask has a lot of merit on it's own, but there is a big difference between being Flask-like and being an Asynchronous-Flask. I would aim for the first: Take what works, don't take what doesn't, keep the surface small and simple, let the users make extensions that provide extra functionality. In my opinion that is much more sane than just trying to reimplement everything that Flask has on an asynchronous backend, and this does not imply in anyway that we should keep a poor feature set in Sanic for sake of performance.
Sanic currently fills it's own niche, and for people who need something more along the lines of Flask, well, Flask is not going anywhere. 😉
All that being said, pulling in all of Werkzeug only to use a fraction of it's feature set is a poor decision in my opinion, and I would much rather go with @subyraman's idea of taking a good look into how it does routing and seeing what would be worthwhile to mimic in Sanic. We could start slowly optimizing Sanic's routing from what we currently have, then slowly start bringing in new improvements over from Werkzeug.
I value the current router a lot for it's simplicity, it's so small and easy to understand. The cost of having to look though Werkzeug's (horrible) documentation to implement things is something that will bring us only pain. Moreover Werkzeug is geared towards Flask's use cases and WSGI, and it may evolve into taking design decisions that don't necessarily favor our usage of it.
On a last note, routing is simple! It's definitely not the biggest worry in a project like this, and we could very well do it ourselves like we do today. Why not just take what we need from Werkzeug?
Closing, I think we've reached a consensus centered around @luizberti's comment.
Most helpful comment
I would never advocate for use refusing to implement any decent feature in the name of sheer performance alone (unless the hit is too big of course), my only point here is to avoid feature and dependency creep.
In my opinion Flask overshoots it's feature set, and I would suggest us not borrowing absolutely everything we can from it as you seemed to suggest. Flask is terribly inconsistent in a lot of ways, and is not a "role-model project" by any means. Flask is widely used because all you have to do is pop a few
@app.routes here and there and you'll have a working prototype, the 0-60 time is very fast. To that extent, which is Flask's most important aspect, I'd say Sanic can absolutely call itself Flask-like, cause the interface is very similar, and it's equally as easy to pull something out of nothing with it.Flask has a lot of merit on it's own, but there is a big difference between being Flask-like and being an Asynchronous-Flask. I would aim for the first: Take what works, don't take what doesn't, keep the surface small and simple, let the users make extensions that provide extra functionality. In my opinion that is much more sane than just trying to reimplement everything that Flask has on an asynchronous backend, and this does not imply in anyway that we should keep a poor feature set in Sanic for sake of performance.
Sanic currently fills it's own niche, and for people who need something more along the lines of Flask, well, Flask is not going anywhere. 😉
All that being said, pulling in all of Werkzeug only to use a fraction of it's feature set is a poor decision in my opinion, and I would much rather go with @subyraman's idea of taking a good look into how it does routing and seeing what would be worthwhile to mimic in Sanic. We could start slowly optimizing Sanic's routing from what we currently have, then slowly start bringing in new improvements over from Werkzeug.
I value the current router a lot for it's simplicity, it's so small and easy to understand. The cost of having to look though Werkzeug's (horrible) documentation to implement things is something that will bring us only pain. Moreover Werkzeug is geared towards Flask's use cases and WSGI, and it may evolve into taking design decisions that don't necessarily favor our usage of it.
On a last note, routing is simple! It's definitely not the biggest worry in a project like this, and we could very well do it ourselves like we do today. Why not just take what we need from Werkzeug?