Loading openflexure_microscope/api/example_extensions/ev_gui.py 0 → 100644 +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) ) openflexure_microscope/api/utilities.py→openflexure_microscope/api/utilities/__init__.py +2 −0 Original line number Diff line number Diff line Loading @@ -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 Loading openflexure_microscope/api/utilities/gui.py 0 → 100644 +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") openflexure_microscope/common/flask_labthings/extensions.py +12 −30 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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): Loading openflexure_microscope/common/flask_labthings/schema.py +2 −4 Original line number Diff line number Diff line Loading @@ -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() Loading @@ -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), } Loading Loading
openflexure_microscope/api/example_extensions/ev_gui.py 0 → 100644 +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) )
openflexure_microscope/api/utilities.py→openflexure_microscope/api/utilities/__init__.py +2 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
openflexure_microscope/api/utilities/gui.py 0 → 100644 +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")
openflexure_microscope/common/flask_labthings/extensions.py +12 −30 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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): Loading
openflexure_microscope/common/flask_labthings/schema.py +2 −4 Original line number Diff line number Diff line Loading @@ -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() Loading @@ -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), } Loading