summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorromangraef <roman.graef@gmail.com>2018-06-09 12:28:18 +0200
committerromangraef <roman.graef@gmail.com>2018-06-09 12:28:18 +0200
commitbb27efebb9ebb30e85e780758d259bcb72a699c4 (patch)
treefdeb8218f46c78ffd3800640308d4753f287af42
downloaddatapackmanagerapi-bb27efebb9ebb30e85e780758d259bcb72a699c4.tar.gz
datapackmanagerapi-bb27efebb9ebb30e85e780758d259bcb72a699c4.tar.bz2
datapackmanagerapi-bb27efebb9ebb30e85e780758d259bcb72a699c4.zip
Initial commit
-rw-r--r--api/__init__.py3
-rw-r--r--api/v1/db.py35
-rw-r--r--api/v1/server.py51
-rw-r--r--api/v1/util.py61
-rw-r--r--app.py16
-rw-r--r--requirements.txt3
6 files changed, 169 insertions, 0 deletions
diff --git a/api/__init__.py b/api/__init__.py
new file mode 100644
index 0000000..685c125
--- /dev/null
+++ b/api/__init__.py
@@ -0,0 +1,3 @@
+from .v1.server import bp as v1_blueprint
+
+bp = v1_blueprint
diff --git a/api/v1/db.py b/api/v1/db.py
new file mode 100644
index 0000000..3d226c5
--- /dev/null
+++ b/api/v1/db.py
@@ -0,0 +1,35 @@
+from peewee import SqliteDatabase, ForeignKeyField, CharField, Model, IntegerField
+
+db = SqliteDatabase('datapackmanager.db')
+
+
+class BaseModel(Model):
+ class Meta:
+ database = db
+
+
+class User(BaseModel):
+ name = CharField()
+ id = IntegerField(primary_key=True)
+
+
+class Category(BaseModel):
+ name = CharField()
+ id = IntegerField(primary_key=True)
+
+
+class DataPack(BaseModel):
+ id = IntegerField(primary_key=True)
+ name = CharField()
+ description = CharField(max_length=10000)
+ category = ForeignKeyField(Category)
+ author = ForeignKeyField(User)
+
+
+class Version(BaseModel):
+ name = CharField()
+ datapack = ForeignKeyField(DataPack)
+
+
+db.create_tables([DataPack, Category, Version, User])
+__all__ = ('DataPack', 'Category', 'Version', 'db')
diff --git a/api/v1/server.py b/api/v1/server.py
new file mode 100644
index 0000000..96ff8da
--- /dev/null
+++ b/api/v1/server.py
@@ -0,0 +1,51 @@
+from flask import Blueprint, jsonify, abort, make_response, request
+
+from .db import Category, DataPack
+from .util import model_paginator, query_paginator, ConstrainFailed
+
+bp = Blueprint("api v1", __name__)
+
+
+@bp.errorhandler(ConstrainFailed)
+def handler(e: ConstrainFailed):
+ return make_response(jsonify({
+ 'error': {
+ 'description': e.description,
+ 'parameter': e.parameter,
+ 'reason': e.reason,
+ }
+ }), 404)
+
+
+@bp.route('/version/')
+def version():
+ return jsonify({
+ 'version': '1.0.0',
+ 'deprecated': False,
+ })
+
+
+@bp.route('/list/categories/')
+def list_categories():
+ return model_paginator(Category, lambda cat: {
+ 'name': cat.name,
+ 'id': cat.id,
+ })
+
+
+@bp.route('/list/datapacks')
+def list_datapacks():
+ category = request.args.get('category', '')
+ query = DataPack.select()
+ if category != '':
+ query = query.join(Category).where(DataPack.category.id == category)
+ return query_paginator(query, lambda dp: {
+ 'name': dp.name,
+ 'id': dp.id,
+ 'category': dp.category.id,
+ })
+
+
+@bp.route('/<path:invalid_path>')
+def invalid_path(invalid_path):
+ abort(404)
diff --git a/api/v1/util.py b/api/v1/util.py
new file mode 100644
index 0000000..f2dddad
--- /dev/null
+++ b/api/v1/util.py
@@ -0,0 +1,61 @@
+import typing
+
+from flask import jsonify, request
+from peewee import Model, ModelSelect
+
+_V = typing.TypeVar('_V', bound=Model)
+
+
+def model_paginator(model: typing.Type[_V], mapping: typing.Callable[[_V], dict]):
+ return query_paginator(model.select(), mapping)
+
+
+class ConstrainFailed(Exception):
+ def __init__(self, description: str, parameter: str, reason: str):
+ self.reason = reason
+ self.parameter = parameter
+ self.description = description
+
+
+def try_or_fail_contrain(func: typing.Callable, parameter: str, value=None, default=None):
+ if value is None:
+ value = request.args.get(parameter, default)
+ try:
+ return func(value)
+ except BaseException as e:
+ func = func.__name__
+ raise ConstrainFailed(f"{func}({parameter}) failed with an exception", parameter,
+ f"{func} raised {type(e).__name__}")
+
+
+def max_constrain(max: int, parameter: str, value=None, default=None):
+ value = try_or_fail_contrain(int, parameter, value, default)
+ if value > max:
+ raise ConstrainFailed(f"requested {parameter} too big", parameter, f'> {max}')
+ return value
+
+
+def min_constrain(min: int, parameter: str, value=None, default=None):
+ value = try_or_fail_contrain(int, parameter, value, default)
+ if value < min:
+ raise ConstrainFailed(f"requested {parameter} too big", parameter, f'< {min}')
+ return value
+
+
+def query_paginator(query: ModelSelect, mapping: typing.Callable[[_V], dict]):
+ size = try_or_fail_contrain(int, 'size', default='50')
+ max_constrain(50, 'size', size)
+ min_constrain(2, 'size', size)
+ offset = try_or_fail_contrain(int, 'offset', default='0')
+ min_constrain(0, 'offset', offset)
+ res = []
+ for obj in query.limit(size + 1).offset(offset):
+ res.append(mapping(obj))
+ more = len(res) > size
+ if more:
+ res[-1:] = []
+ return jsonify({
+ 'more': more,
+ 'next': offset + size if more else None,
+ 'results': res,
+ })
diff --git a/app.py b/app.py
new file mode 100644
index 0000000..75f2211
--- /dev/null
+++ b/app.py
@@ -0,0 +1,16 @@
+from flask import Flask, abort
+
+from api import bp as api_blueprint
+
+app = Flask(__name__)
+
+
+@app.route('/')
+def index():
+ abort(404)
+
+
+app.register_blueprint(api_blueprint, url_prefix='/api')
+
+if __name__ == '__main__':
+ app.run()
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..55a911d
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+Flask
+peewee
+