summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-api/app.py
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-web/opendc-web-api/app.py')
-rwxr-xr-xopendc-web/opendc-web-api/app.py179
1 files changed, 67 insertions, 112 deletions
diff --git a/opendc-web/opendc-web-api/app.py b/opendc-web/opendc-web-api/app.py
index ee4b3d32..5041457f 100755
--- a/opendc-web/opendc-web-api/app.py
+++ b/opendc-web/opendc-web-api/app.py
@@ -1,25 +1,35 @@
#!/usr/bin/env python3
-import json
import os
-import sys
-import traceback
from dotenv import load_dotenv
-from flask import Flask, request, jsonify
+from flask import Flask, jsonify
from flask_compress import Compress
from flask_cors import CORS
+from flask_restful import Api
+from marshmallow import ValidationError
-from opendc.util import rest, path_parser, database
-from opendc.util.auth import AuthError, AuthManager, AsymmetricJwtAlgorithm
-from opendc.util.exceptions import AuthorizationTokenError, RequestInitializationError
-from opendc.util.json import JSONEncoder
+from opendc.api.portfolios import Portfolio, PortfolioScenarios
+from opendc.api.prefabs import Prefab, PrefabList
+from opendc.api.projects import ProjectList, Project, ProjectTopologies, ProjectPortfolios
+from opendc.api.scenarios import Scenario
+from opendc.api.schedulers import SchedulerList
+from opendc.api.topologies import Topology
+from opendc.api.traces import TraceList, Trace
+from opendc.auth import AuthError
+from opendc.util import JSONEncoder
+
+# Load environmental variables from dotenv file
load_dotenv()
-TEST_MODE = "OPENDC_FLASK_TESTING" in os.environ
-# Setup Sentry if DSN is specified
-if 'SENTRY_DSN' in os.environ:
+def setup_sentry():
+ """
+ Setup the Sentry integration for Flask if a DSN is supplied via the environmental variables.
+ """
+ if 'SENTRY_DSN' not in os.environ:
+ return
+
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration
@@ -28,119 +38,64 @@ if 'SENTRY_DSN' in os.environ:
traces_sample_rate=0.1
)
-# Set up database if not testing
-if not TEST_MODE:
- database.DB.initialize_database(
- user=os.environ['OPENDC_DB_USERNAME'],
- password=os.environ['OPENDC_DB_PASSWORD'],
- database=os.environ['OPENDC_DB'],
- host=os.environ.get('OPENDC_DB_HOST', 'localhost'))
-
-# Set up the core app
-app = Flask("opendc")
-app.testing = TEST_MODE
-app.config['SECRET_KEY'] = os.environ['OPENDC_FLASK_SECRET']
-app.json_encoder = JSONEncoder
-
-# Set up CORS support
-CORS(app)
-
-compress = Compress()
-compress.init_app(app)
-
-auth = AuthManager(AsymmetricJwtAlgorithm(jwks_url=f"https://{os.environ['AUTH0_DOMAIN']}/.well-known/jwks.json"),
- issuer=f"https://{os.environ['AUTH0_DOMAIN']}/", audience=os.environ['AUTH0_AUDIENCE'])
-
-API_VERSIONS = {'v2'}
-
-
-@app.errorhandler(AuthError)
-def handle_auth_error(ex):
- response = jsonify(ex.error)
- response.status_code = ex.status_code
- return response
-
-
-@app.route('/<string:version>/<path:endpoint_path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
-@auth.require
-def api_call(version, endpoint_path):
- """Call an API endpoint directly over HTTP."""
-
- # Check whether given version is valid
- if version not in API_VERSIONS:
- return jsonify(error='API version not found'), 404
-
- # Get path and parameters
- (path, path_parameters) = path_parser.parse(version, endpoint_path)
-
- query_parameters = request.args.to_dict()
- for param in query_parameters:
- try:
- query_parameters[param] = int(query_parameters[param])
- except:
- pass
-
- try:
- body_parameters = json.loads(request.get_data())
- except:
- body_parameters = {}
-
- # Create and call request
- (req, response) = _process_message({
- 'id': 0,
- 'method': request.method,
- 'parameters': {
- 'body': body_parameters,
- 'path': path_parameters,
- 'query': query_parameters
- },
- 'path': path,
- 'token': request.headers.get('auth-token')
- })
- print(
- f'HTTP:\t{req.method} to `/{req.path}` resulted in {response.status["code"]}: {response.status["description"]}')
- sys.stdout.flush()
+def setup_api(app):
+ """
+ Setup the API interface.
+ """
+ api = Api(app)
+ # Map to ('string', 'ObjectId') passing type and format
+ api.add_resource(ProjectList, '/projects/')
+ api.add_resource(Project, '/projects/<string:project_id>')
+ api.add_resource(ProjectTopologies, '/projects/<string:project_id>/topologies')
+ api.add_resource(ProjectPortfolios, '/projects/<string:project_id>/portfolios')
+ api.add_resource(Topology, '/topologies/<string:topology_id>')
+ api.add_resource(PrefabList, '/prefabs/')
+ api.add_resource(Prefab, '/prefabs/<string:prefab_id>')
+ api.add_resource(Portfolio, '/portfolios/<string:portfolio_id>')
+ api.add_resource(PortfolioScenarios, '/portfolios/<string:portfolio_id>/scenarios')
+ api.add_resource(Scenario, '/scenarios/<string:scenario_id>')
+ api.add_resource(TraceList, '/traces/')
+ api.add_resource(Trace, '/traces/<string:trace_id>')
+ api.add_resource(SchedulerList, '/schedulers/')
- flask_response = jsonify(json.loads(response.to_JSON()))
- flask_response.status_code = response.status['code']
- return flask_response
+ @app.errorhandler(AuthError)
+ def handle_auth_error(ex):
+ response = jsonify(ex.error)
+ response.status_code = ex.status_code
+ return response
+ @app.errorhandler(ValidationError)
+ def handle_validation_error(ex):
+ return {'message': 'Input validation failed', 'errors': ex.messages}, 400
-def _process_message(message):
- """Process a request message and return the response."""
+ return api
- try:
- req = rest.Request(message)
- res = req.process()
- return req, res
+def create_app(testing=False):
+ app = Flask(__name__)
+ app.config['TESTING'] = testing
+ app.config['SECRET_KEY'] = os.environ['OPENDC_FLASK_SECRET']
+ app.config['RESTFUL_JSON'] = {'cls': JSONEncoder}
+ app.json_encoder = JSONEncoder
- except AuthorizationTokenError:
- res = rest.Response(401, 'Authorization error')
- res.id = message['id']
+ # Setup Sentry if DSN is specified
+ setup_sentry()
- except RequestInitializationError as e:
- res = rest.Response(400, str(e))
- res.id = message['id']
+ # Set up CORS support
+ CORS(app)
- if not 'method' in message:
- message['method'] = 'UNSPECIFIED'
- if not 'path' in message:
- message['path'] = 'UNSPECIFIED'
+ # Setup compression
+ compress = Compress()
+ compress.init_app(app)
- except Exception:
- res = rest.Response(500, 'Internal server error')
- if 'id' in message:
- res.id = message['id']
- traceback.print_exc()
+ # Setup API
+ setup_api(app)
- req = rest.Request()
- req.method = message['method']
- req.path = message['path']
+ return app
- return req, res
+application = create_app(testing="OPENDC_FLASK_TESTING" in os.environ)
if __name__ == '__main__':
- app.run()
+ application.run()