Skip to content

Commit

Permalink
Use Flask-RestX
Browse files Browse the repository at this point in the history
Fixes: fedora-infra#25

Signed-off-by: Aurélien Bompard <aurelien@bompard.org>
  • Loading branch information
abompard committed Apr 28, 2020
1 parent 0f80206 commit f2f6ef3
Show file tree
Hide file tree
Showing 19 changed files with 354 additions and 239 deletions.
46 changes: 46 additions & 0 deletions fasjson/web/apis/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import ldap
from flask import jsonify
from flask_restx import Api, abort
from werkzeug.exceptions import HTTPException


def handle_ldap_local_error(error):
return ({"message": "LDAP local error", "details": str(error)}, 500)


def handle_ldap_server_error(error):
return {"message": "LDAP server is down"}, 500


def handle_webserver_error(code):
"""Generate JSON on Apache-generated errors (or whichever webserver is used)."""
abort(code)


class FasJsonApi(Api):
def init_app(self, app, **kwargs):
super().init_app(app, **kwargs)
self.errorhandler(ldap.LOCAL_ERROR)(handle_ldap_local_error)
self.errorhandler(ldap.SERVER_DOWN)(handle_ldap_server_error)
self.blueprint.record_once(self._on_blueprint_registration)

def _on_blueprint_registration(self, state):
# Add an URL rule on the top level app
state.app.add_url_rule(
f"/specs/{self.blueprint.name}.json",
endpoint=f"{self.blueprint.name}.spec",
view_func=self._view_spec,
)

# TODO: make sure the following two instructions are not done multiple times when we have
# multiple API versions.

# Make the main app's error handler use the API's error handler in order to output JSON
state.app.register_error_handler(HTTPException, self.handle_error)
# Register views for the webserver to use so that it outputs JSON too
state.app.add_url_rule(
"/errors/<int:code>", view_func=handle_webserver_error
)

def _view_spec(self):
return jsonify(self.__schema__)
19 changes: 19 additions & 0 deletions fasjson/web/apis/v1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from flask import Blueprint

from .base import FasJsonApi
from ..resources.me import api_v1 as me
from ..resources.users import api_v1 as users
from ..resources.groups import api_v1 as groups

blueprint = Blueprint("v1", __name__, url_prefix="/v1")
api = FasJsonApi(
blueprint,
title="FASJSON",
version="1.0",
description="The FASJSON API",
doc="/doc/",
)

api.add_namespace(me)
api.add_namespace(users)
api.add_namespace(groups)
1 change: 0 additions & 1 deletion fasjson/web/apis/v1/__init__.py

This file was deleted.

16 changes: 0 additions & 16 deletions fasjson/web/apis/v1/app.py

This file was deleted.

4 changes: 0 additions & 4 deletions fasjson/web/apis/v1/resources/__init__.py

This file was deleted.

68 changes: 0 additions & 68 deletions fasjson/web/apis/v1/resources/groups.py

This file was deleted.

17 changes: 0 additions & 17 deletions fasjson/web/apis/v1/resources/me.py

This file was deleted.

9 changes: 0 additions & 9 deletions fasjson/web/apis/v1/resources/root.py

This file was deleted.

22 changes: 0 additions & 22 deletions fasjson/web/apis/v1/resources/users.py

This file was deleted.

62 changes: 30 additions & 32 deletions fasjson/web/app.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,49 @@
from flask import Flask, request
import re

from flask import Flask, jsonify, url_for
from werkzeug.routing import BaseConverter

from . import errors
from .apis import v1
from .response import ApiResponse
from .apis.v1 import blueprint as blueprint_v1

from .extensions.flask_gss import FlaskGSSAPI
from .extensions.flask_ipacfg import IPAConfig


app = Flask(__name__)
app.response_class = ApiResponse

# extensions

# Extensions
FlaskGSSAPI(app)
IPAConfig(app)


# converters
class UserGroupConverter(BaseConverter):
# URL converters
class NameConverter(BaseConverter):
regex = "[a-zA-Z][a-zA-Z0-9_.-]{0,63}"


app.url_map.converters["usergroup"] = UserGroupConverter

# blueprints
app.register_blueprint(v1.app, url_prefix="/v1")


@app.errorhandler(errors.WebApiError)
def handle_error(e):
return e.get_response()
app.url_map.converters["name"] = NameConverter


@app.errorhandler(404)
def handle_error_404(e):
data = {"path": request.path, "method": request.method}
e = errors.WebApiError("resource not found", 404, data=data)
return e.get_response()
# TODO: consider having only one class per resource and passing the API version from the global g
# variable as described here:
# https://flask.palletsprojects.com/en/1.1.x/patterns/urlprocessors/#internationalized-blueprint-urls
app.register_blueprint(blueprint_v1)


@app.errorhandler(500)
def handle_error_500(e):
original = getattr(e, "original_exception", None)
data = {
"path": request.path,
"method": request.method,
"exception": str(original),
}
e = errors.WebApiError("unexpected internal error", 500, data=data)
return e.get_response()
@app.route("/")
def root():
blueprints = sorted(
[name for name in app.blueprints if re.match("^v[0-9]+$", name)],
key=lambda name: int(name[1:]),
)
apis = [
{
"version": int(name[1:]),
"uri": url_for(f"{name}.root", _external=True),
"spec": url_for(f"{name}.spec", _external=True),
"doc": url_for(f"{name}.doc", _external=True),
}
for name in blueprints
]
return jsonify({"message": "Welcome to FASJSON", "apis": apis})
53 changes: 0 additions & 53 deletions fasjson/web/errors.py

This file was deleted.

Empty file.
7 changes: 7 additions & 0 deletions fasjson/web/resources/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from flask_restx import Namespace as RestXNamespace


class Namespace(RestXNamespace):
def marshal_with(self, *args, **kwargs):
kwargs.setdefault("envelope", "result")
return super().marshal_with(*args, **kwargs)
Loading

0 comments on commit f2f6ef3

Please sign in to comment.