jfinkels / flask-restless

NO LONGER MAINTAINED - A Flask extension for creating simple ReSTful JSON APIs from SQLAlchemy models.
https://flask-restless.readthedocs.io
GNU Affero General Public License v3.0
1.02k stars 301 forks source link

Best way to decorate a view function #318

Open mmautner opened 10 years ago

mmautner commented 10 years ago

I was investigating issue #99 about how to cache API results and realized that the Flask-Restless pre-/post-processors are not the way to go as they cannot return a response in lieu of what the class-based view functions return.

What is the best way to then go about decorating the views that get added to the app object by the Flask-Restless blueprint?

Here's a go I had it, but am curious whether there's a better/more harmonious way to do it:

#!/usr/bin/env python

import hashlib
from functools import update_wrapper
from flask import Flask
from flask import request
import flask.ext.sqlalchemy
import flask.ext.restless
from werkzeug.contrib.cache import MemcachedCache
cache = MemcachedCache(['127.0.0.1:11211'])

def get_cache(func):
    def wrapper(*args, **kwargs):
        # < 250 chars for memcached
        cache_key = hashlib.md5(request.full_path).hexdigest()
        if request.method == 'GET' and cache.get(cache_key):
            return cache.get(cache_key)

        result = func(*args, **kwargs)

        if request.method == 'GET':
            cache.set(cache_key, result)
        return result
    return update_wrapper(wrapper, func)

db = flask.ext.sqlalchemy.SQLAlchemy()

class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode, unique=True)
    birth_date = db.Column(db.Date)

class Person2(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode, unique=True)
    birth_date = db.Column(db.Date)

def create_app():
    app = Flask(__name__)
    app.config['DEBUG'] = True
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
    db.init_app(app)

    db.drop_all(app=app)
    db.create_all(app=app)

    manager = flask.ext.restless.APIManager(app, flask_sqlalchemy_db=db)
    manager.create_api(Person, methods=['GET', 'POST', 'DELETE'])
    manager.create_api(Person2, methods=['GET', 'POST', 'DELETE'])

    # hackish view decoration:
    for model in [Person, Person2]:
        model_route = '{0}api0.{0}api'.format(model.__name__.lower())
        app.view_functions[model_route] = get_cache(app.view_functions[model_route])

    return app

if __name__ == "__main__":
    create_app().run(debug=True)
jfinkels commented 10 years ago

Would it help to allow the user to add decorator functions to the view methods (by extending the list stored at views.API.decorators)? Those decorators are applied at each request.

chfw commented 10 years ago

Why would a server need caching for the responses of PUT, POST and DELETE? Caching of GET response is my concern.

jfinkels commented 10 years ago

According to StackOverflow, if the HTTP POST request includes the appropriate headers, then the application running on the server may cache an HTTP response, whatever that may mean. I think it should be theoretically allowed even if not used often in practice. Plus it may be easier to allow a decorator on top of all methods, rather than on just one.

Perhaps Flask-Restless should provide some default caching, checking for the appropriate HTTP headers?

chfw commented 10 years ago

Given that I could select which HTTP method to do caching, I would support your idea of one decorator for four methods.

Internet Explorer had default caching, which took me some time to figure it out. So, personally I wouldn't want default caching unless I would like to have it.

In the example, I didn't see cache duration and cache storage. Hopefully, it's possible to use Flask-Cache.

reubano commented 3 years ago
...
model_route = '{0}api0.{0}api'.format(manager.collection_name(model))
...