Skip to content

route decorator on MethodView classes #3404

@dougthor42

Description

@dougthor42

TL;DR:

  • Being able to use a Blueprint's @pb.route decorator on a MethodView class can help made code more readable
  • It should be a pretty trivial fix.
  • I'm working on tests and a merge request right now.

Expected Behavior

Users should be able to combine blueprints with MethodView.

from flask import Blueprint
from flask.views import MethodView

api = Blueprint("api", __name__, url_prefix="/api/v1")

@api.route("/users")
@api.route("/users/<user_id>")
class UserAPI(MethodView):

    def get(self, user_id=None):
        if user_id is None:
            return "list of all users"
        else:
            return f"just user {user_id}"

    def post(self, user_id):
        return f"POST {user_id}"

    def delete(self, user_id):
        return f"DELETE {user_id}"

    def put(self, user_id):
        return f"PUT {user_id}"

    def PATCH(self, user_id):
        return f"PATCH {user_id}"

Actual Behavior

Using a blueprint's @route decorator on a MethodView results in TypeError is raised:

Traceback (most recent call last):
  File "/c/gitlab/mocvd/sic-inventory/.venv/lib/python3.6/site-packages/flask/app.py", line 2463, in __call__
    return self.wsgi_app(environ, start_response)
  File "/c/gitlab/mocvd/sic-inventory/.venv/lib/python3.6/site-packages/flask/app.py", line 2449, in wsgi_app
    response = self.handle_exception(e)
  File "/c/gitlab/mocvd/sic-inventory/.venv/lib/python3.6/site-packages/flask/app.py", line 1866, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/c/gitlab/mocvd/sic-inventory/.venv/lib/python3.6/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/c/gitlab/mocvd/sic-inventory/.venv/lib/python3.6/site-packages/flask/app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
  File "/c/gitlab/mocvd/sic-inventory/.venv/lib/python3.6/site-packages/flask/app.py", line 1952, in full_dispatch_request
    return self.finalize_request(rv)
  File "/c/gitlab/mocvd/sic-inventory/.venv/lib/python3.6/site-packages/flask/app.py", line 1967, in finalize_request
    response = self.make_response(rv)
  File "/c/gitlab/mocvd/sic-inventory/.venv/lib/python3.6/site-packages/flask/app.py", line 2130, in make_response
    " {rv.__class__.__name__}.".format(rv=rv)
TypeError: The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a UserAPI.

Instead, users need to manually add rules to the app after the fact, which can be hard to read and requires that the app instance be available in the module.

user_view = UserAPI.as_view('user_api')
app.add_url_rule('/users/', defaults={'user_id': None},
                 view_func=user_view, methods=['GET',])
app.add_url_rule('/users/', view_func=user_view, methods=['POST',])
app.add_url_rule('/users/<int:user_id>', view_func=user_view,
                 methods=['GET', 'PUT', 'PATCH', 'DELETE'])

Environment

  • Python version: 3.6.8
  • Flask version: 1.1.1
  • Werkzeug version: 0.16.0

The Fix

Based on my testing is should be relatively trivial to support this. All that needs to be done is edit Blueprint.route:

diff --git a/src/flask/blueprints.py b/src/flask/blueprints.py
index 8978104d..f6f62dd6 100644
--- a/src/flask/blueprints.py
+++ b/src/flask/blueprints.py
@@ -11,6 +11,8 @@
 """
 from functools import update_wrapper

+from .views import MethodViewType
+
 from .helpers import _endpoint_from_view_func
 from .helpers import _PackageBoundObject

@@ -276,6 +278,11 @@ class Blueprint(_PackageBoundObject):

         def decorator(f):
             endpoint = options.pop("endpoint", f.__name__)
+
+            # Support decorating MethodView classes
+            if isinstance(f, MethodViewType):
+                f = f.as_view(endpoint)
+
             self.add_url_rule(rule, endpoint, f, **options)
             return f

Note: I originally saw this feature in the marshmallow-code/flask-smorest library (originally Nobatek/flask-rest-api) flask_smorest/blueprint.py.
The above code is taken almost verbatim from that library.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions