Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork16.7k
Description
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 uniquename. 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 mapMap([<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_funcsdefaultdict(<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_specdefaultdict(<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 toGET /api/v1, it will not trigger any handlers, because handlers are registered tox_latest and/api/v1 was registered withx_v1 blueprint name.
The behaviour I'd expect after usingapp.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_funcsdefaultdict(<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_specdefaultdict(<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