2011-09-13 11:27:36 +02:00
|
|
|
import json
|
2011-09-13 11:52:11 +02:00
|
|
|
from flask import request
|
2011-09-13 18:15:07 +02:00
|
|
|
import werkzeug
|
2011-09-13 11:27:36 +02:00
|
|
|
|
2011-09-11 22:11:36 +02:00
|
|
|
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",}
|
|
|
|
|
2011-09-11 23:00:32 +02:00
|
|
|
def __init__(self, name, route, app, handler, authentifier=None,
|
|
|
|
actions=None, inject_name=None):
|
2011-09-11 22:11:36 +02:00
|
|
|
"""
|
|
|
|
: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
|
|
|
|
|
2011-09-13 11:52:11 +02:00
|
|
|
:actions:
|
2011-09-11 23:00:32 +02:00
|
|
|
Authorized actions. Optional. None means all.
|
2011-09-11 22:11:36 +02:00
|
|
|
|
|
|
|
:handler:
|
|
|
|
The handler instance which will handle the requests
|
|
|
|
|
|
|
|
:authentifier:
|
2011-09-13 11:52:11 +02:00
|
|
|
callable checking the authentication. If specified, all the
|
2011-09-11 22:11:36 +02:00
|
|
|
methods will be checked against it.
|
|
|
|
"""
|
2011-09-11 23:00:32 +02:00
|
|
|
if not actions:
|
|
|
|
actions = self._VERBS.keys()
|
2011-09-11 22:11:36 +02:00
|
|
|
|
|
|
|
self._route = route
|
|
|
|
self._handler = handler
|
|
|
|
self._name = name
|
|
|
|
self._identifier = "%s_id" % name
|
|
|
|
self._authentifier = authentifier
|
2011-09-11 23:00:32 +02:00
|
|
|
self._inject_name = inject_name # FIXME
|
2011-09-11 22:11:36 +02:00
|
|
|
|
|
|
|
for action in actions:
|
|
|
|
self.add_url_rule(app, action)
|
2011-09-13 11:52:11 +02:00
|
|
|
|
2011-09-11 22:11:36 +02:00
|
|
|
def _get_route_for(self, action):
|
|
|
|
"""Return the complete URL for this action.
|
|
|
|
|
|
|
|
Basically:
|
2011-09-13 11:52:11 +02:00
|
|
|
|
2011-09-11 22:11:36 +02:00
|
|
|
- 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
|
2011-09-13 11:52:11 +02:00
|
|
|
|
2011-09-11 22:11:36 +02:00
|
|
|
return route
|
|
|
|
|
|
|
|
def add_url_rule(self, app, action):
|
2011-09-13 11:52:11 +02:00
|
|
|
"""Registers a new url to the given application, regarding
|
2011-09-11 22:11:36 +02:00
|
|
|
the action.
|
|
|
|
"""
|
|
|
|
method = getattr(self._handler, action)
|
|
|
|
|
|
|
|
# decorate the view
|
|
|
|
if self._authentifier:
|
2011-09-13 11:52:11 +02:00
|
|
|
method = need_auth(self._authentifier,
|
2011-09-11 23:00:32 +02:00
|
|
|
self._inject_name or self._name)(method)
|
2011-09-11 22:11:36 +02:00
|
|
|
|
2011-09-13 11:52:11 +02:00
|
|
|
method = serialize(method)
|
2011-09-13 11:27:36 +02:00
|
|
|
|
2011-09-11 22:11:36 +02:00
|
|
|
app.add_url_rule(
|
|
|
|
self._get_route_for(action),
|
|
|
|
"%s_%s" % (self._name, action),
|
|
|
|
method,
|
|
|
|
methods=[self._VERBS.get(action, "GET")])
|
|
|
|
|
|
|
|
|
2011-09-11 23:00:32 +02:00
|
|
|
def need_auth(authentifier, name=None, remove_attr=True):
|
2011-09-13 11:52:11 +02:00
|
|
|
"""Decorator checking that the authentifier does not returns false in
|
2011-09-11 22:11:36 +02:00
|
|
|
the current context.
|
|
|
|
|
|
|
|
If the request is authorized, the object returned by the authentifier
|
|
|
|
is added to the kwargs of the method.
|
|
|
|
|
2011-10-08 13:22:18 +02:00
|
|
|
If not, issue a 401 Unauthorized error
|
2011-09-11 22:11:36 +02:00
|
|
|
|
|
|
|
: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
|
2011-09-11 23:00:32 +02:00
|
|
|
|
|
|
|
:remove_attr:
|
2011-09-13 11:52:11 +02:00
|
|
|
Remove or not the `*name*_id` from the kwargs before calling the
|
2011-09-11 23:00:32 +02:00
|
|
|
function
|
2011-09-11 22:11:36 +02:00
|
|
|
"""
|
|
|
|
def wrapper(func):
|
|
|
|
def wrapped(*args, **kwargs):
|
|
|
|
result = authentifier(*args, **kwargs)
|
|
|
|
if result:
|
|
|
|
if name:
|
|
|
|
kwargs[name] = result
|
2011-09-11 23:00:32 +02:00
|
|
|
if remove_attr:
|
|
|
|
del kwargs["%s_id" % name]
|
2011-09-11 22:11:36 +02:00
|
|
|
return func(*args, **kwargs)
|
|
|
|
else:
|
2011-10-08 13:22:18 +02:00
|
|
|
return 401, "Unauthorized"
|
2011-09-11 22:11:36 +02:00
|
|
|
return wrapped
|
|
|
|
return wrapper
|
2011-09-13 11:27:36 +02:00
|
|
|
|
|
|
|
# serializers
|
|
|
|
|
2011-09-13 11:52:11 +02:00
|
|
|
def serialize(func):
|
2011-09-13 18:15:07 +02:00
|
|
|
"""If the object returned by the view is not already a Response, serialize
|
|
|
|
it using the ACCEPT header and return it.
|
|
|
|
"""
|
2011-09-13 11:52:11 +02:00
|
|
|
def wrapped(*args, **kwargs):
|
2011-09-13 18:15:07 +02:00
|
|
|
# get the mimetype
|
2011-10-08 13:22:18 +02:00
|
|
|
mime = request.accept_mimetypes.best_match(SERIALIZERS.keys()) or "text/json"
|
2011-09-13 18:15:07 +02:00
|
|
|
data = func(*args, **kwargs)
|
2011-09-13 22:58:53 +02:00
|
|
|
serializer = SERIALIZERS[mime]
|
2011-09-13 18:15:07 +02:00
|
|
|
|
2011-09-13 22:58:53 +02:00
|
|
|
status = 200
|
|
|
|
if len(data) == 2:
|
|
|
|
status, data = data
|
|
|
|
|
|
|
|
# serialize it
|
|
|
|
return werkzeug.Response(serializer.encode(data),
|
|
|
|
status=status, mimetype=mime)
|
2011-09-13 18:15:07 +02:00
|
|
|
|
2011-09-13 11:52:11 +02:00
|
|
|
return wrapped
|
2011-09-13 11:27:36 +02:00
|
|
|
|
|
|
|
|
|
|
|
class JSONEncoder(json.JSONEncoder):
|
2011-09-13 18:15:07 +02:00
|
|
|
"""Subclass of the default encoder to support custom objects"""
|
2011-09-13 11:27:36 +02:00
|
|
|
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
|
2011-09-13 18:15:07 +02:00
|
|
|
elif hasattr(o, "isoformat"):
|
|
|
|
return o.isoformat()
|
2011-09-13 11:27:36 +02:00
|
|
|
else:
|
|
|
|
return json.JSONEncoder.default(self, o)
|
|
|
|
|
2011-09-13 11:52:11 +02:00
|
|
|
SERIALIZERS = {"text/json": JSONEncoder()}
|