Python

Deployment of GUIs created with Simian GUI generally consists of the following steps:

  1. Prepare the environment for the deployment of a new GUI.
  2. Add simian-gui to your dependencies to ensure it will be available in the deployed Python environment.
    • To install from our PyPi server use the pip option --extra-index-url https://pypiserver.monkeyproofsolutions.nl/simple/
    • For testing with the Ball Thrower example, add simian-examples to the dependencies of your deployed Python environment.
  3. Ensure that requests to the deployed code are going to the entrypoint module. (Examples below)

The files that need to be deployed on the server are:

  • GUI definition file(s)
  • License file for the server. (Ensure it can be found)
  • Model/back-end. (Alternatively API calls can be used to connect with a model on a different server.)
  • Deployment target specific entrypoint wrapper function. (Examples below)
    • To connect to your own GUI, replace the "simian.examples.ballthrower" "namespace" value with the full name of your own GUI definition module.

Since there is no standard method for deploying Python code, a number of potential methods are discussed in the following sections:

Other deployment targets should also be possible, but they will require their own version of what is described below.

To test whether the deployed GUIs work, send a POST request with json body ["config", {}, {}] to the deployment host and port. You should get a response with a stringified json dictionary containing a session_id and some other fields.

Azure Functions

GUIs created with Simian GUI can be deployed as Azure functions:

  1. Create an Azure Functions project.

    Include the Simian GUI requirements in the Azure Functions requirements file to ensure that the required modules for Simian GUI are installed in the Azure environment.

  2. Put all the necessary files as described above in the project folder.

  3. In the Azure Functions package __init__.py file, the Azure Functions HttpRequests need to be processed and Responses need to be created. See the example code below on how to make Azure Functions communicate with Simian GUI.

    To ensure all Simian GUI and GUI definition files can be found and used they need to be on the Python path.

  4. Publish the project on Azure and deploy to the Function App.

# Use back-end type `python_azure_functions_v2` in the portal configuration.
import json
import logging
import traceback

import azure.functions as func
from simian.entrypoint import entry_point


def main(req: func.HttpRequest) -> func.HttpResponse:
    try:
        request_data = req.get_json()
    except ValueError as exc:
        return func.HttpResponse(
            _create_error_response(
                "An error occurred when processing the request data: " + str(exc), exc
            ),
            status_code=200,
        )

    # Route the post to the entrypoint method.
    request_data[1].update({"namespace": "simian.examples.ballthrower"})

    try:
        # Call the entry_point to access the application with the request data.
        payload_out = entry_point(
            request_data[0],
            request_data[1],
            request_data[2],
        )

        # Defer loading the utils until the entry_point has checked that Simian GUI
        # can be used.
        from simian.gui import utils

        # Return the payload as a string to Azure.
        response = f'{{"returnValue": {utils.payloadToString(payload_out)}}}'

    except Exception as exc:
        # Put the error message in the response.
        logging.error("Error caught by entrypoint wrapper: %r", exc)
        response = _create_error_response(str(exc), exc)

    return func.HttpResponse(response, status_code=200)


def _create_error_response(msg: str, exc: Exception) -> dict:
    """Create an error response dictionary from a caught exception."""
    return json.dumps(
        {
            "error": {
                "message": msg,
                "stacktrace": traceback.format_tb(exc.__traceback__),  # Optional
            }
        }
    )

ownR

GUIs created with Simian GUI can be deployed as ownR applications:

  1. Create a new ownR application as documented on the Functional Analytics wiki (account required).

  2. Put the necessary files as described above into the repository of the ownR application.

    Ensure the requirements.txt file of Simian GUI is on the root level and add simian-gui (and optionally simian-examples) to it. This will ensure that ownR can use it to install the required modules in the Python environment.

  3. ownR requires a module to exist at the root level that has the same name as the application. In this module import Simian GUI's entrypoint module and route the requests from the ownR environment to Simian GUI's entry_point function as illustrated below.

    To ensure all Simian GUI and GUI definition files can be found and used they need to be on the Python path.

  4. Commit and push the changes to the repository. This will trigger a rebuild of the application and your changes to become available.

# Use back-end type `python_ownr` in the portal configuration.

from simian.entrypoint import entry_point

def entry_point(operation: str, meta_data: dict, payload_in: dict) -> dict:
    """Route the request to the entrypoint module."""
    meta_data.update({'namespace': 'simian.examples.ballthrower'})
    return entry_point(
        operation=operation,
        meta_data=meta_data,
        payload_in=payload_in
    )

