Prior to version Flask 2.0.2, it was possible to register blueprints multiple of times with different url prefix during Flask App factory. Such as:
# Flask 0.10.1
from my_project.api.v1.user import user_api_v1
app.register_blueprint(user_api_v1,
url_prefix='/api/latest/users')
app.register_blueprint(user_api_v1,
url_prefix='/api/v1/users')
from my_project.api.v1.recipe import recipe_api_v1
app.register_blueprint(recipe_api_v1,
url_prefix='/api/latest/recipes')
app.register_blueprint(recipe_api_v1,
url_prefix='/api/v1/recipes')
But with latest Flask version, the above was updated into this:
from my_project.api.v1.user import user_api_v1
app.register_blueprint(user_api_v1,
name='users_latest',
url_prefix='/api/latest/users')
app.register_blueprint(user_api_v1,
name='users_v1',
url_prefix='/api/v1/users')
from my_project.api.v1.recipe import recipe_api_v1
app.register_blueprint(recipe_api_v1,
name='recipes_latest',
url_prefix='/api/latest/recipes')
app.register_blueprint(recipe_api_v1,
name='recipes_v1',
url_prefix='/api/v1/recipes')
Basically, adding unique name. However, this change caused an issue that the error- and before_request-handlers that was registered to blueprints do not get mapped to the second blueprint.
This was the result for the new Flask App:
# App's url map
Map([<Rule '/api/latest/recipes/' (GET, HEAD, OPTIONS) -> recipes_latest.get_all>,
<Rule '/api/latest/users/' (GET, HEAD, OPTIONS) -> users_latest.get_all>,
<Rule '/api/v1/recipes/' (GET, HEAD, OPTIONS) -> recipes_v1.get_all>,
<Rule '/api/v1/users/' (GET, HEAD, OPTIONS) -> users_v1.get_all>,
<Rule '/api/latest/recipes/<id>' (POST, OPTIONS) -> recipes_latest.post>,
<Rule '/api/v1/recipes/<id>' (POST, OPTIONS) -> recipes_v1.post>,
<Rule '/api/latest/recipes/<id>' (GET, HEAD, OPTIONS) -> recipes_latest.get>,
<Rule '/api/v1/recipes/<id>' (GET, HEAD, OPTIONS) -> recipes_v1.get>,
<Rule '/static/<filename>' (GET, HEAD, OPTIONS) -> static>])
# App's before_request_funcs
defaultdict(<class 'list'>,
{None: [<function requires_staff_permission at 0x7fe42b9be840>],
'recipes_latest': [<function _require_chef_permission at 0x7fe429a67f28>],
'users_latest': [<function _require_manager_permission at 0x7fe42acb90d0>]})
# App's error_handler_spec
defaultdict(<function Scaffold.__init__.<locals>.<lambda> at 0x7fe42ad0aa60>,
{None: defaultdict(<class 'dict'>,
{None: {<class 'Exception'>: <function generic_error_handler.<locals>.error_handler at 0x7fe42ad0ab70>}}),
'recipes_latest': defaultdict(<class 'dict'>,
{None: {<class 'my_project.api.v1.recipe.MissingParameter'>: <function handle_missing_argument at 0x7fe429a68510>,
<class 'my_project.api.v1.recipe.MongoDocumentNotFound'>: <function handle_no_mongo_document at 0x7fe429a68400>,
<class 'my_project.api.v1.recipe.UnsupportedParameter'>: <function handle_invalid_argument at 0x7fe429a68488>}}),
'users_latest': defaultdict(<class 'dict'>,
{None: {<class 'my_project.api.v1.user.UnsupportedParameter'>: <function handle_invalid_argument at 0x7fe42acb92f0>}})})
What this means is that when request is done to GET /api/v1, it will not trigger any handlers, because handlers are registered to x_latest and /api/v1 was registered with x_v1 blueprint name.
The behaviour I'd expect after using app.register_blueprint for same blueprint object with different name is that the new registered name should also have same registered error- and before request-handler mapped like so:
# Expected App's before_request_funcs
defaultdict(<class 'list'>,
{None: [<function requires_staff_permission at 0x7fe42b9be840>],
'recipes_latest': [<function _require_chef_permission at 0x7fe429a67f28>],
'recipes_v1': [<function _require_chef_permission at 0x7fe429a67f28>],
'users_latest': [<function _require_manager_permission at 0x7fe42acb90d0>],
'users_v1': [<function _require_manager_permission at 0x7fe42acb90d0>]})
# Expected App's error_handler_spec
defaultdict(<function Scaffold.__init__.<locals>.<lambda> at 0x7fe42ad0aa60>,
{None: defaultdict(<class 'dict'>,
{None: {<class 'Exception'>: <function generic_error_handler.<locals>.error_handler at 0x7fe42ad0ab70>}}),
'recipes_latest': defaultdict(<class 'dict'>,
{None: {<class 'my_project.api.v1.recipe.MissingParameter'>: <function handle_missing_argument at 0x7fe429a68510>,
<class 'my_project.api.v1.recipe.MongoDocumentNotFound'>: <function handle_no_mongo_document at 0x7fe429a68400>,
<class 'my_project.api.v1.recipe.UnsupportedParameter'>: <function handle_invalid_argument at 0x7fe429a68488>}}),
'recipes_v1': defaultdict(<class 'dict'>,
{None: {<class 'my_project.api.v1.recipe.MissingParameter'>: <function handle_missing_argument at 0x7fe429a68510>,
<class 'my_project.api.v1.recipe.MongoDocumentNotFound'>: <function handle_no_mongo_document at 0x7fe429a68400>,
<class 'my_project.api.v1.recipe.UnsupportedParameter'>: <function handle_invalid_argument at 0x7fe429a68488>}}),
'users_latest': defaultdict(<class 'dict'>,
{None: {<class 'my_project.api.v1.iser.UnsupportedParameter'>: <function handle_invalid_argument at 0x7fe42acb92f0>}}),
'users_v1': defaultdict(<class 'dict'>,
{None: {<class 'my_project.api.v1.iser.UnsupportedParameter'>: <function handle_invalid_argument at 0x7fe42acb92f0>}})})
Environment:
- Python version: 3.6.8
- Flask version: 2.0.2