diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2020-06-23 23:18:07 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2020-06-23 23:18:07 +0200 |
| commit | 3cf70771cb45cc16ec33ffe44e7a1a4799d8f395 (patch) | |
| tree | 55f0edb53141d5f043b486d722f507bfd94abdea /animism-align/cli/app/server | |
| parent | 014816dc724c1be60b7dd28d4e608c89b4ed451c (diff) | |
adding web app base
Diffstat (limited to 'animism-align/cli/app/server')
| -rw-r--r-- | animism-align/cli/app/server/__pycache__/decorators.cpython-37.pyc | bin | 0 -> 4476 bytes | |||
| -rw-r--r-- | animism-align/cli/app/server/__pycache__/helpers.cpython-37.pyc | bin | 0 -> 1339 bytes | |||
| -rw-r--r-- | animism-align/cli/app/server/__pycache__/socket.cpython-37.pyc | bin | 0 -> 3602 bytes | |||
| -rw-r--r-- | animism-align/cli/app/server/__pycache__/web.cpython-37.pyc | bin | 0 -> 2555 bytes | |||
| -rw-r--r-- | animism-align/cli/app/server/decorators.py | 127 | ||||
| -rw-r--r-- | animism-align/cli/app/server/helpers.py | 78 | ||||
| -rw-r--r-- | animism-align/cli/app/server/representations.py | 12 | ||||
| -rw-r--r-- | animism-align/cli/app/server/web.py | 59 |
8 files changed, 276 insertions, 0 deletions
diff --git a/animism-align/cli/app/server/__pycache__/decorators.cpython-37.pyc b/animism-align/cli/app/server/__pycache__/decorators.cpython-37.pyc Binary files differnew file mode 100644 index 0000000..712f13a --- /dev/null +++ b/animism-align/cli/app/server/__pycache__/decorators.cpython-37.pyc diff --git a/animism-align/cli/app/server/__pycache__/helpers.cpython-37.pyc b/animism-align/cli/app/server/__pycache__/helpers.cpython-37.pyc Binary files differnew file mode 100644 index 0000000..0b37fd7 --- /dev/null +++ b/animism-align/cli/app/server/__pycache__/helpers.cpython-37.pyc diff --git a/animism-align/cli/app/server/__pycache__/socket.cpython-37.pyc b/animism-align/cli/app/server/__pycache__/socket.cpython-37.pyc Binary files differnew file mode 100644 index 0000000..15a5126 --- /dev/null +++ b/animism-align/cli/app/server/__pycache__/socket.cpython-37.pyc diff --git a/animism-align/cli/app/server/__pycache__/web.cpython-37.pyc b/animism-align/cli/app/server/__pycache__/web.cpython-37.pyc Binary files differnew file mode 100644 index 0000000..7a43dff --- /dev/null +++ b/animism-align/cli/app/server/__pycache__/web.cpython-37.pyc diff --git a/animism-align/cli/app/server/decorators.py b/animism-align/cli/app/server/decorators.py new file mode 100644 index 0000000..2e6f9dd --- /dev/null +++ b/animism-align/cli/app/server/decorators.py @@ -0,0 +1,127 @@ +""" +These decorator functions wrap APIs for the simple search server. +""" + +import os +from time import time +from datetime import datetime +import numpy as np +from PIL import Image +from flask import request, jsonify +from werkzeug.utils import secure_filename + +from app.sql.common import Session + +DEFAULT_LIMIT = 30 + +def api_query(f): + """Wrap basic API queries with timing""" + def wrap_api_query(*args, **kwargs): + start = time() + query = {} + kwargs['query'] = query + results = f(*args, **kwargs) + query['timing'] = round(time() - start, 2) + if 'crop' in kwargs['query'] and kwargs['query']['crop'] is None: + del kwargs['query']['crop'] + else: + crop = kwargs['query']['crop'] + kwargs['query']['crop'] = { + 'x': crop[0], + 'y': crop[1], + 'w': crop[2], + 'h': crop[3], + } + if isinstance(results, list): + return { 'query': query, 'res': results } + else: + return { 'query': query, 'res': results } + wrap_api_query.__name__ = f.__name__ + return wrap_api_query + +def db_session(f): + """Wrap API queries in a database session object which gets committed""" + def wrap_db_session(*args, **kwargs): + session = Session() + try: + kwargs['session'] = session + f(*args, **kwargs) + session.commit() + except: + session.rollback() + raise + finally: + session.close() + wrap_db_session.__name__ = f.__name__ + return wrap_db_session + +def get_offset_and_limit(f): + """Normalize offset/limit query string params""" + def wrap_offset(*args, **kwargs): + kwargs['query']['offset'] = request.args.get('offset', default=0, type=int) + kwargs['query']['limit'] = request.args.get('limit', default=DEFAULT_LIMIT, type=int) + x = float(request.args.get('x', default=0.0, type=float)) + y = float(request.args.get('y', default=0.0, type=float)) + w = float(request.args.get('w', default=0.0, type=float)) + h = float(request.args.get('h', default=0.0, type=float)) + if w != 0.0 and h != 0.0: + kwargs['query']['crop'] = (x, y, w, h,) + else: + kwargs['query']['crop'] = None + return f(*args, **kwargs) + wrap_offset.__name__ = f.__name__ + return wrap_offset + +def store_uploaded_image(param_name, store=False, uploaded_im_path='static/data/uploaded'): + """Retrive an uploaded image and prepare for processing. Optionally store it to disk.""" + def decorator(f): + def wrap_uploaded_image(*args, **kwargs): + if param_name not in request.files: + raise APIError('No file uploaded') + + file = request.files[param_name] + + # convert string of image data to uint8 + nparr = np.fromstring(file.read(), np.uint8) + + # decode image + im = Image.fromarray(nparr) + kwargs['im'] = im + + if store: + uploaded_im_fn = secure_filename(datetime.now().isoformat() + "_" + file.filename) + uploaded_im_abspath = os.path.join(uploaded_im_path, uploaded_im_fn) + uploaded_im_remote_path = os.path.join('/', uploaded_im_path, uploaded_im_fn) + nparr.tofile(uploaded_im_abspath) + kwargs['query']['url'] = uploaded_im_remote_path + return f(*args, **kwargs) + wrap_uploaded_image.__name__ = f.__name__ + return wrap_uploaded_image + return decorator + +def as_json(f): + """Output an API query as JSON""" + def wrap_jsonify(*args, **kwargs): + return jsonify(f(*args, **kwargs)) + wrap_jsonify.__name__ = f.__name__ + return wrap_jsonify + +def exception_handler(f): + """Handle exceptions caused by the API""" + def wrapper(*args, **kwargs): + try: + return f(*args, **kwargs) + except Exception as e: + return { + "error": True, + "message": e.message, + "query": kwargs['query'], + } + wrapper.__name__ = f.__name__ + return wrapper + +class APIError(Exception): + def __init__(self, message): + self.message = message + def __str__(self): + return repr(self.message) diff --git a/animism-align/cli/app/server/helpers.py b/animism-align/cli/app/server/helpers.py new file mode 100644 index 0000000..107a7fa --- /dev/null +++ b/animism-align/cli/app/server/helpers.py @@ -0,0 +1,78 @@ +from sqlalchemy import and_ +from app.settings import app_cfg +import datetime +import dateutil + +def parse_search_args(args): + try: + limit = min(int(args.get('limit', default=app_cfg.DEFAULT_LIMIT)), app_cfg.MAX_LIMIT) + except Exception as e: + limit = app_cfg.DEFAULT_LIMIT + try: + offset = int(args.get('offset', default=0)) + except Exception as e: + offset = 0 + return offset, limit + +def parse_sort_args(args, table=None, default_sort="id", default_order="asc"): + try: + sort = args.get('sort', default=default_sort) + except Exception as e: + sort = 'id' + try: + order = args.get('order', default=default_order) + except: + order = 'asc' + if table is not None: + column = getattr(table, sort) + order_by = getattr(column, order)() + if sort != 'id': + order_by_id = getattr(table.id, order)() + else: + order_by_id = None + else: + order_by = None + order_by_id = None + return sort, order, order_by, order_by_id + +def parse_crop_arg(args): + try: + x = float(args.get('x')) + y = float(args.get('y')) + w = float(args.get('w')) + h = float(args.get('h')) + crop = (x, y, w, h,) + except Exception as e: + crop = None + return crop + +# def parse_media_args(args): +# criteria = [] +# criteria_kwargs = {} +# try: +# import_id = int(args.get('import_id')) +# criteria.append(Media.import_id == import_id) +# criteria_kwargs['import_id_1'] = import_id +# except: +# pass +# try: +# start_date = str(args.get('start_date')) +# start_date = dateutil.parser.parse(start_date) +# start_date = start_date.replace(tzinfo=datetime.timezone.utc) +# criteria.append(Media.created_at >= start_date) +# criteria_kwargs['created_at_1'] = start_date +# except: +# pass +# try: +# end_date = str(args.get('end_date')) +# end_date = dateutil.parser.parse(end_date) +# end_date = end_date.replace(tzinfo=datetime.timezone.utc) +# criteria.append(Media.created_at <= end_date) +# criteria_kwargs['created_at_2'] = end_date +# except: +# pass +# if len(criteria) > 1: +# return criteria, criteria_kwargs +# elif len(criteria) == 1: +# return criteria, criteria_kwargs +# return None, {} diff --git a/animism-align/cli/app/server/representations.py b/animism-align/cli/app/server/representations.py new file mode 100644 index 0000000..76d9ed4 --- /dev/null +++ b/animism-align/cli/app/server/representations.py @@ -0,0 +1,12 @@ +import simplejson as json +from flask import make_response + +def output_json(data, code, headers=None): + content_type = 'application/json' + dumped = json.dumps(data) + if headers: + headers.update({'Content-Type': content_type}) + else: + headers = {'Content-Type': content_type} + response = make_response(dumped, code, headers) + return response diff --git a/animism-align/cli/app/server/web.py b/animism-align/cli/app/server/web.py new file mode 100644 index 0000000..c3a812a --- /dev/null +++ b/animism-align/cli/app/server/web.py @@ -0,0 +1,59 @@ +import os +import logging +import logging.handlers + +logger = logging.getLogger("") +logger.setLevel(logging.DEBUG) +handler = logging.handlers.RotatingFileHandler("flask.log", + maxBytes=3000000, backupCount=2) +formatter = logging.Formatter( + '[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s') +handler.setFormatter(formatter) +logger.addHandler(handler) +logging.getLogger().addHandler(logging.StreamHandler()) + +from flask import Flask, Blueprint, send_from_directory, request +from app.sql.common import db, connection_url + +from app.settings import app_cfg +from app.controllers.upload_controller import UploadView + +def create_app(script_info=None): + """ + functional pattern for creating the flask app + """ + logging.debug("Starting Flask app...") + + app = Flask(__name__, static_folder=app_cfg.DIR_STATIC, static_url_path='/static') + app.config['SQLALCHEMY_DATABASE_URI'] = connection_url + app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + app.config['SERVER_NAME'] = app_cfg.SERVER_NAME + app.url_map.strict_slashes = False + + db.init_app(app) + + UploadView.register(app, route_prefix='/api/v1/') + + index_html = 'index.html' + + @app.errorhandler(404) + def page_not_found(e): + return app.send_static_file(index_html), 200 + # path = os.path.join(os.path.dirname(__file__), './static/index.html') + # with open(path, "r") as f: + # return f.read(), 200 + + @app.route('/', methods=['GET']) + def index(): + return app.send_static_file('index.html') + + @app.route('/favicon.ico') + def favicon(): + return send_from_directory(os.path.join(app.root_path, 'static/img/'), + 'favicon.ico',mimetype='image/vnd.microsoft.icon') + + @app.shell_context_processor + def shell_context(): + return { 'app': app, 'db': db } + + return app |
