summaryrefslogtreecommitdiff
path: root/animism-align/cli/app/server
diff options
context:
space:
mode:
Diffstat (limited to 'animism-align/cli/app/server')
-rw-r--r--animism-align/cli/app/server/__pycache__/decorators.cpython-37.pycbin0 -> 4476 bytes
-rw-r--r--animism-align/cli/app/server/__pycache__/helpers.cpython-37.pycbin0 -> 1339 bytes
-rw-r--r--animism-align/cli/app/server/__pycache__/socket.cpython-37.pycbin0 -> 3602 bytes
-rw-r--r--animism-align/cli/app/server/__pycache__/web.cpython-37.pycbin0 -> 2555 bytes
-rw-r--r--animism-align/cli/app/server/decorators.py127
-rw-r--r--animism-align/cli/app/server/helpers.py78
-rw-r--r--animism-align/cli/app/server/representations.py12
-rw-r--r--animism-align/cli/app/server/web.py59
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
new file mode 100644
index 0000000..712f13a
--- /dev/null
+++ b/animism-align/cli/app/server/__pycache__/decorators.cpython-37.pyc
Binary files differ
diff --git a/animism-align/cli/app/server/__pycache__/helpers.cpython-37.pyc b/animism-align/cli/app/server/__pycache__/helpers.cpython-37.pyc
new file mode 100644
index 0000000..0b37fd7
--- /dev/null
+++ b/animism-align/cli/app/server/__pycache__/helpers.cpython-37.pyc
Binary files differ
diff --git a/animism-align/cli/app/server/__pycache__/socket.cpython-37.pyc b/animism-align/cli/app/server/__pycache__/socket.cpython-37.pyc
new file mode 100644
index 0000000..15a5126
--- /dev/null
+++ b/animism-align/cli/app/server/__pycache__/socket.cpython-37.pyc
Binary files differ
diff --git a/animism-align/cli/app/server/__pycache__/web.cpython-37.pyc b/animism-align/cli/app/server/__pycache__/web.cpython-37.pyc
new file mode 100644
index 0000000..7a43dff
--- /dev/null
+++ b/animism-align/cli/app/server/__pycache__/web.cpython-37.pyc
Binary files differ
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