#!/usr/bin/env python3 import tempfile import os from flask import request, jsonify, current_app, abort, Response, url_for from flask_classful import FlaskView, route from model import db, Default, AIModel, AIModelType, SVMDetails from minio.error import NoSuchKey from schemas import AIModelSchema, InfoSchema from marshmallow.exceptions import ValidationError from utils import storage, ensure_buckets, multipart_required from pyAudioAnalysis.audioTrainTest import load_model class SVMView(FlaskView): route_base = 'svm' aimodel_schema = AIModelSchema(many=False) info_schema = InfoSchema(many=False) MODEL_DIRECTORY = "model/" MEANS_DIRECTORY = "means/" @multipart_required def post(self): # get important data from the request try: info = self.info_schema.loads(request.form.get('info')) except ValidationError as e: abort(400, str(e)) # check for conflict m = AIModel.query.filter_by(id=info['id']).first() if m: abort(409) # get and validate file model_file = request.files['modelFile'] if model_file.content_length <= 0: abort(411, f"Content length for modelFile is not a positive integer or missing.") means_file = request.files['meansFile'] if means_file.content_length <= 0: abort(411, f"Content length for meansFile is not a positive integer or missing.") # create bucket if necessary ensure_buckets() # Temporarily save the file, because pyAudioAnalysis can only read files temp_model_handle, temp_model_filename = tempfile.mkstemp() temp_means_filename = temp_model_filename + "MEANS" os.close(temp_model_handle) # BRUUUUH model_file.save(temp_model_filename) means_file.save(temp_means_filename) try: _, _, _, classes, mid_window, mid_step, short_window, short_step, compute_beat \ = load_model(temp_model_filename) if info['target_class_name'] not in classes: abort(400, f"This model does not have a class named {info['target_class_name']}") # Because of pyAudiomeme the files already saved, so we just use the file uploader functions storage.connection.fput_object( current_app.config['MINIO_SVM_BUCKET_NAME'], self.MODEL_DIRECTORY + str(info['id']), temp_model_filename ) storage.connection.fput_object( current_app.config['MINIO_SVM_BUCKET_NAME'], self.MEANS_DIRECTORY + str(info['id']), temp_means_filename ) finally: os.remove(temp_model_filename) os.remove(temp_means_filename) m = AIModel(id=info['id'], type=AIModelType.svm, target_class_name=info['target_class_name']) d = SVMDetails( aimodel=m, mid_window=mid_window, mid_step=mid_step, short_window=short_window, short_step=short_step, compute_beat=compute_beat ) db.session.add(m) db.session.add(d) db.session.commit() return jsonify(self.aimodel_schema.dump(m)), 200 def delete(self, id_: str): if id_ == "$default": default = Default.query.filter_by(type=AIModelType.svm).first_or_404() m = default.aimodel else: m = AIModel.query.filter_by(type=AIModelType.svm, id=id_).first_or_404() storage.connection.remove_object(current_app.config['MINIO_SVM_BUCKET_NAME'], self.MEANS_DIRECTORY + str(m.id)) storage.connection.remove_object(current_app.config['MINIO_SVM_BUCKET_NAME'], self.MODEL_DIRECTORY + str(m.id)) db.session.delete(m) db.session.commit() return '', 204 # builtin file proxy @route('/file') def get_file(self, id_: str): if id_ == "$default": default = Default.query.filter_by(type=AIModelType.svm).first_or_404() m = default.aimodel else: m = AIModel.query.filter_by(type=AIModelType.svm, id=id_).first_or_404() if "means" in request.args: path = self.MEANS_DIRECTORY + str(m.id) else: path = self.MODEL_DIRECTORY + str(m.id) try: data = storage.connection.get_object(current_app.config['MINIO_SVM_BUCKET_NAME'], path) except NoSuchKey: abort(500, "The ID is stored in the database but not int the Object Store") return Response(data.stream(), mimetype=data.headers['Content-type'])