pallets / flask

The Python micro framework for building web applications.
https://flask.palletsprojects.com
BSD 3-Clause "New" or "Revised" License
68.02k stars 16.21k forks source link

Url prefix duplicating when instanticiating multiple apps with nested blueprints #4062

Closed reritom closed 3 years ago

reritom commented 3 years ago

When using an app factory approach to create an app that has a blueprint with a url prefix, and a nested blueprint, the more times the app factory is called, the more times the parent url_prefix gets appended.

from flask import Flask, jsonify, Blueprint
from flask.views import MethodView

# Create a child blueprint with a simple view
class UsersView(MethodView):
    def get(self):
        return jsonify([]), 200

child_blueprint = Blueprint("child", __name__)
child_blueprint.add_url_rule("/user", view_func=UsersView.as_view("accounts"))

# Create a parent blueprint that registers the child blueprint
parent_blueprint = Blueprint("parent", __name__)
parent_blueprint.register_blueprint(child_blueprint)

# App factory
def create_app():
    app = Flask(__name__)
    app.register_blueprint(parent_blueprint, url_prefix="/api")
    return app

print("First app")
app = create_app()
print(app.url_map)

print("Second app")
app = create_app()
print(app.url_map)

print("Third app")
app = create_app()
print(app.url_map)

Output:

First app
Map([<Rule '/api/user' (HEAD, GET, OPTIONS) -> parent.child.accounts>,
 <Rule '/static/<filename>' (HEAD, GET, OPTIONS) -> static>])
Second app
Map([<Rule '/api/api/user' (HEAD, GET, OPTIONS) -> parent.child.accounts>,
 <Rule '/static/<filename>' (HEAD, GET, OPTIONS) -> static>])
Third app
Map([<Rule '/api/api/api/user' (HEAD, GET, OPTIONS) -> parent.child.accounts>,
 <Rule '/static/<filename>' (HEAD, GET, OPTIONS) -> static>])

In the above output you can see that the url_prefix gets appended an additional time each time the app factory is called

I would expect that the app factory could be called as many times as wanted, with the resulting url_map being the same each time

Environment:

davidism commented 3 years ago

Fixed by #4036

reritom commented 3 years ago

Running the same code on branch 2.0.x has a different problem with the output being:

First app
Map([<Rule '/user' (HEAD, OPTIONS, GET) -> parent.child.accounts>,
 <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>])
Second app
Map([<Rule '/user' (HEAD, OPTIONS, GET) -> parent.child.accounts>,
 <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>])
Third app
Map([<Rule '/user' (HEAD, OPTIONS, GET) -> parent.child.accounts>,
 <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>])

So the prefix is now no longer being added at all. Same goes for if I register the parent blueprint as with a url_prefix. The top-level prefix only gets applied if the child blueprint also has a prefix.

This custom unittest fails in 2.0.x but I would expect it to pass:

def test_nested_blueprint_url_prefix_only_parent_prefix(app, client):
    parent = flask.Blueprint("parent", __name__)
    child = flask.Blueprint("child", __name__)

    @child.route("/child-endpoint")
    def child_index():
        return "Child"

    parent.register_blueprint(child)
    app.register_blueprint(parent, url_prefix="/parent")

    assert client.get("/parent/child-endpoint").data == b"Child"

but if I set the child prefix too, the parent prefix is added (this case is already tested in the #4036 fix)

def test_nested_blueprint_url_prefix_parent_and_prefix(app, client):
    parent = flask.Blueprint("parent", __name__)
    child = flask.Blueprint("child", __name__)

    @child.route("/child-endpoint")
    def child_index():
        return "Child"

    parent.register_blueprint(child, url_prefix="/child")
    app.register_blueprint(parent, url_prefix="/parent")

    assert client.get("/parent/child/child-endpoint").data == b"Child"

I don't see the test_nested_blueprint_url_prefix_only_parent_prefix test or a similar one in the existing 2.0.x suite. Unless I am missing something, the existing tests all seem to have a explicit prefix_urls on all blueprints.

Apologies if this has already been fixed.