Commit eeb15453 authored by Joel Collins's avatar Joel Collins
Browse files

Moved eV GUI stuff out of LabThings

parent 3fef94dc
Loading
Loading
Loading
Loading
+113 −0
Original line number Diff line number Diff line
from openflexure_microscope.common.flask_labthings.resource import Resource
from openflexure_microscope.common.flask_labthings.extensions import BaseExtension

from openflexure_microscope.api.utilities.gui import expand_routes

import logging

from flask import jsonify

# Some value that will change over time
# Here, we add 1 to it every time a GET request is made
val_int = 0


def dynamic_form():
    return {
        "id": "test-plugin",
        "icon": "pets",
        "forms": [
            {
                "name": "Simple request",
                "isCollapsible": False,
                "isTask": False,
                "selfUpdate": True,
                "route": "/do",
                "submitLabel": "Do things",
                "schema": [
                    {
                        "fieldType": "numberInput",
                        "name": "val_int",
                        "label": "Number value",
                        "minValue": 0,
                    },
                    {
                        "fieldType": "htmlBlock",
                        "name": "html_block",
                        "content": f"<i>Value is: </i><br>{val_int}.",  # We dynamically use the val_int variable
                    },
                ],
            }
        ],
    }


# Alternate form without any dynamic parts
static_form = {
    "id": "test-plugin",
    "icon": "pets",
    "forms": [
        {
            "name": "Simple request",
            "isCollapsible": False,
            "isTask": False,
            "selfUpdate": True,
            "route": "/do",
            "submitLabel": "Do things",
            "schema": [
                {
                    "fieldType": "numberInput",
                    "name": "val_int",
                    "label": "Number value",
                    "minValue": 0,
                },
                {
                    "fieldType": "htmlBlock",
                    "name": "html_block",
                    "content": f"<i>Value is fixed</i>.",
                },
            ],
        }
    ],
}


class TestAPIView(Resource):
    def get(self):
        global val_int
        val_int += 1
        return jsonify({"val": True})


class TestDoAPIView(Resource):
    def post(self):
        global val_int
        val_int += 1
        return jsonify({"val": True})


# Using the dynamic form
dynamic_test_extension_v2 = BaseExtension("dynamic_test_extension")
dynamic_test_extension_v2.add_view(
    TestAPIView, "/get", endpoint="dynamic_test_extension_get"
)
dynamic_test_extension_v2.add_view(
    TestDoAPIView, "/do", endpoint="dynamic_test_extension_do"
)

dynamic_test_extension_v2.add_meta(
    "gui", expand_routes(dynamic_form, dynamic_test_extension_v2)
)


# Using the static form
static_test_extension_v2 = BaseExtension("static_test_extension")
static_test_extension_v2.add_view(
    TestAPIView, "/get", endpoint="static_test_extension_get"
)
static_test_extension_v2.add_view(
    TestDoAPIView, "/do", endpoint="static_test_extension_do"
)
static_test_extension_v2.add_meta(
    "gui", expand_routes(static_form, static_test_extension_v2)
)
+2 −0
Original line number Diff line number Diff line
@@ -4,6 +4,8 @@ import errno
from werkzeug.exceptions import BadRequest
from flask import url_for, Blueprint, current_app

from . import gui


def view_class_from_endpoint(endpoint: str):
    return current_app.view_functions[endpoint].view_class
+41 −0
Original line number Diff line number Diff line
import copy
import logging
from functools import wraps


def expand_routes_from_dict(gui_description, extension_object):
    api_gui = copy.deepcopy(gui_description)
    if "forms" in gui_description and isinstance(api_gui["forms"], list):
        for form in api_gui["forms"]:
            if "route" in form and form["route"] in extension_object._rules.keys():
                form["route"] = extension_object._rules[form["route"]]["rule"]
            else:
                logging.warn(
                    "No valid expandable route found for {}".format(form["route"])
                )
    return api_gui


def expand_routes_from_func(func, extension_object):
    @wraps(func)
    def wrapped(*args, **kwargs):
        return expand_routes_from_dict(func(*args, **kwargs), extension_object)

    return wrapped


def expand_routes(gui_description, extension_object):
    # If given a function that generates a GUI dictionary
    if callable(gui_description):
        # Wrap in the route expander
        return expand_routes_from_func(gui_description, extension_object)
    # If given a dictionary directly
    elif isinstance(gui_description, dict):
        # Build a GUI generator function
        def gui_description_func():
            return gui_description

        # Wrap in the route expander
        return expand_routes_from_func(gui_description_func, extension_object)
    else:
        raise RuntimeError("GUI description must be a function or a dictionary")
+12 −30
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ class BaseExtension:
            {}
        )  # Key: Full, Python-safe ID. Val: Original rule, and view class
        self._rules = {}  # Key: Original rule. Val: View class
        self._meta = {}  # Extra metadata to add to the extension description
        self._gui = None

        self._cls = str(self)  # String description of extension instance
@@ -71,36 +72,17 @@ class BaseExtension:
        self.properties.append(view_class)

    @property
    def gui(self):
        # Handle missing/no GUI
        if not self._gui:
            return None
        # Handle GUI as a dictionary-returning callable function
        elif isinstance(self._gui, collections.Callable):
            api_gui = self._gui()
        # Handle GUI as a static dictionary
        elif isinstance(self._gui, dict):
            api_gui = copy.deepcopy(self._gui)
        # Handle borked GUI
    def meta(self):
        d = {}
        for k, v in self._meta.items():
            if callable(v):
                d[k] = v()
            else:
            raise ValueError(
                "GUI must be a dictionary, or a dictionary-returning function with no arguments."
            )

        api_gui["id"] = self._name

        if "forms" in api_gui and isinstance(api_gui["forms"], list):
            for form in api_gui["forms"]:
                if "route" in form and form["route"] in self._rules.keys():
                    form["route"] = self._rules[form["route"]]["rule"]
                else:
                    logging.warn(
                        "No valid expandable route found for {}".format(form["route"])
                    )
        return api_gui
                d[k] = v
        return d

    def set_gui(self, form_dictionary: dict):
        self._gui = form_dictionary
    def add_meta(self, key, val):
        self._meta[key] = val

    @property
    def _name(self):
+2 −4
Original line number Diff line number Diff line
@@ -71,7 +71,7 @@ class ExtensionSchema(Schema):
    name = fields.String(data_key="title")
    _name_python_safe = fields.String(data_key="pythonName")
    _cls = fields.String(data_key="pythonObject")
    gui = fields.Dict()
    meta = fields.Dict()
    description = fields.String()

    links = fields.Dict()
@@ -81,12 +81,10 @@ class ExtensionSchema(Schema):
        d = {}
        for view_id, view_data in data.views.items():
            view_cls = view_data["view"]
            view_kwargs = view_data["kwargs"]
            view_rule = view_data["rule"]
            # Make links dictionary if it doesn't yet exist
            d[view_id] = {
                "href": url_for(EXTENSION_LIST_ENDPOINT, **view_kwargs, _external=True)
                + view_rule,
                "href": url_for(EXTENSION_LIST_ENDPOINT, _external=True) + view_rule,
                **description_from_view(view_cls),
            }