FastAPI - uvicorn

GUIs created with Simian GUI can be deployed with FastAPI and uvicorn.

  1. Install simian-gui, FastAPI, and uvicorn in your Python environment.

  2. Put all the necessary files as described above in a folder.

  3. Create a fastapi_deploy.py file as shown below. For your own GUIs ensure that the namespace of your own GUI is put in the dictionary of the second request_data item.

  4. To host the GUI locally with uvicorn run the fastapi_deploy script, optionally on a different host and port.

    Alternatively, you can use the procedure as described on the uvicorn website to run uvicorn.

    Hosting the app with multiple workers is supported with gunicorn.

The contents of the fastapi_deploy.py file should be:

# Use back-end type `python_fastapi` in the portal configuration.
import logging
import sys
import traceback

from fastapi import Body, FastAPI
from fastapi.responses import JSONResponse
from simian.entrypoint import entry_point
import uvicorn

app = FastAPI()


@app.post('/apps/ballthrower', response_class=JSONResponse)
def route_app_requests(request_data: list = Body()) -> dict:
    """Route requests to ballthrower GUI and return the response."""
    # Set the namespace that contains the GUI definition.
    request_data[1].update({"namespace": "simian.examples.ballthrower"})

    try:
        # Route the post to the entrypoint method.
        payload_out = entry_point(
            request_data[0],
            request_data[1],
            request_data[2],
        )

        # Defer loading the utils until the entry_point has checked that Simian GUI
        # can be used.
        from simian.gui import utils

        # Return the payload_out as a json string.
        response = {"returnValue": utils.payloadToString(payload_out)}

    except Exception as exc:
        logging.error('Error caught by entrypoint wrapper: %r', exc)
        response = {
            "error": {
                "message": str(exc),
                "stacktrace": traceback.format_tb(exc.__traceback__)  # Optional
            }
        }

    return response


def main(host: str = "127.0.0.1", port=5000, log_level: str = "info"):
    """Host the application using uvicorn."""
    uvicorn.run("fastapi_deploy:app", host=host, port=port, log_level=log_level)


if __name__ == "__main__":
    main(*sys.argv[1:])

Flask

GUIs created with Simian GUI can be deployed with Flask. Note that Flask is not recommended to be used for production.

  1. Install Flask in your Python environment.

  2. Put all the necessary files as described above in a folder.

  3. Create a flask_hosting.py file as shown below. For your own GUIs modify the secret key (or store it elsewhere) and ensure that the namespace of your own GUI is put in the dictionary of the second request_data item.

  4. To host the GUI locally with Flask run the flask_hosting script, optionally on a different host and port.

    Alternatively, you can use the procedure as described on the Flask website to run Flask.

The contents of the flask_hosting.py file should be:

# Use back-end type `python_flask_v2` in the portal configuration.
import json
import logging
import sys
import traceback

from flask import Flask, request
from simian.entrypoint import entry_point


def create_app() -> Flask:
    """Create Flask App for hosting a webGUI."""
    # create and configure the app
    app = Flask(__name__)

    # Store secret key elsewhere.
    app.secret_key = b'_8#h3S"T7U9z\n\xec]/'
    log = app.logger
    log.level = logging.DEBUG

    @app.route('/apps/<app_name>', methods=('POST', ))
    def route_app_requests(app_name) -> dict:
        log.debug('Detected POST request')
        request_data = json.loads(request.data)

        # Set the namespace that contains the GUI definition.
        request_data[1].update({"namespace": "simian.examples.ballthrower"})

        try:
            # Route the post to the entrypoint method.
            payload_out = entry_point(
                request_data[0],
                request_data[1],
                request_data[2],
            )

            # Defer loading the utils until the entry_point has checked that Simian GUI
            # framework can be used.
            from simian.gui import utils

            # Return the payload_out as a json string.
            response = {"returnValue": utils.payloadToString(payload_out)}

        except Exception as exc:
            logging.error('Error caught by entrypoint wrapper: %r', exc)
            response = {
                "error": {
                    "message": str(exc),
                    "stacktrace": traceback.format_tb(exc.__traceback__)  # Optional
                }
            }

        return response

    return app


def main(host: str = '127.0.0.1', port: int = 5000, debug=False):
    create_app().run(host=host, port=port, debug=debug)


if __name__ == "__main__":
    main(*sys.argv[1:])