From edecc01ca1f918f06e89731adb741b1f0de568e0 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Tue, 19 Nov 2019 15:45:54 +0000 Subject: [PATCH] Added proper root representation --- openflexure_microscope/api/app.py | 3 ++ .../api/v2/blueprints/__init__.py | 2 +- .../api/v2/blueprints/actions/__init__.py | 13 +++-- .../api/v2/blueprints/captures.py | 4 +- .../api/v2/blueprints/plugins.py | 16 ++++-- .../api/v2/blueprints/root.py | 44 +++++++++++++++ openflexure_microscope/plugins/loader.py | 53 +++++++++++++++++-- 7 files changed, 122 insertions(+), 13 deletions(-) create mode 100644 openflexure_microscope/api/v2/blueprints/root.py diff --git a/openflexure_microscope/api/app.py b/openflexure_microscope/api/app.py index 9e91b40a..33ea8ec1 100644 --- a/openflexure_microscope/api/app.py +++ b/openflexure_microscope/api/app.py @@ -144,6 +144,9 @@ app.register_blueprint(task_blueprint, url_prefix=uri("/task", "v1")) ### V2 # Root routes +v2_root_blueprint = v2.blueprints.root.construct_blueprint(api_microscope) +app.register_blueprint(v2_root_blueprint, url_prefix=uri("/", "v2")) + v2_stream_blueprint = v2.blueprints.stream.construct_blueprint(api_microscope) app.register_blueprint(v2_stream_blueprint, url_prefix=uri("/", "v2")) diff --git a/openflexure_microscope/api/v2/blueprints/__init__.py b/openflexure_microscope/api/v2/blueprints/__init__.py index c29dd363..473540f2 100644 --- a/openflexure_microscope/api/v2/blueprints/__init__.py +++ b/openflexure_microscope/api/v2/blueprints/__init__.py @@ -1 +1 @@ -from . import captures, settings, status, tasks, stream, plugins, actions +from . import root, captures, settings, status, tasks, stream, plugins, actions diff --git a/openflexure_microscope/api/v2/blueprints/actions/__init__.py b/openflexure_microscope/api/v2/blueprints/actions/__init__.py index 88285f12..f16a41e3 100644 --- a/openflexure_microscope/api/v2/blueprints/actions/__init__.py +++ b/openflexure_microscope/api/v2/blueprints/actions/__init__.py @@ -1,6 +1,8 @@ from flask import Blueprint, url_for, jsonify from sys import platform +from openflexure_microscope.api.views import MicroscopeView + from . import camera, stage, system _actions = { @@ -64,11 +66,14 @@ def actions_representation(): return actions +class ActionsAPI(MicroscopeView): + def get(self): + return jsonify(actions_representation()) def construct_blueprint(microscope_obj): global _actions - blueprint = Blueprint("actions_blueprint", __name__) + blueprint = Blueprint("v2_actions_blueprint", __name__) # For each enabled action route defined in our dictionary above for name, action in enabled_actions().items(): @@ -78,8 +83,8 @@ def construct_blueprint(microscope_obj): view_func=action["view_class"].as_view(name, microscope=microscope_obj), ) - @blueprint.route("/") - def representation(): - return jsonify(actions_representation()) + blueprint.add_url_rule( + "/", view_func=ActionsAPI.as_view("actions", microscope=microscope_obj) + ) return blueprint diff --git a/openflexure_microscope/api/v2/blueprints/captures.py b/openflexure_microscope/api/v2/blueprints/captures.py index 814445e6..0a54e69a 100644 --- a/openflexure_microscope/api/v2/blueprints/captures.py +++ b/openflexure_microscope/api/v2/blueprints/captures.py @@ -378,7 +378,7 @@ class TagsAPI(MicroscopeView): def construct_blueprint(microscope_obj): - blueprint = Blueprint("captures_blueprint", __name__) + blueprint = Blueprint("v2_captures_blueprint", __name__) # Tag routes blueprint.add_url_rule( @@ -405,6 +405,6 @@ def construct_blueprint(microscope_obj): ) blueprint.add_url_rule( - "/", view_func=ListAPI.as_view("capture_list", microscope=microscope_obj) + "/", view_func=ListAPI.as_view("captures", microscope=microscope_obj) ) return blueprint diff --git a/openflexure_microscope/api/v2/blueprints/plugins.py b/openflexure_microscope/api/v2/blueprints/plugins.py index 2ac55c2b..50f298f0 100644 --- a/openflexure_microscope/api/v2/blueprints/plugins.py +++ b/openflexure_microscope/api/v2/blueprints/plugins.py @@ -1,7 +1,7 @@ from openflexure_microscope.plugins import PluginLoader, MicroscopePlugin from openflexure_microscope.api.views import MicroscopeViewPlugin -from flask import Blueprint, jsonify +from flask import Blueprint, jsonify, url_for from openflexure_microscope.api.views import MicroscopeView import copy @@ -27,6 +27,11 @@ def plugins_representation(plugin_loader_object: PluginLoader): "routes": plugin["routes"], "form": plugin["form"], } + + for route in d["routes"]: + route_id = route["id"] + uri = url_for(f"v2_plugins_blueprint.{route_id}") + route["links"]["self"] = uri plugins.append(d) return plugins @@ -51,7 +56,7 @@ class PluginFormAPI(MicroscopeView): def construct_blueprint(microscope_obj): - blueprint = Blueprint("plugins_blueprint", __name__) + blueprint = Blueprint("v2_plugins_blueprint", __name__) # Create a base route to return plugin API forms, if any exist blueprint.add_url_rule( @@ -110,7 +115,12 @@ def construct_blueprint(microscope_obj): ) # Add route to the plugin representation dictionary - plugin_representation["routes"].append(full_view_route) + route_representation = { + "id": plugin_route_id, + "route": full_view_route, + "links": {}, + } + plugin_representation["routes"].append(route_representation) else: warnings.warn( diff --git a/openflexure_microscope/api/v2/blueprints/root.py b/openflexure_microscope/api/v2/blueprints/root.py new file mode 100644 index 00000000..6106ad86 --- /dev/null +++ b/openflexure_microscope/api/v2/blueprints/root.py @@ -0,0 +1,44 @@ +from flask import Blueprint, jsonify, url_for +from openflexure_microscope import Microscope +from openflexure_microscope.api.views import MicroscopeView + +def root_representation(microscope_obj: Microscope): + d = { + "settings": { + "description": "Writeable settings for the microscope, and attached hardware", + "links": {"self": url_for("v2_settings_blueprint.settings")} + }, + "status": { + "description": "Read-only status of the microscope, and attached hardware info", + "links": {"self": url_for("v2_status_blueprint.status")} + }, + "plugins": { + "description": "Top-level representation of attached and enabled plugins", + "links": {"self": url_for("v2_plugins_blueprint.plugins")} + }, + "captures": { + "description": "Top-level representation of all acquired captures", + "links": {"self": url_for("v2_captures_blueprint.captures")} + }, + "actions": { + "description": "Top-level representation of enabled actions", + "links": {"self": url_for("v2_actions_blueprint.actions")} + }, + } + + return d + + +class RootAPI(MicroscopeView): + def get(self): + + return jsonify(root_representation(self.microscope)) + + +def construct_blueprint(microscope_obj): + blueprint = Blueprint("root_blueprint", __name__) + + blueprint.add_url_rule( + "/", view_func=RootAPI.as_view("root_repr", microscope=microscope_obj) + ) + return blueprint diff --git a/openflexure_microscope/plugins/loader.py b/openflexure_microscope/plugins/loader.py index fc5e3f93..4aaef28e 100644 --- a/openflexure_microscope/plugins/loader.py +++ b/openflexure_microscope/plugins/loader.py @@ -1,8 +1,11 @@ import importlib +import copy import os import inspect import logging +from openflexure_microscope.api.views import MicroscopeViewPlugin + class ConColors: HEADER = "\033[95m" @@ -137,6 +140,50 @@ def class_from_map(plugin_map): return load_plugin_class(*plugin_arr) +def expand_views(plugin_object, plugin_name): + routes = [] + + # If plugin contains valid endpoints + if hasattr(plugin_object, "api_views") and isinstance(plugin_object.api_views, dict): + + # We'll keep a record of how each route was expanded + expanded_routes = {} + + # For each defined endpoint + for view_route, view_class in plugin_object.api_views.items(): + + # Remove all leading slashes from view route + cleaned_route = view_route + while cleaned_route[0] == "/": + cleaned_route = cleaned_route[1:] + + # Construct a full view route from the plugin name + full_view_route = "/{}/{}".format(plugin_name, cleaned_route) + logging.debug(full_view_route) + + # Record how the view_route got expanded + expanded_routes[view_route] = full_view_route + + # Check if endpoint name clashes + if issubclass(view_class, MicroscopeViewPlugin): + + # Create a Python-safe name for the route + plugin_route_id = "plugin{}".format(full_view_route).replace( + "/", "_" + ) + + # Add route to the plugin representation dictionary + route_representation = { + "id": plugin_route_id, + "route": full_view_route, + "links": {}, + } + + routes.append(route_representation) + + return routes + + class PluginLoader(object): """ A mount-point for all loaded plugins. Attaches to a Microscope object. @@ -174,7 +221,7 @@ class PluginLoader(object): if plugin_class is not None: - pythonsafe_plugin_name = plugin_name.replace("/", "_") + plugin_name_python_safe = plugin_name.replace("/", "_") if plugin_class and plugin_name: plugin_object = plugin_class() @@ -194,13 +241,13 @@ class PluginLoader(object): plugin_object, MicroscopePlugin ): # If plugin_object is an instance of MicroscopePlugin # Attach plugin_object to the plugin mount - setattr(self, pythonsafe_plugin_name, plugin_object) + setattr(self, plugin_name_python_safe, plugin_object) # Store the plugin object, and it's properties self._plugins.append( { "name": plugin_name, - "python_name": pythonsafe_plugin_name, + "python_name": plugin_name_python_safe, "plugin": plugin_object, "routes": [], "form": None,