""" 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)