Flask: Blueprint url_prefix isn't additive with register_blueprint url_prefix

Created on 19 Jun 2015  ·  14Comments  ·  Source: pallets/flask

So imagine I'm building an API from a set of blueprints like

<snip from the signup.py file>
signupbp = Blueprint('signupbp', __name__, url_prefix="/signup")

@signupbp.route("/")
def signup_start():
    return "signup_start"

@signupbp.route("/complete")
def signup_complete():
    return "signup_complete"

then in my main.py code I do something like:

app.register_blueprint(signupbp, url_prefix='/api')

I expect to see the following routes:

/api/signup/                    signupbp.signup_start   
/api/signup/complete            signupbp.signup_complete
/static/<path:filename>         static                  

What I actually see is:

/api/                    signupbp.signup_start   
/api/complete            signupbp.signup_complete
/static/<path:filename>  static                  

I imagine the url_prefixes should be additive not replacement (in the worse possible way). I've tried all combinations of / before and after the url_prefix params.

This is actually a very simplified example - in my API I'm using module names to implement versioning so I can't build the URL into any part of the code before thats suggested as a solution.

Most helpful comment

@davidism I see your point on the url_prefix, but I agree with @masom that it's behavior is surprising at first. It would be nice to have a way to do something in the lines of:

api = Blueprint('api', base_path='/api')
foo = Blueprint('foo', base_path='/foo')
bar = Blueprint('bar', base_path='/bar')

@foo.route('/a')
def a():
    ...

@foo.route('/b')
def b():
    ...
@bar.route('/x')
def x():
    ...

api.register_blueprint(foo)
api.register_blueprint(bar)
app.register_blueprint(api)

And then the paths would look like this:

/api
    /foo
        /a
        /b
    /bar
        /x

This way the paths are set once, avoiding duplicated code.

All 14 comments

url_prefix should use signup, api is not the url for signup blueprint.

app.register_blueprint(signupbp, url_prefix='/api')

=>

app.register_blueprint(signupbp, url_prefix='/signup')

if you use a front-end with nginx, you could rewrite the url to this app, eg:

rewrite ^api/(.*) /$1 break; # the app will receive the signup url.

2015-06-19 8:18 GMT+08:00 Tony Million [email protected]:

So imagine I'm building an API from a set of blueprints like


signupbp = Blueprint('signupbp', name, url_prefix="/signup")

@signupbp.route("/")
def signup_start():
return "signup_start"

@signupbp.route("/complete")
def signup_complete():
return "signup_complete"

then in my main.py code I do something like:

app.register_blueprint(signupbp, url_prefix='/api')

I expect to see the following routes:

/api/signup/ signupbp.signup_start
/api/signup/complete signupbp.signup_complete
/static/path:filename static

What I actually see is:

/api/ signupbp.signup_start
/api/complete signupbp.signup_complete
/static/path:filename static

I imagine the url_prefixes should be additive not replacement (in the
worse possible way). I've tried all combinations of / before and after
the url_prefix params.


Reply to this email directly or view it on GitHub
https://github.com/mitsuhiko/flask/issues/1507.

DamonChen
让生活更俏皮些 http://www.zz1788.com

That's not a fix for this actual problem, its a workaround using specific technology.

There is a point in the code where this could be fixed in blueprint.py and I'm considering submitting a patch.

Another fix would be #593 which proposed nestable blueprints.

Are you suggesting that there is a bug, or that the current API is imperfect?

Because in case of the former, this is working as intended. The url_prefix you pass to __init__ is a default, which gets overridden by the url_prefix you provide to register_blueprint.

@justanr I don't see how this is related.

@unittaker Maybe I'm thinking too hard, but nesting related blueprints together under a single endpoint.

A blueprint for the API endpoint, then register signup, etc on there.

The current behaviour is somewhat surprising.

admin = Blueprint(..., url_prefix='/admin')

app.register_blueprint(admin, url_prefix='/backend')

I would have expected that admin be mounted as /backend/admin and not /backend

This behavior doesn't seem surprising at all. It would be more surprising if passing a value to override a default concatenated the paths. If you want to register a blueprint with the prefix /api/signup, register it with the prefix /api/signup. Anything less explicit seems prone to just as much confusion by others.

@davidism I see your point on the url_prefix, but I agree with @masom that it's behavior is surprising at first. It would be nice to have a way to do something in the lines of:

api = Blueprint('api', base_path='/api')
foo = Blueprint('foo', base_path='/foo')
bar = Blueprint('bar', base_path='/bar')

@foo.route('/a')
def a():
    ...

@foo.route('/b')
def b():
    ...
@bar.route('/x')
def x():
    ...

api.register_blueprint(foo)
api.register_blueprint(bar)
app.register_blueprint(api)

And then the paths would look like this:

/api
    /foo
        /a
        /b
    /bar
        /x

This way the paths are set once, avoiding duplicated code.

The behaviour is surprising to me because my mental model of Flask is that I create a blueprint instance with one prefix, and mount it using another prefix, and I expect those prefixes to be _additive_ because they are (in my mind) separate concerns. To have one actually override the other is then surprising because it seems like concepts are mixed together. I.e. blueprint concept != blueprint registration concept (or so I thought).

Just my two cents to cast light on why the behaviour surprises me.

I'm altering an app that registers eight blueprints (for eight different top-level-folders in the URL path). Our goal is to have the eight areas of the app be independently available to several geographic instances of the app. For example, the "contact" area of the app should behave the same for all locations, but each location will allow the manager of that location to administer the "contact" area for that location, providing contact info specific to that location. The goal was for the URL to change from what it is now (always "/contact/") to have the location specified as its own top-level URL folder ("/SanFrancisco/contact").

Blueprints looked like a good way to do that, but it sounds like what we really register with "register_blueprint" is the URL prefix (the one specified in the Blueprint constructor ("__init__" if untitaker was right at https://github.com/pallets/flask/issues/1507#issuecomment-113515474) if we don't specify what turns out to be an "override" in the call to register_blueprint), and not the blueprint itself. It would be nifty to create a blueprint and then be able to use it a few times.

Please correct any assumptions in what I wrote above. I'm sticking my neck out a little in order to learn.

I agree, this is very odd.

I have a users blueprint, defined using:

bp = Blueprint("users", __name__, url_prefix="/<uuid:user_id>")

I then preprocess this user ID inside the blueprint. Every request to this blueprint should begin with /:uuid, relative to the mount point.

At the app level, I decide to mount the users blueprint at /users:

app.register_blueprint(bp_users, url_prefix="/users")

I expect the blueprint to therefore respond to requests like /users/<uuid:user_id>. It will only respond to /users. The app shouldn't care about underlying /<uuid:user_id>.

How can I achieve this without being WET (bad!) and doing

app.register_blueprint(bp_users, url_prefix="/users/<uuid:user_id>")

@qaisjp You cannot with the package's current architecture. It seems the maintainer took a hard stance when he closed it, but that was five years ago - so maybe the mentality has changed? In any case, there is an issue open which would allow nesting blueprints (#593)

I've already explained why this specific issue isn't going to change. That said, see the linked issue about general nestable blueprints.

Was this page helpful?
0 / 5 - 0 ratings