Why use classes vs decorators in Python Flask?
These are my personal rules of the thumb.
- If I have to port from an existing application, I use the convention that is used in the source application. Having two possible routing styles is a big advantage.
- If the application uses different URLs for the same code, I create an explicit mapping between URLs and handler classes.
- If the number of URLs and classes used in the application is small, I will use decorators.
- If the application is complex, with complex URLs, I create an a mapping between URLs and handler classes.
As seen in the below MVP code, I generally find that I like class based routes when I will implement many of the REST API verbs, most likely around a resource like a database table. This is also beneficial for clarity when using other decorators, and you can also choose which verb may have a certain decorator.
Alternately, I will use a decorated method to implement a route for returning html for static pages, and I only really need the GET verb.
from flask import Flaskfrom flask_restful import Api, Resource, reqparse, abort, fields, marshal_withfrom flask_sqlalchemy import SQLAlchemyapp = Flask(__name__)api = Api(app)app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db'db = SQLAlchemy(app)class VideoModel(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) views = db.Column(db.Integer, nullable=False) likes = db.Column(db.Integer, nullable=False) def __repr__(self): return f"Video(name = {name}, views = {views}, likes = {likes})"video_put_args = reqparse.RequestParser()video_put_args.add_argument("name", type=str, help="Name of the video is required", required=True)video_put_args.add_argument("views", type=int, help="Views of the video", required=True)video_put_args.add_argument("likes", type=int, help="Likes on the video", required=True)video_update_args = reqparse.RequestParser()video_update_args.add_argument("name", type=str, help="Name of the video is required")video_update_args.add_argument("views", type=int, help="Views of the video")video_update_args.add_argument("likes", type=int, help="Likes on the video")resource_fields = { 'id': fields.Integer, 'name': fields.String, 'views': fields.Integer, 'likes': fields.Integer}class Video(Resource): @marshal_with(resource_fields) def get(self, video_id): result = VideoModel.query.filter_by(id=video_id).first() if not result: abort(404, message="Could not find video with that id") return result @marshal_with(resource_fields) def put(self, video_id): args = video_put_args.parse_args() result = VideoModel.query.filter_by(id=video_id).first() if result: abort(409, message="Video id taken...") video = VideoModel(id=video_id, name=args['name'], views=args['views'], likes=args['likes']) db.session.add(video) db.session.commit() return video, 201 @marshal_with(resource_fields) def patch(self, video_id): args = video_update_args.parse_args() result = VideoModel.query.filter_by(id=video_id).first() if not result: abort(404, message="Video doesn't exist, cannot update") if args['name']: result.name = args['name'] if args['views']: result.views = args['views'] if args['likes']: result.likes = args['likes'] db.session.commit() return result def delete(self, video_id): abort_if_video_id_doesnt_exist(video_id) del videos[video_id] return '', 204api.add_resource(Video, "/video/<int:video_id>")if __name__ == "__main__": app.run(debug=True)