Search code examples
pythonflasksubdomainblueprint

Flask - Can't override default subdomain for blueprints needing a different subdomain


I have a Flask project/app and want it to be served mainly from app.example.com. I also have a single blueprint inside this app which should only be served from api.example.com.

Now, if I set app as the default subdomain, I'm unable to override this default in other blueprints which should be served from a different subdomain (e.g. api). In fact, any blueprints created with a different subdomain will 404.

In other words, the code below doesn't work (api.example.com/test2 will 404):

# -*- coding: utf-8 -*-

from flask import Flask, Blueprint

app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com'
app.url_map.default_subdomain = 'app' # set default subdomain, intending to override it below for `api.*`

appbp = Blueprint('app', 'app')
apibp = Blueprint('api', 'api') 


@appbp.route('/test1')
def app_hello():
    # this works (app.example.com/test1)
    return 'appbp.app_hello'


@apibp.route('/test2')
def api_hello():
    # this will 404 (api.example.com/test2)
    return 'apibp.api_hello'


app.register_blueprint(appbp) # this works, serves from `app.example.com`
app.register_blueprint(apibp, subdomain='api') # doesn't work, `api.example.com/test2` will 404, so will `app.example.com/test2` (tried just in case it was using the default subdomain instead)

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8888, debug=True)

However, if I don't set a default subdomain and instead set a subdomain each time I register a blueprint, it magically works for both app and api:

# -*- coding: utf-8 -*-

from flask import Flask, Blueprint

app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com'
# app.url_map.default_subdomain = 'app' # now try without a default

appbp = Blueprint('app', 'app') 
apibp = Blueprint('api', 'api')


@appbp.route('/test1')
def app_hello():
    # this works (app.example.com/test1)
    return 'appbp.app_hello'


@apibp.route('/test2')
def api_hello():
    # this works (api.example.com/test2)
    return 'apibp.api_hello'


app.register_blueprint(appbp, subdomain='app') # works, explicitly set subdomain on each blueprint
app.register_blueprint(apibp, subdomain='api') # works, explicitly set subdomain on each blueprint

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8888, debug=True)

In both examples, it appears the blueprints are registered with the correct subdomain:

 <Rule 'app|/test1' (OPTIONS, GET, HEAD) -> app.app_hello>
 <Rule 'api|/test2' (OPTIONS, GET, HEAD) -> api.api_hello>

But, clearly, there's a difference between setting app.url_map.default_subdomain intending to override it later and just explicitly setting subdomains manually.

Any idea what's going on here?

Bonus points: which of these is the preferred way to set a subdomain? I've seen it done both ways.

app.register_blueprint(apibp, subdomain='api')

vs.

apibp = Blueprint('api', 'api', subdomain='api')
app.register_blueprint(apibp)

Solution

  • What's missing is the subdomain_matching option for Flask():

    subdomain_matching – consider the subdomain relative to SERVER_NAME when matching routes. Defaults to False.

    Since app is a relative name, you need to set this to True:

    app = Flask(__name__, subdomain_matching=True)
    

    This used to be done implicitly, but as of Flask 1.0 it's an explicit switch. The change was made because different people had different expectations of what setting SERVER_NAME means, see Flask issue #998.

    As for where you set the subdomain option, that's your choice. If you are reusing blueprints in different contexts, then it makes more sense to set the subdomain option in the app.register_blueprint() call, while setting it in the Blueprint() instance call may make it more self-documenting if you are creating that blueprint object closer to your routes for and so want to make it clearer to someone working on that code that they are affecting a specific subdomain only.