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

blueprint 404 error handler not honored with url_prefix #1498

Closed
ael-code opened this issue Jun 13, 2015 · 10 comments
Closed

blueprint 404 error handler not honored with url_prefix #1498

ael-code opened this issue Jun 13, 2015 · 10 comments

Comments

@ael-code
Copy link

Reproduce the bug:

  • make a blueprint with an errorhandler for 404
  • register the blueprint on the main app under url_prefix="/blue"

Now, if you visit some not existent page under blueprint url prefix, like /blue/notExist you will recive response from the main app 404 error handler.

The only way to trigger the blueprint 404 error handler is by calling abort(404) from within the blueprint.

The correct behaviour would be to choose which error handler to activate also on the basis of the url_prefix parameter.

import unittest
from flask import Flask, Blueprint, abort, request


app = Flask(__name__)
myblueprint = Blueprint('myblueprint', __name__)

@myblueprint.route('/hello', methods=['GET'])
def hello():
    return 'hello world!'

@myblueprint.route('/forced_404', methods=['GET'])
def forced_404():
    abort(404)

myblueprint.errorhandler(404)(lambda e: ('myblueprint 404', 404))

app.register_blueprint(myblueprint, url_prefix="/blue")

app.errorhandler(404)(lambda e: ('app 404', 404))


class BlueprintOrAppTestCase(unittest.TestCase):

    def setUp(self):
        self.client = app.test_client()

    def test_200(self):
        resp = self.client.get('/blue/hello')
        self.assertEqual(resp.status_code, 200)
        self.assertEqual(resp.get_data(True), 'hello world!')

    def test_404_main(self):
        with app.test_client() as client:
            resp = client.get('/notExist')
            self.assertEqual(resp.status_code, 404)
            self.assertEqual(resp.get_data(True), 'app 404')

    def test_404_blueprint(self):
        with app.test_client() as client:
            resp = client.get('/blue/notExist')
            self.assertEqual(resp.status_code, 404)
            self.assertEqual(resp.get_data(True), 'myblueprint 404')
            self.assertEqual(request.blueprint, 'myblueprint')

    def test_404_forced_blueprint(self):
        with app.test_client() as client:
            resp = client.get('/blue/forced_404')
            self.assertEqual(resp.status_code, 404)
            self.assertEqual(resp.get_data(True), 'myblueprint 404')
            self.assertEqual(request.blueprint, 'myblueprint')

if __name__ == '__main__':
    # app.run(host="0.0.0.0", use_reloader=True)
    unittest.main()

Response:

.F..
======================================================================
FAIL: test_404_blueprint (__main__.BlueprintOrAppTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "asd.py", line 43, in test_404_blueprint
    self.assertEqual(resp.get_data(True), 'myblueprint 404')
AssertionError: u'app 404' != 'myblueprint 404'

----------------------------------------------------------------------
Ran 4 tests in 0.013s

FAILED (failures=1)
@jacobsvante
Copy link

I've stumbled upon this problem as well and it made me scratch my head for hours. Until I found this solution by @svieira. So the solution for your problems would be to do something like this:

@blueprint.route("/<path:invalid_path>")
def handle_unmatchable(*args, **kwargs):
    raise NotFound()

It kind of makes sense that it doesn't match your Blueprint when the path it's looking for doesnt exist in the URL mapping. Having said that I see no reason why not to allow the Blueprint to catch the 404 when it's a sub-path of its own url_prefix.

@ThiefMaster
Copy link
Member

Besides the workaround with the wildcard-ish rule I don't think there's any easy way to have blueprint-local 404 handlers. All url_prefix really does is prepending the prefix to each rule added to the blueprint...

Flask should probably show a warning when registering 404/405 handlers for a blueprint though.

@ael-code
Copy link
Author

On 06/14/2015 10:40 AM, Adrian wrote:

Besides the workaround with the wildcard-ish rule I don't think there's
any easy way to have blueprint-local 404 handlers. All |url_prefix|
really does is prepending the prefix to each rule added to the blueprint...

Flask should probably show a warning when registering 404/405 handlers
for a blueprint though.

If you use that workaround you're actually telling to flask that will be
always a valid function to trigger for whatever URI will be received.

Using this solution breaks many things, for example:

  • Automatic OPTIONS response generation
  • automatic 405 response generation

I think that a solution could be found.
We only need a way to understand at which blueprint belongs a given URI.
In case of exception for a given route, you'll grep the right blueprint
and try the registered errorhandler for that blueprint.

@ThiefMaster
Copy link
Member

But there might not always be a dedicated path/prefix for a blueprint....

@ael-code
Copy link
Author

On 06/14/2015 12:15 PM, Adrian wrote:

But there might not always be a dedicated path/prefix for a blueprint....

The prefix do not necessary needs. When you register routes you know for
which blueprint you are registering.

@ThiefMaster
Copy link
Member

Yes, when registering routes. But how do you know it for a 404 candidate? It does not match anything within a Blueprint.

You might very well have to blueprints with the same url prefix.

@ael-code
Copy link
Author

Yes, when registering routes. But how do you know it for a 404 candidate? It does not match anything within a Blueprint.

You might very well have to blueprints with the same url prefix.

If does not match anything and you don't have a prefix you will use the top level handler for 404.

Two blueprints with the same url_prefix it is a very insane configuration. In this case you can:

  • use the top level folder (bug again but only in rare case, warning should be fine in this case)
  • use the first/last registered 404 errorhandler associated with blueprint with the matching prefix.

@svieira
Copy link
Contributor

svieira commented Jun 14, 2015

@ael-code - using two blueprints with the same URL-prefix can have a sane rational. For example, switching over to a different Flask-Rest* extension and for an API - new endpoints use the new extension and old endpoints get migrated over one at a time - but they are all under the same /api/ endpoint (imagine you are moving for more flexibility, not breaking any existing client-facing functionality).

The wildcard 404 handler is making explicit that this blueprint owns every URL below it. Otherwise, there is no way for Flask / Werkzeug to know that this is what is going on as @ThiefMaster points out.

Here are a couple of solutions that come to mind:

  • Use sub-apps instead of blueprints (add documentation to Flask's docs to make this use-case clearer).
  • Add a new API to support registering a blueprint that is supposed to own the entire sub-namespace (so 404 / 405 errors will always use the Blueprint handler). This may mean new APIs in Werkzeug as well as Flask.

@jacobsvante
Copy link

@ael-code What do you mean it breaks 405 response generation?

@jacobsvante
Copy link

@ael-code @svieira you seem to have some experience with 405s in Flask. Would you mind having a look at issue #1494?

@davidism davidism closed this as completed Jun 1, 2017
kravivar pushed a commit to kravivar/python-flask-full-stack that referenced this issue Aug 29, 2017
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 14, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants