Source code for mlapi.app

import json
# import sqlite3
import os
import logging
import coloredlogs
coloredlogs.install(level='DEBUG', format='%(levelname)s%(asctime)s:%(message)s',)

from flask import request, url_for, Response, jsonify, g, redirect
from flask_api import FlaskAPI, status, exceptions
from flask_api.decorators import set_parsers
from flask_cors import CORS
from flask_jwt import JWT, jwt_required, current_identity
from werkzeug.security import safe_str_cmp
from mlapi.modelRouter import ModelRouterClass

from mlapi.api_users_methods import create_user, delete_user, update_user
from mlapi.helpers import err_tmplt

from flask_api.parsers import JSONParser, URLEncodedParser, MultiPartParser
from mlapi.parsers.imageParser import JpegImageParser, PngImageParser

# from flask_httpauth import HTTPBasicAuth
from flask_bcrypt import Bcrypt
from .images import ImageStorage
from models.modelsHolder import ModelsHolderClass
from flask_sqlalchemy import SQLAlchemy

from .shellColors import ShellColors

bcrypt = ""
database = ""
app = ""
jwt = ""
MODELS_DIR = "./models/computed/"
white_list = [
    'http://localhost:3000', 'http://localhost:8000',
    'https://api.mlapi.io', 'https://demo.mlapi.io']

if os.popen('stty size', 'r').read():
    rows, columns = os.popen('stty size', 'r').read().split()
    columns = int(columns)
    print('\n' + columns * "=")
    print(((columns-len(__name__))//2) * "=" + ShellColors.HEADER + __name__ + ShellColors.ENDC + ((columns -1 - len(__name__))//2 + 1) * "=")
    print(columns * "=" + '\n')
else:
    print("======MLAPI.IO=======")

[docs]def reduce_uses(fn): '''A decorator function that invokes the DB uses-reducing function. ''' def W1(*args, **kwargs): errors = [] if current_identity and request: if dbc.get_users_available_uses(current_identity['user_id']) == 0: errors.append("No token uses left for this user") else: dbc.reduce_uses(current_identity['user_id']) return fn(errors=errors, *args,**kwargs) W1.__name__ = fn.__name__ return W1
[docs]def save_request(response, data_type=None, data=None): '''Function invoking request saving in database ''' try: dbc.save_request( request_type=request.method, request_url=request.url, response = response, user_id = current_identity['user_id'] if current_identity else 0, is_xhr = request.is_xhr, headers = str(request.headers), data_type = data_type, data = data) except: logging.warning("There was an error while saving request data to DB. {}".format(request))
[docs]def errors_handler(errors=None): '''Wrapper for logging errors ''' if errors: for e in errors: logging.error(e) return errors
[docs]def create_app(image_storage): '''Function returning Flask app instance ''' global bcrypt, database, jwt, app, dbc app = FlaskAPI(__name__) CORS(app, origins=white_list) models_holder_instance = ModelsHolderClass(MODELS_DIR) app_settings = os.getenv('APP_SETTINGS','db.config.DevelopmentConfig') app.config.from_object(app_settings) logging.info("Using {} settings.".format(app_settings)) bcrypt = Bcrypt(app) try: database = SQLAlchemy(app) except Exception: logging.critical("Couldn't connect to DB!") from db.dbConnection import DbConnectionClass dbc = DbConnectionClass(database, app.config) jwt = JWT(app, dbc.authenticate, dbc.identity) @app.route('/') def slash(): return redirect(url_for('root')) @app.route('/v2/', methods=['GET']) def root(): '''Function responding to request to "/v2/" route. Returns a list of possible routes. ''' text = { 'available_routes': [ "api.mlapi.io/v2/token - Check current token balance status [POST]", "api.mlapi.io/v2/cars - Car recognition NN[GET, POST]", "api.mlapi.io/v2/sentiment - Text Sentiment Analysis [GET, POST]", ] } return text @app.route('/v2/token', methods=['GET']) @jwt_required() def token(): '''Retrieve description of route or amount of uses left on token ''' return { "uses_left": str(dbc.get_users_available_uses(current_identity['user_id'])) } @app.route('/v2/user', methods=['POST', 'PATCH', 'DELETE']) @set_parsers(JSONParser) @jwt_required() def user(): '''Manage users. ''' ################################# #### METHODS FOR ADMIN USERS ONLY if request.method == 'POST': req = request.data logging.debug(req) uses = req['uses'] if 'uses' in req else 100 isa = req['is_admin'] if "is_admin" in req else False if 'email' not in req: return { 'error' : "No email given" }, status.HTTP_400_BAD_REQUEST return create_user(dbc, req['email'], uses, isa) elif request.method == 'DELETE': id = request.user_id return delete_user(id) #### METHODS FOR ADMIN USERS ONLY ################################# elif request.method == 'GET': pass elif request.method == 'PUT': pass ## Needed for unathorized pre-requests @app.route('/v2/cars', methods=['OPTIONS']) def cars_opt(): return "" @app.route('/v2/cars', methods=['GET', 'POST']) @jwt_required() @set_parsers(JSONParser, JpegImageParser, PngImageParser, MultiPartParser) @reduce_uses def cars(errors=None): '''Responds with predictions on the sent image on POST. Returns description on GET request. Accepted formats: image/jpeg, image/png, application/json with an image in Base64 format. ''' if errors: return {"result":errors_handler(errors)} model_name = 'cars' logging.debug("REQUEST: {}".format(repr(request))) if request.method == 'GET': return { "description" : "Make an authenticated POST request for predicting the image. POST binary file with proper header or { 'image' : 'BASE64 image' }", "accepted_content_type" : [ "image/jpeg", "image/png", "application/json" ] } elif request.method == 'POST': # logging.debug("Got files from client: >>> {} <<<".format(request.files)) if request.files: val = request.files['file'] path = image_storage.save(val, request.headers['Content-Type']) response = { "result" : models_holder_instance.sendRequest(model_name, path) } save_request( response = str(response['result']), data_type = "I", data = path) return response elif request.data: path = image_storage.save(request.data, request.headers['Content-Type']) response = { "result" : models_holder_instance.sendRequest(model_name, path) } save_request( response = str(response['result']), data_type = "I", data = path) return response else: return { "result":"You provided no data" } ## Needed for unathorized pre-requests @app.route('/v2/sentiment', methods=['OPTIONS']) def sentiment_opt(): return "" @app.route('/v2/sentiment', methods=['GET', 'POST']) @jwt_required() @reduce_uses def sentiment(errors=None): '''Responds with predictions on the sent text on POST. Returns description on GET request. Accepted formats: application/json ''' if errors: return {"result":errors_handler(errors)} model_name = 'sentiment' if request.method == 'GET': return { "description" : "Make an authenticated POST request for predicting the text. { 'text' : 'Text to predict' }", "accepted_content_type" : [ "application/json" ] } elif request.method == 'POST': if request.data: response = { "result": models_holder_instance.sendRequest(model_name, request.data) } save_request( response = str(response['result']), data_type = "T", data = request.data['text']) return response else: return { "result":"You provided no data" } return app, bcrypt, database, image_storage, jwt
def get_app(): image_storage = ImageStorage(storage_path = "./images/") return create_app(image_storage) if __name__ == "mlapi.app": app, bcrypt, database, image_storage, jwt = application, bcrypt, database, image_storage, jwt = get_app()