diff --git a/budget/api.py b/budget/api.py index b197b81..9d41b3c 100644 --- a/budget/api.py +++ b/budget/api.py @@ -6,7 +6,7 @@ from forms import (ProjectForm, EditProjectForm, MemberForm, BillForm, get_billform_for) from utils import for_all_methods -from rest import RESTResource, need_auth# FIXME make it an ext +from flask_rest import RESTResource, need_auth from werkzeug import Response diff --git a/budget/requirements.txt b/budget/requirements.txt index 16e3f11..1532b76 100644 --- a/budget/requirements.txt +++ b/budget/requirements.txt @@ -3,3 +3,4 @@ flask-wtf flask-sqlalchemy flask-mail flask-babel +flask-rest diff --git a/budget/rest.py b/budget/rest.py deleted file mode 100644 index 992a61e..0000000 --- a/budget/rest.py +++ /dev/null @@ -1,158 +0,0 @@ -import json -from flask import request -import werkzeug - -class RESTResource(object): - """Represents a REST resource, with the different HTTP verbs""" - _NEED_ID = ["get", "update", "delete"] - _VERBS = {"get": "GET", - "update": "PUT", - "delete": "DELETE", - "list": "GET", - "add": "POST",} - - def __init__(self, name, route, app, handler, authentifier=None, - actions=None, inject_name=None): - """ - :name: - name of the resource. This is being used when registering - the route, for its name and for the name of the id parameter - that will be passed to the views - - :route: - Default route for this resource - - :app: - Application to register the routes onto - - :actions: - Authorized actions. Optional. None means all. - - :handler: - The handler instance which will handle the requests - - :authentifier: - callable checking the authentication. If specified, all the - methods will be checked against it. - """ - if not actions: - actions = self._VERBS.keys() - - self._route = route - self._handler = handler - self._name = name - self._identifier = "%s_id" % name - self._authentifier = authentifier - self._inject_name = inject_name # FIXME - - for action in actions: - self.add_url_rule(app, action) - - def _get_route_for(self, action): - """Return the complete URL for this action. - - Basically: - - - get, update and delete need an id - - add and list does not - """ - route = self._route - - if action in self._NEED_ID: - route += "/<%s>" % self._identifier - - return route - - def add_url_rule(self, app, action): - """Registers a new url to the given application, regarding - the action. - """ - method = getattr(self._handler, action) - - # decorate the view - if self._authentifier: - method = need_auth(self._authentifier, - self._inject_name or self._name)(method) - - method = serialize(method) - - app.add_url_rule( - self._get_route_for(action), - "%s_%s" % (self._name, action), - method, - methods=[self._VERBS.get(action, "GET")]) - - -def need_auth(authentifier, name=None, remove_attr=True): - """Decorator checking that the authentifier does not returns false in - the current context. - - If the request is authorized, the object returned by the authentifier - is added to the kwargs of the method. - - If not, issue a 401 Unauthorized error - - :authentifier: - The callable to check the context onto. - - :name: - **Optional**, name of the argument to put the object into. - If it is not provided, nothing will be added to the kwargs - of the decorated function - - :remove_attr: - Remove or not the `*name*_id` from the kwargs before calling the - function - """ - def wrapper(func): - def wrapped(*args, **kwargs): - result = authentifier(*args, **kwargs) - if result: - if name: - kwargs[name] = result - if remove_attr: - del kwargs["%s_id" % name] - return func(*args, **kwargs) - else: - return 401, "Unauthorized" - return wrapped - return wrapper - -# serializers - -def serialize(func): - """If the object returned by the view is not already a Response, serialize - it using the ACCEPT header and return it. - """ - def wrapped(*args, **kwargs): - # get the mimetype - mime = request.accept_mimetypes.best_match(SERIALIZERS.keys()) or "text/json" - data = func(*args, **kwargs) - serializer = SERIALIZERS[mime] - - status = 200 - if len(data) == 2: - status, data = data - - # serialize it - return werkzeug.Response(serializer.encode(data), - status=status, mimetype=mime) - - return wrapped - - -class JSONEncoder(json.JSONEncoder): - """Subclass of the default encoder to support custom objects""" - def default(self, o): - if hasattr(o, "_to_serialize"): - # build up the object - data = {} - for attr in o._to_serialize: - data[attr] = getattr(o, attr) - return data - elif hasattr(o, "isoformat"): - return o.isoformat() - else: - return json.JSONEncoder.default(self, o) - -SERIALIZERS = {"text/json": JSONEncoder()}