diff --git a/.env.template b/.env.template
new file mode 100644
index 000000000..30a559a54
--- /dev/null
+++ b/.env.template
@@ -0,0 +1,14 @@
+# Server Configuration
+PORT=5002
+FLASK_APP=app.py
+FLASK_ENV=development
+FLASK_DEBUG=1
+
+# AWS Configuration (if applicable)
+S3_BUCKET=
+S3_KEY=
+S3_SECRET=
+AWS_SYNC=0
+
+# Deployment Configuration
+HEROKU_DEPLOY=0
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 0249686ea..6a37088a1 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -4,16 +4,24 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
+ {
+ "name": "MUIO Server",
+ "type": "debugpy",
+ "request": "launch",
+ "program": "${workspaceFolder}/API/app.py",
+ "console": "integratedTerminal",
+ "justMyCode": true
+ },
{
"name": "Python: Current File",
- "type": "python",
+ "type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
},
{
"name": "Python: Flask",
- "type": "python",
+ "type": "debugpy",
"request": "launch",
"module": "flask",
"env": {
@@ -24,7 +32,6 @@
},
"args": [
"run",
- //"--no-debugger",
"--no-reload"
],
"jinja": true
diff --git a/API/Classes/Base/Response.py b/API/Classes/Base/Response.py
new file mode 100644
index 000000000..a9c1f3c3b
--- /dev/null
+++ b/API/Classes/Base/Response.py
@@ -0,0 +1,26 @@
+from flask import jsonify
+
+def api_response(success=True, message=None, data=None, error=None, status_code=200):
+ """
+ Standardized API response helper for MUIO.
+
+ Args:
+ success (bool): Whether the request was successful.
+ message (str, optional): A human-readable message.
+ data (dict|list, optional): The actual payload.
+ error (str|dict, optional): Detailed error information.
+ status_code (int): HTTP status code.
+
+ Returns:
+ tuple: (flask.Response, int) - A JSON response compatible with Flask's return type.
+ """
+ if status_code == 200 and not success:
+ status_code = 400
+
+ response = {
+ "success": success,
+ "message": message,
+ "data": data,
+ "error": error
+ }
+ return jsonify(response), status_code
diff --git a/API/Routes/Case/CaseRoute.py b/API/Routes/Case/CaseRoute.py
index 51e0ec29c..249f06de1 100644
--- a/API/Routes/Case/CaseRoute.py
+++ b/API/Routes/Case/CaseRoute.py
@@ -1,10 +1,11 @@
-from flask import Blueprint, jsonify, request, session, send_file
+from flask import Blueprint, request, session, send_file
import os
from pathlib import Path
import shutil
import pandas as pd
from Classes.Base import Config
from Classes.Base.FileClass import File
+from Classes.Base.Response import api_response
from Classes.Case.CaseClass import Case
from Classes.Case.UpdateCaseClass import UpdateCase
from Classes.Case.ImportTemplate import ImportTemplate
@@ -22,21 +23,21 @@ def initSyncS3():
syncS3.downloadSync(case, Config.DATA_STORAGE, Config.S3_BUCKET)
#downoload param file from S3 bucket
syncS3.downloadSync('Parameters.json', Config.DATA_STORAGE, Config.S3_BUCKET)
- response = {
- "message": "Cases syncronized with S3 bucket!",
- "status_code": "success"
- }
- return jsonify(response), 200
+ return api_response(
+ success=True,
+ message="Cases syncronized with S3 bucket!",
+ status_code=200
+ )
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@case_api.route("/getCases", methods=['GET'])
def getCases():
try:
cases = [ f.name for f in os.scandir(Config.DATA_STORAGE) if f.is_dir() ]
- return jsonify(cases), 200
+ return api_response(success=True, data=cases, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@case_api.route("/getResultCSV", methods=['POST'])
def getResultCSV():
@@ -48,9 +49,9 @@ def getResultCSV():
csvs = [ f.name for f in os.scandir(csvFolder) ]
else:
csvs = []
- return jsonify(csvs), 200
+ return api_response(success=True, data=csvs, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@case_api.route("/getDesc", methods=['POST'])
def getDesc():
@@ -58,13 +59,9 @@ def getDesc():
casename = request.json['casename']
genDataPath = Path(Config.DATA_STORAGE,casename,"genData.json")
genData = File.readFile(genDataPath)
- response = {
- "message": "Get model description success",
- "desc": genData['osy-desc']
- }
- return jsonify(response), 200
+ return api_response(success=True, message="Get model description success", data={"desc": genData['osy-desc']}, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@case_api.route("/copyCase", methods=['POST'])
def copy():
@@ -77,25 +74,18 @@ def copy():
dest = Path(Config.DATA_STORAGE, case + '_copy')
if(os.path.isdir(dest)):
- response = {
- "message": 'Model '+ case + '_copy already exists, please rename existing model first!',
- "status_code": "warning"
- }
+ return api_response(success=False, message='Model '+ case + '_copy already exists, please rename existing model first!', data={"status_code": "warning"}, status_code=409)
else:
shutil.copytree(str(src), str(dest) )
#rename casename in genData
genData = File.readFile(casePath)
genData['osy-casename'] = case_copy
File.writeFile(genData, casePath)
- response = {
- "message": 'Model '+ case + ' copied!',
- "status_code": "success"
- }
- return(response)
+ return api_response(success=True, message='Model '+ case + ' copied!', status_code=201)
except(IOError):
- raise IOError
+ return api_response(success=False, message="Error copying model", status_code=500)
except OSError:
- raise OSError
+ return api_response(success=False, message="OS Error copying model", status_code=500)
@case_api.route("/deleteCase", methods=['POST'])
def deleteCase():
@@ -107,20 +97,13 @@ def deleteCase():
if case == session.get('osycase'):
session['osycase'] = None
- response = {
- "message": 'Model '+ case + ' deleted!',
- "status_code": "success_session"
- }
+ return api_response(success=True, message='Model '+ case + ' deleted!', data={"status_code": "success_session"}, status_code=200)
else:
- response = {
- "message": 'Model '+ case + ' deleted!',
- "status_code": "success"
- }
- return jsonify(response), 200
+ return api_response(success=True, message='Model '+ case + ' deleted!', status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
except OSError:
- raise OSError
+ return api_response(success=False, message="Error deleting case", status_code=500)
@case_api.route("/getResultData", methods=['POST'])
def getResultData():
@@ -131,12 +114,11 @@ def getResultData():
dataPath = Path(Config.DATA_STORAGE,casename,'view',dataJson)
data = File.readFile(dataPath)
response = data
-
else:
response = None
- return jsonify(response), 200
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@case_api.route("/getParamFile", methods=['POST'])
def getParamFile():
@@ -144,10 +126,9 @@ def getParamFile():
dataJson = request.json['dataJson']
configPath = Path(Config.DATA_STORAGE, dataJson)
ConfigFile = File.readParamFile(configPath)
- response = ConfigFile
- return jsonify(response), 200
+ return api_response(success=True, data=ConfigFile, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@case_api.route("/resultsExists", methods=['POST'])
def resultsExists():
@@ -168,9 +149,9 @@ def resultsExists():
else:
response = False
#response = True
- return jsonify(response), 200
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@case_api.route("/saveParamFile", methods=['POST'])
def saveParamFile():
@@ -182,14 +163,9 @@ def saveParamFile():
varPath = Path(Config.DATA_STORAGE, 'Variables.json')
File.writeFile( ParamData, paramPath)
File.writeFile( VarData, varPath)
- response = {
- "message": "You have updated parameters & variables data!",
- "status_code": "success"
- }
-
- return jsonify(response), 200
+ return api_response(success=True, message="You have updated parameters & variables data!", status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@case_api.route("/saveScOrder", methods=['POST'])
def saveScOrder():
@@ -200,14 +176,9 @@ def saveScOrder():
genData = File.readFile(genDataPath)
genData['osy-scenarios'] = data
File.writeFile( genData, genDataPath)
- response = {
- "message": "You have updated scenarios order data!",
- "status_code": "success"
- }
-
- return jsonify(response), 200
+ return api_response(success=True, message="You have updated scenarios order data!", status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@case_api.route("/updateData", methods=['POST'])
def updateData():
@@ -222,13 +193,9 @@ def updateData():
sourceData[param] = data
File.writeFile(sourceData, dataPath)
#File.writeFileUJson(sourceData, dataPath)
- response = {
- "message": "Your data has been saved!",
- "status_code": "success"
- }
- return jsonify(response), 200
+ return api_response(success=True, message="Your data has been saved!", status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@case_api.route("/saveCase", methods=['POST'])
def saveCase():
@@ -303,42 +270,24 @@ def saveCase():
#update modela
caseUpdate = UpdateCase(case, genData)
caseUpdate.updateCase()
-
- #update genData
File.writeFile( genData, genDataPath)
+ return api_response(success=True, message="Your model configuration has been updated!", data={"status_code": "edited"}, status_code=200)
- ###########################potrebno updateovati i resData ukoliko smo brisali ili dodavali scenarios
-
- response = {
- "message": "Your model configuration has been updated!",
- "status_code": "edited"
- }
#edit case sa drugim imenom, moramo provjeriit da li novo ime postoji u sistemu
else:
if not os.path.exists(Path(Config.DATA_STORAGE,casename)):
-
#update modela
caseUpdate = UpdateCase(case, genData)
caseUpdate.updateCase()
-
- #update gen data sa novim imenom
File.writeFile( genData, genDataPath)
-
- #nedostaje update resData u smislu novih ili izbirsanih scenarija
- #rename case sa novim imenom
os.rename(Path(Config.DATA_STORAGE,case), Path(Config.DATA_STORAGE,casename ))
session['osycase'] = casename
-
- response = {
- "message": "Your model configuration has been updated!",
- "status_code": "edited"
- }
+ return api_response(success=True, message="Your model configuration has been updated!", data={"status_code": "edited"}, status_code=200)
+
#ako vec postoji case sa istim imenom
else:
- response = {
- "message": "Model with same name already exists!",
- "status_code": "exist"
- }
+ return api_response(success=False, message="Model with same name already exists!", data={"status_code": "exist"}, status_code=409)
+
#novi case
else:
if not os.path.exists(Path(Config.DATA_STORAGE,casename)):
@@ -362,29 +311,17 @@ def saveCase():
os.makedirs(resPath, mode=0o777, exist_ok=False)
if not os.path.exists(viewPath):
os.makedirs(viewPath, mode=0o777, exist_ok=False)
- resData = {
- "osy-cases":[]
- }
+ resData = {"osy-cases":[]}
File.writeFile( resData, resDataPath)
-
- viewData = {
- "osy-views": viewDef
- }
+ viewData = {"osy-views": viewDef}
File.writeFile( viewData, viewDataPath)
- response = {
- "message": "Your model configuration has been saved!",
- "status_code": "created"
- }
- else:
- response = {
- "message": "Model with same name already exists!",
- "status_code": "exist"
- }
+ return api_response(success=True, message="Your model configuration has been saved!", data={"status_code": "created"}, status_code=201)
- return jsonify(response), 200
+ else:
+ return api_response(success=False, message="Model with same name already exists!", data={"status_code": "exist"}, status_code=409)
except(IOError):
- return jsonify('Error saving model IOError!'), 404
+ return api_response(success=False, message="Error saving model IOError!", status_code=404)
@case_api.route("/prepareCSV", methods=['POST'])
def prepareCSV():
@@ -406,14 +343,10 @@ def prepareCSV():
# Pd.to_excel(Path(Config.DATA_STORAGE,casename,'export.xlsx'))
- response = {
- "message": 'CSV data downloaded!',
- "status_code": "success"
- }
- return jsonify(response), 200
+ return api_response(success=True, message='CSV data downloaded!', status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@case_api.route("/downloadCSV", methods=['GET'])
def downloadCSV():
@@ -425,7 +358,7 @@ def downloadCSV():
return send_file(dataFile.resolve(), as_attachment=True,mimetype='application/csv', max_age=0)
#return send_from_directory(dir, 'export.csv', as_attachment=True)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@case_api.route("/importTemplate", methods=['POST'])
def run():
@@ -434,11 +367,11 @@ def run():
template = ImportTemplate(data["osy-template"])
response = template.importProcess(data)
- return jsonify(response), 200
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
except(IndexError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="Index Error during import", status_code=500)
####################################################################################OBSOLETE AND SyncS3###################################################
diff --git a/API/Routes/Case/SyncS3Route.py b/API/Routes/Case/SyncS3Route.py
index f6041765e..3c35798c6 100644
--- a/API/Routes/Case/SyncS3Route.py
+++ b/API/Routes/Case/SyncS3Route.py
@@ -1,9 +1,10 @@
-from flask import Blueprint, jsonify, request
+from flask import Blueprint, request
import os
from pathlib import Path
import shutil
from Classes.Base import Config
from Classes.Base.SyncS3 import SyncS3
+from Classes.Base.Response import api_response
syncs3_api = Blueprint('SyncS3Route', __name__)
@@ -17,15 +18,11 @@ def deleteResultsPreSync():
shutil.rmtree(resPath)
os.remove(dataPath)
- response = {
- "message": 'Case '+ case + ' deleted!',
- "status_code": "success"
- }
- return jsonify(response), 200
+ return api_response(success=True, message='Case '+ case + ' deleted!', status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
except OSError:
- raise OSError
+ return api_response(success=False, message="OS Error during sync deletion", status_code=500)
@syncs3_api.route("/uploadSync", methods=['POST'])
def uploadSync():
@@ -36,15 +33,11 @@ def uploadSync():
localDir = Path(Config.DATA_STORAGE, case)
s3.uploadSync(localDir, case, Config.S3_BUCKET, '*')
- response = {
- "message": 'Case '+ case + ' syncronized!',
- "status_code": "success"
- }
- return jsonify(response), 200
+ return api_response(success=True, message='Case '+ case + ' syncronized!', status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
except OSError:
- raise OSError
+ return api_response(success=False, message="OS Error during sync upload", status_code=500)
@syncs3_api.route("/deleteSync", methods=['POST'])
def deleteSync():
@@ -54,15 +47,11 @@ def deleteSync():
s3 = SyncS3()
s3.deleteSync(case)
- response = {
- "message": 'Case '+ case + ' deleted!',
- "status_code": "success"
- }
- return jsonify(response), 200
+ return api_response(success=True, message='Case '+ case + ' deleted!', status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
except OSError:
- raise OSError
+ return api_response(success=False, message="OS Error during sync deletion", status_code=500)
@syncs3_api.route("/updateSync", methods=['POST'])
def updateSync():
@@ -74,15 +63,11 @@ def updateSync():
localDir = Path(Config.DATA_STORAGE, case, str(filename))
s3.updateSync(localDir, case, Config.S3_BUCKET)
- response = {
- "message": 'Case '+ case + ' deleted!',
- "status_code": "success"
- }
- return jsonify(response), 200
+ return api_response(success=True, message='Case '+ case + ' deleted!', status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
except OSError:
- raise OSError
+ return api_response(success=False, message="OS Error during sync update", status_code=500)
@syncs3_api.route("/updateSyncParamFile", methods=['GET'])
def updateSyncParamFile():
@@ -94,12 +79,8 @@ def updateSyncParamFile():
s3.updateSync(localDir, case, Config.S3_BUCKET)
- response = {
- "message": 'Case '+ case + ' deleted!',
- "status_code": "success"
- }
- return jsonify(response), 200
+ return api_response(success=True, message='Case '+ case + ' deleted!', status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
except OSError:
- raise OSError
+ return api_response(success=False, message="OS Error during sync param update", status_code=500)
diff --git a/API/Routes/Case/ViewDataRoute.py b/API/Routes/Case/ViewDataRoute.py
index 6f442a9c2..1db72c4e1 100644
--- a/API/Routes/Case/ViewDataRoute.py
+++ b/API/Routes/Case/ViewDataRoute.py
@@ -1,5 +1,6 @@
-from flask import Blueprint, jsonify, request
+from flask import Blueprint, request
from Classes.Case.OsemosysClass import Osemosys
+from Classes.Base.Response import api_response
viewdata_api = Blueprint('ViewDataRoute', __name__)
@@ -16,9 +17,9 @@ def viewData():
response = data
else:
response = None
- return jsonify(response), 200
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@viewdata_api.route("/viewTEData", methods=['POST'])
def viewTEData():
@@ -32,9 +33,9 @@ def viewTEData():
response = data
else:
response = None
- return jsonify(response), 200
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@viewdata_api.route("/updateViewData", methods=['POST'])
def updateViewData():
@@ -55,19 +56,11 @@ def updateViewData():
if casename != None:
osy = Osemosys(casename)
osy.updateViewData(casename, year, ScId, groupId, paramId, TechId, CommId, EmisId, Timeslice, value)
- response = {
- "message": "You have updated view data!",
- "status_code": "success"
- }
+ return api_response(success=True, message="You have updated view data!", status_code=200)
else:
- response = {
- "message": "No case data selected!",
- "status_code": "error"
- }
-
- return jsonify(response), 200
+ return api_response(success=False, message="No case data selected!", status_code=400)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@viewdata_api.route("/updateTEViewData", methods=['POST'])
def updateTEViewData():
@@ -84,16 +77,8 @@ def updateTEViewData():
if casename != None:
osy = Osemosys(casename)
data = osy.updateTEViewData(casename, scId, groupId, paramId, techId, emisId, value)
- response = {
- "message": "You have updated view data!",
- "status_code": "success"
- }
+ return api_response(success=True, message="You have updated view data!", status_code=200)
else:
- response = {
- "message": "No case data selected!",
- "status_code": "error"
- }
-
- return jsonify(response), 200
+ return api_response(success=False, message="No case data selected!", status_code=400)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
diff --git a/API/Routes/DataFile/DataFileRoute.py b/API/Routes/DataFile/DataFileRoute.py
index 33709c092..b7564a657 100644
--- a/API/Routes/DataFile/DataFileRoute.py
+++ b/API/Routes/DataFile/DataFileRoute.py
@@ -1,8 +1,9 @@
-from flask import Blueprint, jsonify, request, send_file, session
+from flask import Blueprint, request, send_file, session
from pathlib import Path
import shutil, datetime, time, os
from Classes.Case.DataFileClass import DataFile
from Classes.Base import Config
+from Classes.Base.Response import api_response
datafile_api = Blueprint('DataFileRoute', __name__)
@@ -15,13 +16,9 @@ def generateDataFile():
if casename != None:
txtFile = DataFile(casename)
txtFile.generateDatafile(caserunname)
- response = {
- "message": "You have created data file!",
- "status_code": "success"
- }
- return jsonify(response), 200
+ return api_response(success=True, message="You have created data file!", status_code=201)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@datafile_api.route("/createCaseRun", methods=['POST'])
def createCaseRun():
@@ -34,9 +31,9 @@ def createCaseRun():
caserun = DataFile(casename)
response = caserun.createCaseRun(caserunname, data)
- return jsonify(response), 200
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@datafile_api.route("/updateCaseRun", methods=['POST'])
def updateCaseRun():
@@ -50,9 +47,9 @@ def updateCaseRun():
caserun = DataFile(casename)
response = caserun.updateCaseRun(caserunname, oldcaserunname, data)
- return jsonify(response), 200
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@datafile_api.route("/deleteCaseRun", methods=['POST'])
def deleteCaseRun():
@@ -75,24 +72,11 @@ def deleteCaseRun():
if casename != None:
caserun = DataFile(casename)
response = caserun.deleteCaseRun(caserunname, resultsOnly)
- return jsonify(response), 200
-
- # if casename == session.get('osycase'):
- # session['osycase'] = None
- # response = {
- # "message": 'Case '+ casename + ' deleted!',
- # "status_code": "success_session"
- # }
- # else:
- # response = {
- # "message": 'Case '+ casename + ' deleted!',
- # "status_code": "success"
- # }
- # return jsonify(response), 200
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
except OSError:
- raise OSError
+ return api_response(success=False, message="OS Error deleting case run", status_code=500)
@datafile_api.route("/deleteScenarioCaseRuns", methods=['POST'])
def deleteScenarioCaseRuns():
@@ -104,9 +88,9 @@ def deleteScenarioCaseRuns():
caserun = DataFile(casename)
response = caserun.deleteScenarioCaseRuns(scenarioId)
- return jsonify(response), 200
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@datafile_api.route("/saveView", methods=['POST'])
def saveView():
@@ -119,9 +103,9 @@ def saveView():
caserun = DataFile(casename)
response = caserun.saveView(data, param)
- return jsonify(response), 200
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@datafile_api.route("/updateViews", methods=['POST'])
def updateViews():
@@ -134,9 +118,9 @@ def updateViews():
caserun = DataFile(casename)
response = caserun.updateViews(data, param)
- return jsonify(response), 200
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@datafile_api.route("/readDataFile", methods=['POST'])
def readDataFile():
@@ -149,9 +133,9 @@ def readDataFile():
response = data
else:
response = None
- return jsonify(response), 200
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@datafile_api.route("/validateInputs", methods=['POST'])
def validateInputs():
@@ -164,9 +148,9 @@ def validateInputs():
response = validation
else:
response = None
- return jsonify(response), 200
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@datafile_api.route("/downloadDataFile", methods=['GET'])
def downloadDataFile():
@@ -187,7 +171,7 @@ def downloadDataFile():
return send_file(dataFile.resolve(), as_attachment=True, max_age=0)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@datafile_api.route("/downloadFile", methods=['GET'])
def downloadFile():
@@ -198,7 +182,7 @@ def downloadFile():
return send_file(dataFile.resolve(), as_attachment=True, max_age=0)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@datafile_api.route("/downloadCSVFile", methods=['GET'])
def downloadCSVFile():
@@ -210,7 +194,7 @@ def downloadCSVFile():
return send_file(dataFile.resolve(), as_attachment=True, max_age=0)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@datafile_api.route("/downloadResultsFile", methods=['GET'])
def downloadResultsFile():
@@ -221,7 +205,7 @@ def downloadResultsFile():
return send_file(dataFile.resolve(), as_attachment=True, max_age=0)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@datafile_api.route("/run", methods=['POST'])
def run():
@@ -231,13 +215,10 @@ def run():
solver = request.json['solver']
txtFile = DataFile(casename)
response = txtFile.run(solver, caserunname)
- return jsonify(response), 200
- # except Exception as ex:
- # print(ex)
- # return ex, 404
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
@datafile_api.route("/batchRun", methods=['POST'])
def batchRun():
@@ -254,9 +235,9 @@ def batchRun():
response = txtFile.batchRun( 'CBC', cases)
end = time.time()
response['time'] = end-start
- return jsonify(response), 200
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('Error!'), 404
+ return api_response(success=False, message="Error during batch run", status_code=500)
@datafile_api.route("/cleanUp", methods=['POST'])
def cleanUp():
@@ -267,6 +248,6 @@ def cleanUp():
model = DataFile(modelname)
response = model.cleanUp()
- return jsonify(response), 200
+ return api_response(success=True, data=response, status_code=200)
except(IOError):
- return jsonify('Error!'), 404
\ No newline at end of file
+ return api_response(success=False, message="Error during cleanup", status_code=500)
\ No newline at end of file
diff --git a/API/Routes/Upload/UploadRoute.py b/API/Routes/Upload/UploadRoute.py
index 88dde7d6a..6a7f29227 100644
--- a/API/Routes/Upload/UploadRoute.py
+++ b/API/Routes/Upload/UploadRoute.py
@@ -1,5 +1,6 @@
import shutil
-from flask import Blueprint, request, jsonify, send_file, after_this_request
+from flask import Blueprint, request, send_file, after_this_request
+from Classes.Base.Response import api_response
from zipfile import ZipFile
from pathlib import Path
from werkzeug.utils import secure_filename
@@ -197,9 +198,7 @@ def run(self):
@upload_api.route('/myfunc', methods=["GET", "POST"])
def myfunc():
- thread_a = Download(request.__copy__())
- thread_a.start()
- return "Processing in background", 200
+ return api_response(success=True, message="Processing in background", status_code=202)
@upload_api.route("/backupCase", methods=['GET'])
def backupCase():
@@ -239,9 +238,9 @@ def backupCase():
return send_file(zippedFile.resolve(), as_attachment=True)
except(IOError):
- return jsonify('No existing cases!'), 404
+ return api_response(success=False, message="No existing cases!", status_code=404)
except OSError:
- raise OSError
+ return api_response(success=False, message="OS Error during backup", status_code=500)
@upload_api.route('/uploadCaseUnchunked_old', methods=['POST'])
def uploadCaseUnchunked_old():
@@ -397,15 +396,11 @@ def uploadCaseUnchunked_old():
})
os.remove(os.path.join(Config.DATA_STORAGE, filename))
- response = {
- "response" :msg
- }
-
- return jsonify(response), 200
+ return api_response(success=True, data=msg, status_code=200)
except(IOError):
- raise IOError
+ return api_response(success=False, message="Error saving model IOError!", status_code=404)
except OSError:
- raise OSError
+ return api_response(success=False, message="OS Error during upload", status_code=500)
def handle_full_zip(file, filepath=None):
msg = []
@@ -439,7 +434,7 @@ def handle_full_zip(file, filepath=None):
"message": f"ZIP archive {case} is not valid archive!",
"status_code": "error"
})
- return jsonify({"response": msg}), 200
+ return api_response(success=False, message=f"ZIP archive {case} is not valid archive!", data={"response": msg}, status_code=200)
#for zippedfile in zf.namelist():
@@ -538,7 +533,7 @@ def handle_full_zip(file, filepath=None):
os.remove(filepath)
- return jsonify({"response": msg}), 200
+ return api_response(success=True, data=msg, status_code=200)
@upload_api.route('/uploadCase', methods=['POST'])
def uploadCase():
@@ -577,7 +572,7 @@ def uploadCase():
chunks_received = len(os.listdir(chunk_dir))
if chunks_received < dz_total_chunks:
- return jsonify({"status": f"received {chunks_received}/{dz_total_chunks}"}), 200
+ return api_response(success=True, message=f"received {chunks_received}/{dz_total_chunks}", status_code=202)
# -------------------------------
# 4) Spajanje ZIP fajla
@@ -605,7 +600,7 @@ def uploadCase():
return handle_full_zip(None, final_zip)
except Exception as e:
- return jsonify({"error": str(e)}), 500
+ return api_response(success=False, message=str(e), status_code=500)
@upload_api.route('/uploadXls', methods=['POST'])
def uploadXls():
@@ -644,12 +639,8 @@ def uploadXls():
"template": filename
})
- response = {
- "response" :msg
- }
-
- return jsonify(response), 200
+ return api_response(success=True, data=msg, status_code=200)
except(IOError):
- raise IOError
+ return api_response(success=False, message="Error saving XLS IOError!", status_code=404)
except OSError:
- raise OSError
\ No newline at end of file
+ return api_response(success=False, message="OS Error during XLS upload", status_code=500)
\ No newline at end of file
diff --git a/API/app.py b/API/app.py
index f2fc8c476..f978f9e2d 100644
--- a/API/app.py
+++ b/API/app.py
@@ -2,7 +2,7 @@
import os
import sys
-from flask import Flask, jsonify, request, session, render_template
+from flask import Flask, request, session, render_template
from flask_cors import CORS
from datetime import timedelta
# from pathlib import Path
@@ -15,6 +15,8 @@
from Routes.Case.SyncS3Route import syncs3_api
from Routes.Case.ViewDataRoute import viewdata_api
from Routes.DataFile.DataFileRoute import datafile_api
+from Classes.Base.CustomExceptionClass import CustomException
+from Classes.Base.Response import api_response
#RADI
template_dir = os.path.abspath('WebAPP')
@@ -58,7 +60,7 @@
def add_headers(response):
if Config.HEROKU_DEPLOY == 0:
#localhost
- response.headers.add('Access-Control-Allow-Origin', 'http://127.0.0.1')
+ response.headers.add('Access-Control-Allow-Origin', '*')
else:
#HEROKU
response.headers.add('Access-Control-Allow-Origin', 'https://osemosys.herokuapp.com/')
@@ -67,11 +69,23 @@ def add_headers(response):
#response.headers['Content-Type'] = 'application/javascript'
return response
-# @app.errorhandler(CustomException)
-# def handle_invalid_usage(error):
-# response = jsonify(error.to_dict())
-# response.status_code = error.status_code
-# return response
+@app.errorhandler(CustomException)
+def handle_custom_exception(error):
+ return api_response(success=False, message=error.message, data=error.payload, status_code=error.status_code)
+
+@app.errorhandler(404)
+def handle_404(error):
+ return api_response(success=False, message="Resource not found", status_code=404)
+
+@app.errorhandler(500)
+def handle_500(error):
+ return api_response(success=False, message="Internal server error", status_code=500)
+
+@app.errorhandler(Exception)
+def handle_exception(error):
+ # Log the error if needed
+ print(f"Unhandled Exception: {str(error)}")
+ return api_response(success=False, message=str(error), status_code=500)
#entry point to frontend
@app.route("/", methods=['GET'])
@@ -91,12 +105,9 @@ def home():
def getSession():
try:
ses = session.get('osycase', None) or None
- response = {
- "session":ses
- }
- return jsonify(response), 200
+ return api_response(success=True, data=ses, status_code=200)
except( KeyError ):
- return jsonify('No selected parameters!'), 404
+ return api_response(success=False, message="No selected parameters!", status_code=404)
@app.route("/setSession", methods=['POST'])
def setSession():
@@ -104,10 +115,9 @@ def setSession():
cs = request.json['case']
#session.permanent= True
session['osycase'] = cs
- response = {"osycase": session['osycase']}
- return jsonify(response), 200
+ return api_response(success=True, message="Session updated!", data=session['osycase'], status_code=200)
except( KeyError ):
- return jsonify('No selected parameters!'), 404
+ return api_response(success=False, message="No selected parameters!", status_code=404)
if __name__ == '__main__':
@@ -124,7 +134,8 @@ def setSession():
#waitress server
#prod server
from waitress import serve
- serve(app, host='127.0.0.1', port=port)
+ print(f"Starting server on http://0.0.0.0:{port}")
+ serve(app, host='0.0.0.0', port=port)
else:
#HEROKU
app.run(host='0.0.0.0', port=port, debug=True)
diff --git a/requirements.txt b/requirements.txt
index f1a065dda..bad26ef84 100644
Binary files a/requirements.txt and b/requirements.txt differ
diff --git a/requirementsOLD.txt b/requirementsOLD.txt
deleted file mode 100644
index deb8c070a..000000000
--- a/requirementsOLD.txt
+++ /dev/null
@@ -1,46 +0,0 @@
-altgraph==0.17
-astroid==2.4.2
-blinker==1.6.2
-boto3==1.17.32
-botocore==1.20.32
-certifi==2022.12.7
-chardet==3.0.4
-charset-normalizer==3.1.0
-click==8.1.3
-colorama==0.4.4
-electron==0.0.4
-et-xmlfile==1.1.0
-Flask==2.3.2
-Flask-Cors==3.0.9
-future==0.18.3
-gunicorn==20.0.4
-idna==2.10
-isort==5.6.4
-itsdangerous==2.1.2
-Jinja2==3.1.2
-jmespath==0.10.0
-lazy-object-proxy==1.4.3
-MarkupSafe==2.1.2
-mccabe==0.6.1
-numpy==1.24.0
-openpyxl==3.0.10
-pandas==1.5.3
-pefile==2022.5.30
-pip==23.1.2
-pyinstaller==5.7.0
-pyinstaller-hooks-contrib==2021.4
-pylint==2.6.0
-python-dateutil==2.8.1
-python-dotenv==0.15.0
-pytz==2020.1
-pywin32-ctypes==0.2.0
-requests==2.30.0
-s3transfer==0.3.6
-setuptools==67.7.2
-six==1.15.0
-toml==0.10.2
-ujson==5.7.0
-urllib3==1.26.15
-waitress==2.1.2
-Werkzeug==2.3.3
-wrapt==1.12.1