diff --git a/docs/source/plugins.rst b/docs/source/plugins.rst index b7134ad0..16be0d7c 100644 --- a/docs/source/plugins.rst +++ b/docs/source/plugins.rst @@ -8,6 +8,6 @@ Developing Plugins ./plugins/structure.rst ./plugins/routes.rst ./plugins/hardware.rst - ./plugins/schema.rst + ./plugins/form.rst ./plugins/example.rst ./plugins/class.rst \ No newline at end of file diff --git a/docs/source/plugins/example.rst b/docs/source/plugins/example.rst index 5d0ba18b..8a38e504 100644 --- a/docs/source/plugins/example.rst +++ b/docs/source/plugins/example.rst @@ -7,10 +7,10 @@ plugin.py .. literalinclude:: example/plugin.py :language: python -schema.json +form.json +++++++++++ -.. literalinclude:: example/schema.json +.. literalinclude:: example/form.json :language: JSON Notes diff --git a/docs/source/plugins/example/schema.json b/docs/source/plugins/example/forms.json similarity index 94% rename from docs/source/plugins/example/schema.json rename to docs/source/plugins/example/forms.json index 58364150..8832a944 100644 --- a/docs/source/plugins/example/schema.json +++ b/docs/source/plugins/example/forms.json @@ -7,7 +7,7 @@ "isTask": false, "selfUpdate": true, "route": "/hello", - "schema": [ + "form": [ { "fieldType": "textInput", "placeholder": "Enter a string", @@ -21,7 +21,7 @@ "isTask": true, "route": "/timelapse", "submitLabel": "Start timelapse", - "schema": [ + "form": [ { "fieldType": "numberInput", "name": "n_images", diff --git a/docs/source/plugins/example/plugin.py b/docs/source/plugins/example/plugin.py index a0570992..36da5c0d 100644 --- a/docs/source/plugins/example/plugin.py +++ b/docs/source/plugins/example/plugin.py @@ -9,7 +9,7 @@ import json from flask import request, Response, escape, jsonify HERE = os.path.dirname(os.path.realpath(__file__)) -SCHEMA_PATH = os.path.join(HERE, "schema.json") +FORM_PATH = os.path.join(HERE, "forms.json") ### MICROSCOPE PLUGIN ### @@ -18,10 +18,10 @@ class MyPluginClass(MicroscopePlugin): A set of default plugins """ - global SCHEMA_PATH + global FORM_PATH - with open(SCHEMA_PATH, 'r') as sc: - api_schema = json.load(sc) + with open(FORM_PATH, 'r') as sc: + api_form = json.load(sc) api_views = { '/identify': IdentifyAPI, diff --git a/docs/source/plugins/schema.rst b/docs/source/plugins/forms.rst similarity index 81% rename from docs/source/plugins/schema.rst rename to docs/source/plugins/forms.rst index ef0375f4..25b4863a 100644 --- a/docs/source/plugins/schema.rst +++ b/docs/source/plugins/forms.rst @@ -4,39 +4,39 @@ Adding a GUI Introduction ------------ -In order to bind user-interface elements to your plugin, an ``api_schema`` variable must be included in your microscope plugin, similar to your ``api_views``. +In order to bind user-interface elements to your plugin, an ``api_form`` variable must be included in your microscope plugin, similar to your ``api_views``. -This variable must be in the form of a dictionary, with all data able to be parsed into JSON. Because of this requirement, it is suggested that you create your API schema as a JSON file, and load that file as a dictionary into your plugin. For example: +This variable must be in the form of a dictionary, with all data able to be parsed into JSON. Because of this requirement, it is suggested that you create your API form as a JSON file, and load that file as a dictionary into your plugin. For example: .. code-block:: python import json HERE = os.path.dirname(os.path.realpath(__file__)) # Find the full path of your plugin Python file - SCHEMA_PATH = os.path.join(HERE, "schema.json") # Find the full path of the adjascent JSON file + FORM_PATH = os.path.join(HERE, "form.json") # Find the full path of the adjascent JSON file class MyPluginClass(MicroscopePlugin): - with open(SCHEMA_PATH, 'r') as sc: # Open the JSON file - api_schema = json.load(sc) # Load the JSON file into the api_schema dictionary + with open(FORM_PATH, 'r') as sc: # Open the JSON file + api_form = json.load(sc) # Load the JSON file into the api_form dictionary -Throughout this documentation, all example of ``api_schema`` sections will be in JSON format. Keep in mind however that it is possible to directly define your ``api_schema`` as a dictionary, without loading an external file. +Throughout this documentation, all example of ``api_form`` sections will be in JSON format. Keep in mind however that it is possible to directly define your ``api_form`` as a dictionary, without loading an external file. -Forms from ``api_schema`` +Forms from ``api_form`` ------------------------- -The ``api_schema`` object essentially describes HTML forms, which it is up to the client to render. The form is constructed by specifying a set of components, and their values. A form can update it's values by sending a GET request to the API route bound to that form, and can send it's current values via a POST request to *this same API route*. +The ``api_form`` object essentially describes HTML forms, which it is up to the client to render. The form is constructed by specifying a set of components, and their values. A form can update it's values by sending a GET request to the API route bound to that form, and can send it's current values via a POST request to *this same API route*. Each component in the form has a ``name`` property, which must match up to a property your API route expects in JSON POST requests, and returns in JSON GET requests. -Structure of ``api_schema`` +Structure of ``api_form`` --------------------------- Root level ++++++++++ -The root of your ``api_schema`` expects 3 properties: +The root of your ``api_form`` expects 3 properties: ``id`` - A unique ID to give your client-side plugin @@ -61,7 +61,7 @@ Each form is described by a JSON object, with the following properties: ``submitLabel`` *(optional)* - String to place inside of the form's submit button -``schema`` - An array of form components as described below +``form`` - An array of form components as described below Component level +++++++++++++++ @@ -182,11 +182,11 @@ Overview of components JSON form example ----------------- -.. literalinclude:: schema_example.json +.. literalinclude:: forms_example.json :language: JSON .. toctree:: :maxdepth: 1 - ./schema/json_schema.rst \ No newline at end of file + ./form/json_form.rst \ No newline at end of file diff --git a/docs/source/plugins/schema_example.json b/docs/source/plugins/forms_example.json similarity index 98% rename from docs/source/plugins/schema_example.json rename to docs/source/plugins/forms_example.json index 07b348d2..9b8358f1 100644 --- a/docs/source/plugins/schema_example.json +++ b/docs/source/plugins/forms_example.json @@ -9,7 +9,7 @@ "selfUpdate": true, "route": "/do", "submitLabel": "Do things", - "schema": [ + "form": [ { "fieldType": "numberInput", "placeholder": "Some integer", @@ -75,7 +75,7 @@ "selfUpdate": true, "route": "/task", "submitLabel": "Start task", - "schema": [ + "form": [ { "fieldType": "numberInput", "name": "run_time", diff --git a/docs/source/plugins/schema/json_schema.rst b/docs/source/plugins/schema/json_schema.rst index c7cf118e..3560cfb1 100644 --- a/docs/source/plugins/schema/json_schema.rst +++ b/docs/source/plugins/schema/json_schema.rst @@ -1,5 +1,5 @@ -Full JSON Schema +Full JSON Form ================ -.. literalinclude:: schema.json +.. literalinclude:: form.json :language: JSON diff --git a/docs/source/plugins/schema/schema.json b/docs/source/plugins/schema/schema.json index 063e63cd..0a216944 100644 --- a/docs/source/plugins/schema/schema.json +++ b/docs/source/plugins/schema/schema.json @@ -1,9 +1,9 @@ { "definitions": {}, - "$schema": "http://json-schema.org/draft-07/schema#", + "$form": "http://json-form.org/draft-07/form#", "$id": "http://example.com/root.json", "type": "object", - "title": "The Root Schema", + "title": "The Root Form", "required": [ "id", "icon", @@ -41,11 +41,11 @@ "items": { "$id": "#/properties/forms/items", "type": "object", - "title": "The Items Schema", + "title": "The Items Form", "required": [ "name", "route", - "schema" + "form" ], "properties": { "name": { @@ -62,7 +62,7 @@ "isCollapsible": { "$id": "#/properties/forms/items/properties/isCollapsible", "type": "boolean", - "title": "The isCollapsible Schema", + "title": "The isCollapsible Form", "description": "Should the form be rendered as a collapsible item", "default": false, "examples": [ @@ -72,7 +72,7 @@ "isTask": { "$id": "#/properties/forms/items/properties/isTask", "type": "boolean", - "title": "The isTask Schema", + "title": "The isTask Form", "description": "Should the form's submit function be treated as a long-running task", "default": false, "examples": [ @@ -82,7 +82,7 @@ "selfUpdate": { "$id": "#/properties/forms/items/properties/selfUpdate", "type": "boolean", - "title": "The selfUpdate Schema", + "title": "The selfUpdate Form", "description": "Should the form's component values be automatically updated with a GET request", "default": false, "examples": [ @@ -92,7 +92,7 @@ "route": { "$id": "#/properties/forms/items/properties/route", "type": "string", - "title": "The Route Schema", + "title": "The Route Form", "description": "Form submit POST request's corresponding api_views route", "default": "", "examples": [ @@ -103,7 +103,7 @@ "submitLabel": { "$id": "#/properties/forms/items/properties/submitLabel", "type": "string", - "title": "The submitLabel Schema", + "title": "The submitLabel Form", "description": "String to place in submit button", "default": "Submit", "examples": [ @@ -111,12 +111,12 @@ ], "pattern": "^(.*)$" }, - "schema": { - "$id": "#/properties/forms/items/properties/schema", + "form": { + "$id": "#/properties/forms/items/properties/form", "type": "array", "title": "Form component array", "items": { - "$id": "#/properties/forms/items/properties/schema/items", + "$id": "#/properties/forms/items/properties/form/items", "type": "object", "title": "A form component", "required": [ @@ -125,7 +125,7 @@ ], "properties": { "fieldType": { - "$id": "#/properties/forms/items/properties/schema/items/properties/fieldType", + "$id": "#/properties/forms/items/properties/form/items/properties/fieldType", "type": "string", "title": "Component type", "default": "", @@ -143,7 +143,7 @@ "pattern": "^(.*)$" }, "placeholder": { - "$id": "#/properties/forms/items/properties/schema/items/properties/placeholder", + "$id": "#/properties/forms/items/properties/form/items/properties/placeholder", "type": "string", "title": "Component placeholder value", "default": "", @@ -153,7 +153,7 @@ "pattern": "^(.*)$" }, "name": { - "$id": "#/properties/forms/items/properties/schema/items/properties/name", + "$id": "#/properties/forms/items/properties/form/items/properties/name", "type": "string", "title": "Unique component name", "examples": [ @@ -162,7 +162,7 @@ "pattern": "^(.*)$" }, "label": { - "$id": "#/properties/forms/items/properties/schema/items/properties/label", + "$id": "#/properties/forms/items/properties/form/items/properties/label", "type": "string", "title": "Component label (if applicable)", "default": "", @@ -172,7 +172,7 @@ "pattern": "^(.*)$" }, "value": { - "$id": "#/properties/forms/items/properties/schema/items/properties/value", + "$id": "#/properties/forms/items/properties/form/items/properties/value", "type": ["array", "boolean", "integer", "number", "object", "string"], "title": "Component value", "pattern": "^(.*)$" diff --git a/openflexure_microscope/api/v1/blueprints/plugins.py b/openflexure_microscope/api/v1/blueprints/plugins.py index ff7b1183..09ca322d 100644 --- a/openflexure_microscope/api/v1/blueprints/plugins.py +++ b/openflexure_microscope/api/v1/blueprints/plugins.py @@ -6,21 +6,21 @@ from openflexure_microscope.api.v1.views import MicroscopeView import logging import warnings -class PluginSchemaAPI(MicroscopeView): +class PluginFormAPI(MicroscopeView): def get(self): """ - Return the current plugin schemas + Return the current plugin forms - .. :quickref: Plugin; Get schemas + .. :quickref: Plugin; Get forms - Returns an array of present plugin schemas (describing plugin user interfaces.) + Returns an array of present plugin forms (describing plugin user interfaces.) Please note, this is *not* a list of all enabled plugins, only those with associated user interface forms. A complete list of enabled plugins can be found in the microscope state. """ - out = self.microscope.plugin.schemas + out = self.microscope.plugin.forms return jsonify(out) @@ -28,10 +28,10 @@ def construct_blueprint(microscope_obj): blueprint = Blueprint('plugin_blueprint', __name__) - # Create a base route to return plugin API schemas, if any exist + # Create a base route to return plugin API forms, if any exist blueprint.add_url_rule( '/', - view_func=PluginSchemaAPI.as_view('plugin_api_schema', microscope=microscope_obj) + view_func=PluginFormAPI.as_view('plugin_api_form', microscope=microscope_obj) ) all_routes = [] @@ -86,20 +86,21 @@ def construct_blueprint(microscope_obj): ) ) - # If plugin includes an API schema - if hasattr(plugin_obj, 'api_schema') and isinstance(plugin_obj.api_schema, dict): - schema = plugin_obj.api_schema - # TODO: Validate schema? We need to make sure no single plugin can break all plugins. - schema['id'] = plugin_name - if 'forms' in schema and isinstance(schema['forms'], list): - for form in schema['forms']: + # If plugin includes an API form + if hasattr(plugin_obj, 'api_form') and isinstance(plugin_obj.api_form, dict): + api_form_info = plugin_obj.api_form + # TODO: Validate form? We need to make sure no single plugin can break all plugins. + api_form_info['id'] = plugin_name + if 'forms' in api_form_info and isinstance(api_form_info['forms'], list): + for form in api_form_info['forms']: if 'route' in form and form['route'] in expanded_routes.keys(): form['route'] = expanded_routes[form['route']] else: logging.warn("No valid expandable route found for {}".format(form['route'])) - # Store the complete schema in Microscope().plugin.schema - microscope_obj.plugin.schemas.append(schema) + # Store the complete form in Microscope().plugin.form + microscope_obj.plugin.forms.append(api_form_info) + print(microscope_obj.plugin.forms) else: warnings.warn( diff --git a/openflexure_microscope/camera/mock.py b/openflexure_microscope/camera/mock.py index 180d8a2f..38cb45fb 100644 --- a/openflexure_microscope/camera/mock.py +++ b/openflexure_microscope/camera/mock.py @@ -107,7 +107,6 @@ class MockStreamer(BaseCamera): # If stream was paused to update config, unpause if paused_stream: logging.info("Resuming stream.") - self.start_stream_recording() else: raise Exception( diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 874e5282..647522b2 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -94,26 +94,32 @@ class Microscope: # Maybe even attach dummy hardware at __init__, and replace with real hardware if it exists logging.debug("Attaching camera...") - self.camera = camera #: :py:class:`openflexure_microscope.camera.base.BaseCamera`: Picamera object + self.camera = ( + camera + ) #: :py:class:`openflexure_microscope.camera.base.BaseCamera`: Picamera object if not self.camera: logging.info("No camera attached.") else: logging.info("Attached camera {}".format(camera)) - if hasattr(self.camera, 'lock'): # If camera has a lock + if hasattr(self.camera, "lock"): # If camera has a lock logging.info("Attaching {} to composite lock.".format(self.camera.lock)) # Add the lock to the microscope composite lock self.lock.locks.append(self.camera.lock) logging.debug("Attaching stage...") - self.stage = stage #: :py:class:`openflexure_microscope.stage.base.BaseStage`: OpenFlexure stage object + self.stage = ( + stage + ) #: :py:class:`openflexure_microscope.stage.base.BaseStage`: OpenFlexure stage object if not self.stage: logging.info("No stage attached.") else: logging.info("Attached stage {}".format(stage)) - if hasattr(self.stage, 'lock'): # If stage object has a lock - logging.info("Attaching lock {} to composite lock.".format(self.stage.lock)) + if hasattr(self.stage, "lock"): # If stage object has a lock + logging.info( + "Attaching lock {} to composite lock.".format(self.stage.lock) + ) # Add the lock to the microscope composite lock self.lock.locks.append(self.stage.lock) @@ -149,10 +155,10 @@ class Microscope: and :py:attr:`openflexure_microscope.camera.base.BaseCamera.state` """ state = { - 'camera': self.camera.state, - 'stage': self.stage.state, - 'plugin': self.plugin.state, - 'version': pkg_resources.get_distribution('openflexure_microscope').version + "camera": self.camera.state, + "stage": self.stage.state, + "plugin": self.plugin.state, + "version": pkg_resources.get_distribution("openflexure_microscope").version, } return state @@ -163,22 +169,22 @@ class Microscope: logging.debug("Microscope: Applying config: {}".format(config)) # If attached to a camera - if ('camera_settings' in config) and self.camera: - self.camera.apply_config(config['camera_settings']) + if ("camera_settings" in config) and self.camera: + self.camera.apply_config(config["camera_settings"]) # If attached to a stage - if ('stage_settings' in config) and self.stage: - self.stage.apply_config(config['stage_settings']) + if ("stage_settings" in config) and self.stage: + self.stage.apply_config(config["stage_settings"]) # Todo: tidy up with some loopy goodness - if 'id' in config: - self.id = config['id'] - if 'name' in config: - self.name = config['name'] - if 'fov' in config: - self.fov = config['fov'] - if 'plugins' in config: - self.plugin_maps = config['plugins'] + if "id" in config: + self.id = config["id"] + if "name" in config: + self.name = config["name"] + if "fov" in config: + self.fov = config["fov"] + if "plugins" in config: + self.plugin_maps = config["plugins"] def read_config(self, json_safe=False): """ @@ -192,21 +198,21 @@ class Microscope: """ settings_current = { - 'id': self.id, - 'name': self.name, - 'fov': self.fov, - 'plugins': self.plugin_maps + "id": self.id, + "name": self.name, + "fov": self.fov, + "plugins": self.plugin_maps, } # If attached to a camera if self.camera: settings_current_camera = self.camera.read_config() - settings_current['camera_settings'] = settings_current_camera + settings_current["camera_settings"] = settings_current_camera # If attached to a stage if self.stage: settings_current_stage = self.stage.read_config() - settings_current['stage_settings'] = settings_current_stage + settings_current["stage_settings"] = settings_current_stage settings_full = self.settings_file.merge(settings_current) @@ -222,7 +228,9 @@ class Microscope: # Read curent config current_config = self.read_config() # Merge in server version responsible for saving the config file - current_config['server_version'] = pkg_resources.get_distribution('openflexure_microscope').version + current_config["server_version"] = pkg_resources.get_distribution( + "openflexure_microscope" + ).version # Save config to file self.settings_file.save(current_config, backup=True) diff --git a/openflexure_microscope/plugins/loader.py b/openflexure_microscope/plugins/loader.py index d484b4ee..26a5f991 100644 --- a/openflexure_microscope/plugins/loader.py +++ b/openflexure_microscope/plugins/loader.py @@ -132,7 +132,7 @@ class PluginMount(object): def __init__(self, parent): self.parent = parent self.plugins = [] # List of plugin objects - self.schemas = [] # List of plugin schemas + self.forms = [] # List of plugin forms logging.info("Creating plugin mount") @property @@ -182,7 +182,7 @@ class PluginMount(object): logging.info(ConColors.OKGREEN + "Plugin {} loaded as {}.".format(plugin_map, plugin_name) + ConColors.ENDC) else: - logging.error("Error loading plugin. Moving on.") + logging.error(f"Error loading plugin {plugin_map}. Moving on.") class MicroscopePlugin: """ diff --git a/openflexure_microscope/plugins/testing/schema_example/__init__.py b/openflexure_microscope/plugins/testing/form_example/__init__.py similarity index 100% rename from openflexure_microscope/plugins/testing/schema_example/__init__.py rename to openflexure_microscope/plugins/testing/form_example/__init__.py diff --git a/openflexure_microscope/plugins/testing/schema_example/api.py b/openflexure_microscope/plugins/testing/form_example/api.py similarity index 100% rename from openflexure_microscope/plugins/testing/schema_example/api.py rename to openflexure_microscope/plugins/testing/form_example/api.py diff --git a/openflexure_microscope/plugins/testing/schema_example/schema.json b/openflexure_microscope/plugins/testing/form_example/forms.json similarity index 98% rename from openflexure_microscope/plugins/testing/schema_example/schema.json rename to openflexure_microscope/plugins/testing/form_example/forms.json index 4aee4dcd..ab99d8c5 100644 --- a/openflexure_microscope/plugins/testing/schema_example/schema.json +++ b/openflexure_microscope/plugins/testing/form_example/forms.json @@ -9,7 +9,7 @@ "selfUpdate": true, "route": "/do", "submitLabel": "Do things", - "schema": [ + "form": [ [ { "fieldType": "numberInput", @@ -66,7 +66,7 @@ "selfUpdate": true, "route": "/task", "submitLabel": "Start task", - "schema": [ + "form": [ { "fieldType": "numberInput", "placeholder": "", diff --git a/openflexure_microscope/plugins/testing/schema_example/plugin.py b/openflexure_microscope/plugins/testing/form_example/plugin.py similarity index 88% rename from openflexure_microscope/plugins/testing/schema_example/plugin.py rename to openflexure_microscope/plugins/testing/form_example/plugin.py index 0e57fe26..97b1a6c7 100644 --- a/openflexure_microscope/plugins/testing/schema_example/plugin.py +++ b/openflexure_microscope/plugins/testing/form_example/plugin.py @@ -7,16 +7,16 @@ from openflexure_microscope.devel import MicroscopePlugin from .api import DoAPI, TaskAPI HERE = os.path.dirname(os.path.realpath(__file__)) -SCHEMA_PATH = os.path.join(HERE, "schema.json") +FORM_PATH = os.path.join(HERE, "forms.json") class ExamplePlugin(MicroscopePlugin): """ - An example plugin using a comprehensive schema + An example plugin using a comprehensive form """ - global SCHEMA_PATH + global FORM_PATH - with open(SCHEMA_PATH, 'r') as sc: - api_schema = json.load(sc) + with open(FORM_PATH, 'r') as sc: + api_form = json.load(sc) api_views = { '/do': DoAPI, @@ -35,7 +35,7 @@ class ExamplePlugin(MicroscopePlugin): def set_values(self, val_int, val_str, val_radio, val_check, val_select, val_disposable): """ - Demonstrate a plugin with schema + Demonstrate a plugin with form """ if val_int: self.val_int = int(val_int)