mirror of
https://github.com/YunoHost-Apps/ihatemoney_ynh.git
synced 2024-09-03 19:26:15 +02:00
API first draft: utils. (related to #27)
Introduces the "rest" module, with reusable utils for flask applications (will be packaged as a flask extension later on).
This commit is contained in:
parent
20f6f204cf
commit
4bb96b28de
4 changed files with 197 additions and 1 deletions
64
budget/api.py
Normal file
64
budget/api.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from flask import *
|
||||||
|
import werkzeug
|
||||||
|
|
||||||
|
from models import db, Project, Person, Bill
|
||||||
|
from utils import for_all_methods
|
||||||
|
|
||||||
|
from rest import RESTResource, DefaultHandler, need_auth # FIXME make it an ext
|
||||||
|
|
||||||
|
|
||||||
|
api = Blueprint("api", __name__, url_prefix="/api")
|
||||||
|
|
||||||
|
def check_project(*args, **kwargs):
|
||||||
|
"""Check the request for basic authentication for a given project.
|
||||||
|
|
||||||
|
Return the project if the authorization is good, False otherwise
|
||||||
|
"""
|
||||||
|
auth = request.authorization
|
||||||
|
|
||||||
|
# project_id should be contained in kwargs and equal to the username
|
||||||
|
if auth and "project_id" in kwargs and \
|
||||||
|
auth.username == kwargs["project_id"]:
|
||||||
|
project = Project.query.get(auth.username)
|
||||||
|
if project.password == auth.password:
|
||||||
|
return project
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectHandler(DefaultHandler):
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
return "get"
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
return "delete"
|
||||||
|
|
||||||
|
project_resource = RESTResource(
|
||||||
|
name="project",
|
||||||
|
route="/project",
|
||||||
|
app=api,
|
||||||
|
actions=["add", "update", "delete", "get"],
|
||||||
|
authentifier=check_project,
|
||||||
|
handler=ProjectHandler())
|
||||||
|
|
||||||
|
# projects: add, delete, edit, get
|
||||||
|
# GET /project/<id> → get
|
||||||
|
# PUT /project/<id> → add & edit
|
||||||
|
# DELETE /project/<id> → delete
|
||||||
|
|
||||||
|
# project members: list, add, delete
|
||||||
|
# GET /project/<id>/members → list
|
||||||
|
# POST /project/<id>/members/ → add
|
||||||
|
# PUT /project/<id>/members/<user_id> → edit
|
||||||
|
# DELETE /project/<id>/members/<user_id> → delete
|
||||||
|
|
||||||
|
# project bills: list, add, delete, edit, get
|
||||||
|
# GET /project/<id>/bills → list
|
||||||
|
# GET /project/<id>/bills/<bill_id> → get
|
||||||
|
# DELETE /project/<id>/bills/<bill_id> → delete
|
||||||
|
# POST /project/<id>/bills/ → add
|
||||||
|
|
||||||
|
|
||||||
|
# GET, PUT, DELETE: /<id> : Get, update and delete
|
||||||
|
# GET, POST: / Add & List
|
120
budget/rest.py
Normal file
120
budget/rest.py
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
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, actions, handler, authentifier):
|
||||||
|
"""
|
||||||
|
: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.
|
||||||
|
|
||||||
|
:handler:
|
||||||
|
The handler instance which will handle the requests
|
||||||
|
|
||||||
|
:authentifier:
|
||||||
|
callable checking the authentication. If specified, all the
|
||||||
|
methods will be checked against it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._route = route
|
||||||
|
self._handler = handler
|
||||||
|
self._name = name
|
||||||
|
self._identifier = "%s_id" % name
|
||||||
|
self._authentifier = authentifier
|
||||||
|
|
||||||
|
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._name)(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):
|
||||||
|
"""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 403 Forbidden 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
|
||||||
|
"""
|
||||||
|
def wrapper(func):
|
||||||
|
def wrapped(*args, **kwargs):
|
||||||
|
result = authentifier(*args, **kwargs)
|
||||||
|
if result:
|
||||||
|
if name:
|
||||||
|
kwargs[name] = result
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
raise werkzeug.exceptions.Forbidden()
|
||||||
|
return wrapped
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultHandler(object):
|
||||||
|
|
||||||
|
def add(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def update(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def list(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
pass
|
|
@ -1,11 +1,12 @@
|
||||||
from web import main, db, mail
|
from web import main, db, mail
|
||||||
import api
|
from api import api
|
||||||
|
|
||||||
from flask import *
|
from flask import *
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config.from_object("default_settings")
|
app.config.from_object("default_settings")
|
||||||
app.register_blueprint(main)
|
app.register_blueprint(main)
|
||||||
|
app.register_blueprint(api)
|
||||||
|
|
||||||
# db
|
# db
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
import inspect
|
||||||
|
|
||||||
from flask import redirect, url_for, session, request
|
from flask import redirect, url_for, session, request
|
||||||
from werkzeug.routing import HTTPException, RoutingException
|
from werkzeug.routing import HTTPException, RoutingException
|
||||||
|
|
||||||
|
@ -34,3 +36,12 @@ class Redirect303(HTTPException, RoutingException):
|
||||||
|
|
||||||
def get_response(self, environ):
|
def get_response(self, environ):
|
||||||
return redirect(self.new_url, 303)
|
return redirect(self.new_url, 303)
|
||||||
|
|
||||||
|
def for_all_methods(decorator):
|
||||||
|
"""Apply a decorator to all the methods of a class"""
|
||||||
|
def decorate(cls):
|
||||||
|
for name, method in inspect.getmembers(cls, inspect.ismethod):
|
||||||
|
setattr(cls, name, decorator(method))
|
||||||
|
return cls
|
||||||
|
return decorate
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue