Building RESTful APIs with MethodView
When building a REST API in Flask, using a single view function with multiple if request.method == 'GET': branches can lead to complex, hard-to-maintain code. MethodView solves this by automatically dispatching requests to class methods named after the corresponding HTTP verbs.
Defining a RESTful Resource
To create a resource, subclass MethodView and implement methods for the HTTP verbs you want to support (e.g., get, post, put, delete). Flask automatically populates the methods attribute based on the methods you define.
from flask import session, redirect, url_for
from flask.views import MethodView
class CounterAPI(MethodView):
def get(self):
# Handles GET requests
return str(session.get("counter", 0))
def post(self):
# Handles POST requests
session["counter"] = session.get("counter", 0) + 1
return redirect(url_for("counter"))
# Register the view
app.add_url_rule(
"/counter", view_func=CounterAPI.as_view("counter")
)
The as_view method converts the class into a view function that can be passed to add_url_rule. The name passed to as_view (e.g., "counter") is used for URL generation with url_for.
Composing Functionality with Inheritance
You can organize common logic into base classes and combine them using standard Python inheritance. MethodView merges the supported HTTP methods from all base classes.
from flask.views import MethodView
class GetView(MethodView):
def get(self):
return "GET"
class DeleteView(MethodView):
def delete(self):
return "DELETE"
class GetDeleteView(GetView, DeleteView):
"""Supports both GET and DELETE"""
pass
app.add_url_rule("/", view_func=GetDeleteView.as_view("index"))
Using Async Handler Methods
MethodView supports asynchronous handler methods. Flask uses current_app.ensure_sync in dispatch_request to execute these methods correctly within the request context.
import asyncio
from flask.views import MethodView
class AsyncUserAPI(MethodView):
async def get(self, user_id):
await asyncio.sleep(0.1) # Simulate async DB call
return {"id": user_id, "name": "Flask User"}
app.add_url_rule("/user/<int:user_id>", view_func=AsyncUserAPI.as_view("user"))
Applying Decorators
Because MethodView is a class, you cannot use the @app.route decorator directly on its methods. Instead, define a decorators class attribute containing a list of decorators to apply to the generated view function.
from flask import make_response
from flask.views import MethodView
def add_x_parachute(f):
def new_function(*args, **kwargs):
resp = make_response(f(*args, **kwargs))
resp.headers["X-Parachute"] = "awesome"
return resp
return new_function
class DecoratedView(MethodView):
decorators = [add_x_parachute]
def get(self):
return "Decorated response"
app.add_url_rule("/decorated", view_func=DecoratedView.as_view("decorated"))
Optimizing Performance
By default, Flask creates a new instance of your MethodView class for every request. If your view is expensive to initialize or doesn't store request-specific state on self, you can set init_every_request = False to reuse a single instance.
from flask.views import MethodView
class OptimizedView(MethodView):
init_every_request = False
def __init__(self):
# This will only run once when as_view is called
self.shared_data = "initialized"
def get(self):
return self.shared_data
app.add_url_rule("/optimized", view_func=OptimizedView.as_view("optimized"))
[!WARNING] When
init_every_request = False, you must not store request-specific data onself(e.g.,self.user = ...), as this will leak between requests. Useflask.gfor request-bound data instead.
Automatic HEAD Handling
If you implement a get method but not a head method, MethodView automatically handles HEAD requests by calling your get method and discarding the body. This behavior is implemented in dispatch_request:
# Internal logic in flask/views.py
def dispatch_request(self, **kwargs):
meth = getattr(self, request.method.lower(), None)
if meth is None and request.method == "HEAD":
meth = getattr(self, "get", None)
assert meth is not None, f"Unimplemented method {request.method!r}"
return current_app.ensure_sync(meth)(**kwargs)
If a request is made with a method that is not implemented (and is not a HEAD request falling back to GET), an AssertionError is raised. However, Flask typically prevents this by returning a 405 Method Not Allowed response because the methods attribute is correctly populated during class definition.