Source code for aiida.restapi.run_api

#!/usr/bin/env python
# -*- coding: utf-8 -*-
###########################################################################
# Copyright (c), The AiiDA team. All rights reserved.                     #
# This file is part of the AiiDA code.                                    #
#                                                                         #
# The code is hosted on GitHub at https://github.com/aiidateam/aiida_core #
# For further information on the license, see the LICENSE.txt file        #
# For further information please visit http://www.aiida.net               #
###########################################################################

import argparse
import imp
import os

from flask_cors import CORS

from aiida.backends.utils import load_dbenv


[docs]def run_api(App, Api, *args, **kwargs): """ Takes a flask.Flask instance and runs it. Parses command-line flags to configure the app. App: Class inheriting from Flask app class Api = flask_restful API class to be used to wrap the app args: required by argparse kwargs: List of valid parameters: prog_name: name of the command before arguments are parsed. Useful when api is embedded in a command, such as verdi restapi default_host: self-explainatory default_port: self-explainatory default_config_dir = directory containing the config.py file used to configure the RESTapi parse_aiida_profile= if True, parses an option to specify the AiiDA profile All other passed parameters are ignored. """ import aiida # Mainly needed to locate the correct aiida path # Unpack parameters and assign defaults if needed prog_name = kwargs['prog_name'] if 'prog_name' in kwargs else "" default_host = kwargs['default_host'] if 'default_host' in kwargs else \ "127.0.0.1" default_port = kwargs['default_port'] if 'default_port' in kwargs else \ "5000" default_config_dir = kwargs['default_config_dir'] if \ 'default_config_dir' in kwargs \ else os.path.join(os.path.split(os.path.abspath( aiida.restapi.__file__))[0], 'common') parse_aiida_profile = kwargs['parse_aiida_profile'] if \ 'parse_aiida_profile' in kwargs else False catch_internal_server = kwargs['catch_internal_server'] if\ 'catch_internal_server' in kwargs else False hookup = kwargs['hookup'] if 'hookup' in kwargs else False # Set up the command-line options parser = argparse.ArgumentParser(prog=prog_name, description='Hook up the AiiDA ' 'RESTful API') parser.add_argument("-H", "--host", help="Hostname of the Flask app " + \ "[default %s]" % default_host, dest='host', default=default_host) parser.add_argument("-P", "--port", help="Port for the Flask app " + \ "[default %s]" % default_port, dest='port', default=default_port) parser.add_argument("-c", "--config-dir", help="Directory with config.py for Flask app " + \ "[default {}]".format(default_config_dir), dest='config_dir', default=default_config_dir) # This one is included only if necessary if parse_aiida_profile: parser.add_argument("-p", "--aiida-profile", help="AiiDA profile to expose through the RESTful " "API [default: the default AiiDA profile]", dest="aiida_profile", default=None) # Two options useful for debugging purposes, but # a bit dangerous so not exposed in the help message. parser.add_argument("-d", "--debug", action="store_true", dest="debug", help=argparse.SUPPRESS) parser.add_argument("-w", "--wsgi-profile", action="store_true", dest="wsgi_profile", help=argparse.SUPPRESS) parsed_args = parser.parse_args(args) # Import the right configuration file confs = imp.load_source(os.path.join(parsed_args.config_dir, 'config'), os.path.join(parsed_args.config_dir, 'config.py') ) import aiida.backends.settings as settings """ Set aiida profile General logic: if aiida_profile is parsed the following cases exist: aiida_profile: "default" --> default profile set in .aiida/config.json <profile> --> corresponding profile in .aiida/config.json None --> default restapi profile set in <config_dir>/config,py if aiida_profile is not parsed we assume default restapi profile set in <config_dir>/config.py """ if parse_aiida_profile and parsed_args.aiida_profile is not None: aiida_profile = parsed_args.aiida_profile elif confs.default_aiida_profile is not None: aiida_profile = confs.default_aiida_profile else: aiida_profile = "default" if aiida_profile != "default": settings.AIIDADB_PROFILE = aiida_profile else: pass # This way the default of .aiida/config.json will be used # Set the AiiDA environment. If already loaded, load_dbenv will raise an # exception # if not is_dbenv_loaded(): load_dbenv() # Instantiate an app app_kwargs = dict(catch_internal_server=catch_internal_server) app = App(__name__, **app_kwargs) # Config the app app.config.update(**confs.APP_CONFIG) # cors cors_prefix = os.path.join(confs.PREFIX, "*") cors = CORS(app, resources={r"" + cors_prefix: {"origins": "*"}}) # Config the serializer used by the app if confs.SERIALIZER_CONFIG: from aiida.restapi.common.utils import CustomJSONEncoder app.json_encoder = CustomJSONEncoder # If the user selects the profiling option, then we need # to do a little extra setup if parsed_args.wsgi_profile: from werkzeug.contrib.profiler import ProfilerMiddleware app.config['PROFILE'] = True app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30]) # Instantiate an Api by associating its app api_kwargs = dict(PREFIX=confs.PREFIX, PERPAGE_DEFAULT=confs.PERPAGE_DEFAULT, LIMIT_DEFAULT=confs.LIMIT_DEFAULT) api = Api(app, **api_kwargs) # Check if the app has to be hooked-up or just returned if hookup: api.app.run( debug=parsed_args.debug, host=parsed_args.host, port=int(parsed_args.port), threaded=True ) else: # here we return the app, and the api with no specifications on debug # mode, port and host. This can be handled by an external server, # e.g. apache2, which will set the host and port. This implies that # the user-defined configuration of the app is ineffective (it only # affects the internal werkzeug server used by Flask). return (app, api)
# Standard boilerplate to run the api if __name__ == '__main__': """ I run the app via a wrapper that accepts arguments such as host and port e.g. python api.py --host=127.0.0.2 --port=6000 --config-dir=~/.restapi Default address is 127.0.0.1:5000, default config directory is <aiida_path>/aiida/restapi/common Start the app by sliding the argvs to flaskrun, choose to take as an argument also whether to parse the aiida profile or not (in verdi restapi this would not be the case) """ import sys from aiida.restapi.api import AiidaApi, App """ Or, equivalently, (useful starting point for derived apps) import the app object and the Api class that you want to combine. """ run_api(App, AiidaApi, *sys.argv[1:], parse_aiida_profile=True, hookup=True, catch_internal_server=True)