Overhauled v2 plugin attaching
This commit is contained in:
parent
b8f6193f7b
commit
3c096e9b53
6 changed files with 167 additions and 135 deletions
|
|
@ -39,10 +39,7 @@ def construct_blueprint(microscope_obj):
|
||||||
all_routes = []
|
all_routes = []
|
||||||
|
|
||||||
# For each plugin attached to the microscope object
|
# For each plugin attached to the microscope object
|
||||||
for plugin_representation in microscope_obj.plugins.active:
|
for plugin_name, plugin_obj in microscope_obj.plugins._legacy_plugins.items():
|
||||||
|
|
||||||
plugin_obj = plugin_representation["plugin"]
|
|
||||||
plugin_name = plugin_representation["name"]
|
|
||||||
|
|
||||||
# If plugin contains valid endpoints
|
# If plugin contains valid endpoints
|
||||||
if hasattr(plugin_obj, "api_views") and isinstance(plugin_obj.api_views, dict):
|
if hasattr(plugin_obj, "api_views") and isinstance(plugin_obj.api_views, dict):
|
||||||
|
|
|
||||||
|
|
@ -20,20 +20,34 @@ def plugins_representation(plugin_loader_object: PluginLoader):
|
||||||
dict: Dictionary representation of all plugins
|
dict: Dictionary representation of all plugins
|
||||||
"""
|
"""
|
||||||
plugins = []
|
plugins = []
|
||||||
|
|
||||||
|
print(plugin_loader_object.active)
|
||||||
|
|
||||||
for plugin in plugin_loader_object.active:
|
for plugin in plugin_loader_object.active:
|
||||||
|
logging.info(f"Representing plugin {plugin._name}")
|
||||||
d = {
|
d = {
|
||||||
"name": plugin["name"],
|
"name": plugin._name,
|
||||||
"plugin": str(plugin["plugin"]),
|
"plugin": str(plugin),
|
||||||
"routes": plugin["routes"],
|
"views": {},
|
||||||
"form": plugin["form"],
|
"form": plugin.form,
|
||||||
}
|
}
|
||||||
|
|
||||||
for route in d["routes"]:
|
for view_id, view in plugin.views.items():
|
||||||
route_id = route["id"]
|
logging.info(f"Representing view {view_id}")
|
||||||
uri = url_for(f"v2_plugins_blueprint.{route_id}")
|
uri = url_for(f"v2_plugins_blueprint.{view_id}")
|
||||||
route["links"]["self"] = uri
|
# Make links dictionary if it doesn't yet exist
|
||||||
|
view_d = {
|
||||||
|
"links": {"self": uri}
|
||||||
|
}
|
||||||
|
|
||||||
|
d["views"][view_id] = view_d
|
||||||
|
|
||||||
|
print("\n")
|
||||||
|
print(d)
|
||||||
|
print("\n")
|
||||||
plugins.append(d)
|
plugins.append(d)
|
||||||
|
|
||||||
|
print(plugins)
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -66,69 +80,20 @@ def construct_blueprint(microscope_obj):
|
||||||
all_routes = []
|
all_routes = []
|
||||||
|
|
||||||
# For each plugin attached to the microscope object
|
# For each plugin attached to the microscope object
|
||||||
for plugin_representation in microscope_obj.plugins.active:
|
for plugin in microscope_obj.plugins.active:
|
||||||
|
|
||||||
plugin_obj = plugin_representation["plugin"]
|
for plugin_view_id, plugin_view in plugin.views.items():
|
||||||
plugin_name = plugin_representation["name"]
|
# Add route to the plugins blueprint
|
||||||
|
blueprint.add_url_rule(
|
||||||
# If plugin contains valid endpoints
|
plugin_view["rule"],
|
||||||
if hasattr(plugin_obj, "api_views") and isinstance(plugin_obj.api_views, dict):
|
view_func=plugin_view["view"].as_view(
|
||||||
|
plugin_view_id,
|
||||||
# We'll keep a record of how each route was expanded
|
microscope=microscope_obj,
|
||||||
expanded_routes = {}
|
plugin=plugin,
|
||||||
|
),
|
||||||
# For each defined endpoint
|
)
|
||||||
for view_route, view_class in plugin_obj.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 full_view_route not in all_routes and issubclass(
|
|
||||||
view_class, MicroscopeViewPlugin
|
|
||||||
):
|
|
||||||
# Add route to main route dictionary
|
|
||||||
all_routes.append(full_view_route)
|
|
||||||
|
|
||||||
# Create a Python-safe name for the route
|
|
||||||
plugin_route_id = "plugin{}".format(full_view_route).replace(
|
|
||||||
"/", "_"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add route to the plugins blueprint
|
|
||||||
blueprint.add_url_rule(
|
|
||||||
full_view_route,
|
|
||||||
view_func=view_class.as_view(
|
|
||||||
plugin_route_id,
|
|
||||||
microscope=microscope_obj,
|
|
||||||
plugin=plugin_obj,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add route to the plugin representation dictionary
|
|
||||||
route_representation = {
|
|
||||||
"id": plugin_route_id,
|
|
||||||
"route": full_view_route,
|
|
||||||
"links": {},
|
|
||||||
}
|
|
||||||
plugin_representation["routes"].append(route_representation)
|
|
||||||
|
|
||||||
else:
|
|
||||||
warnings.warn(
|
|
||||||
"An endpoint /{} has already been loaded. Skipping {}.".format(
|
|
||||||
full_view_route, view_class
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
"""
|
||||||
# If plugin includes an API form
|
# If plugin includes an API form
|
||||||
if hasattr(plugin_obj, "api_form") and isinstance(
|
if hasattr(plugin_obj, "api_form") and isinstance(
|
||||||
plugin_obj.api_form, dict
|
plugin_obj.api_form, dict
|
||||||
|
|
@ -152,9 +117,6 @@ def construct_blueprint(microscope_obj):
|
||||||
# Store the complete form in Microscope().plugin.form
|
# Store the complete form in Microscope().plugin.form
|
||||||
plugin_representation["form"] = api_form_info
|
plugin_representation["form"] = api_form_info
|
||||||
print(microscope_obj.plugins.forms)
|
print(microscope_obj.plugins.forms)
|
||||||
|
"""
|
||||||
|
|
||||||
else:
|
|
||||||
warnings.warn(
|
|
||||||
"No valid 'api_views' dictionary found in {}".format(plugin_obj)
|
|
||||||
)
|
|
||||||
return blueprint
|
return blueprint
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,8 @@ class ScanPlugin(MicroscopePlugin):
|
||||||
api_views = {"/tile": TileScanAPI}
|
api_views = {"/tile": TileScanAPI}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
MicroscopePlugin.__init__(self)
|
||||||
|
|
||||||
self.images_to_be_captured: int = 1
|
self.images_to_be_captured: int = 1
|
||||||
update_task_data({"images_to_be_captured": self.images_to_be_captured})
|
update_task_data({"images_to_be_captured": self.images_to_be_captured})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import os
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from openflexure_microscope.utilities import camel_to_snake, camel_to_spine
|
||||||
from openflexure_microscope.api.views import MicroscopeViewPlugin
|
from openflexure_microscope.api.views import MicroscopeViewPlugin
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -83,7 +84,6 @@ def name_from_module(plugin_path):
|
||||||
|
|
||||||
|
|
||||||
def load_plugin_module(plugin_path):
|
def load_plugin_module(plugin_path):
|
||||||
|
|
||||||
# If the loader was found (i.e. plugin probably exists)
|
# If the loader was found (i.e. plugin probably exists)
|
||||||
if check_module(plugin_path):
|
if check_module(plugin_path):
|
||||||
try:
|
try:
|
||||||
|
|
@ -140,50 +140,6 @@ def class_from_map(plugin_map):
|
||||||
return load_plugin_class(*plugin_arr)
|
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):
|
class PluginLoader(object):
|
||||||
"""
|
"""
|
||||||
A mount-point for all loaded plugins. Attaches to a Microscope object.
|
A mount-point for all loaded plugins. Attaches to a Microscope object.
|
||||||
|
|
@ -195,6 +151,7 @@ class PluginLoader(object):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self._plugins = [] # List of plugin objects
|
self._plugins = [] # List of plugin objects
|
||||||
|
self._legacy_plugins = {} # DEPRECATED: Dictionary of plugins with old names
|
||||||
self.forms = [] # List of plugin forms
|
self.forms = [] # List of plugin forms
|
||||||
logging.info("Creating plugin mount")
|
logging.info("Creating plugin mount")
|
||||||
|
|
||||||
|
|
@ -221,13 +178,15 @@ class PluginLoader(object):
|
||||||
|
|
||||||
if plugin_class is not None:
|
if plugin_class is not None:
|
||||||
|
|
||||||
|
logging.debug(f"Loading plugin class {plugin_class}, {plugin_name}")
|
||||||
|
|
||||||
plugin_name_python_safe = plugin_name.replace("/", "_")
|
plugin_name_python_safe = plugin_name.replace("/", "_")
|
||||||
|
|
||||||
if plugin_class and plugin_name:
|
if plugin_class and plugin_name:
|
||||||
plugin_object = plugin_class()
|
plugin_object = plugin_class()
|
||||||
|
|
||||||
if hasattr(
|
if hasattr(
|
||||||
self, plugin_name
|
self, plugin_name
|
||||||
): # If a plugin with the same name is already attached.
|
): # If a plugin with the same name is already attached.
|
||||||
logging.warning(
|
logging.warning(
|
||||||
ConColors.WARNING
|
ConColors.WARNING
|
||||||
|
|
@ -238,24 +197,19 @@ class PluginLoader(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
elif isinstance(
|
elif isinstance(
|
||||||
plugin_object, MicroscopePlugin
|
plugin_object, BasePlugin
|
||||||
): # If plugin_object is an instance of MicroscopePlugin
|
): # If plugin_object is an instance of MicroscopePlugin
|
||||||
# Attach plugin_object to the plugin mount
|
# Attach plugin_object to the plugin mount
|
||||||
setattr(self, plugin_name_python_safe, plugin_object)
|
setattr(self, plugin_name_python_safe, plugin_object)
|
||||||
|
|
||||||
# Store the plugin object, and it's properties
|
# Store the plugin object, and it's properties
|
||||||
self._plugins.append(
|
self._plugins.append(plugin_object)
|
||||||
{
|
# DEPRECATED: Store the plugin with it's old name
|
||||||
"name": plugin_name,
|
self._legacy_plugins[plugin_name_python_safe] = plugin_object
|
||||||
"python_name": plugin_name_python_safe,
|
|
||||||
"plugin": plugin_object,
|
|
||||||
"routes": [],
|
|
||||||
"form": None,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Grant plugin access to the hardware
|
# Grant plugin access to the hardware
|
||||||
plugin_object.microscope = self.parent
|
if isinstance(plugin_object, MicroscopePlugin):
|
||||||
|
plugin_object.microscope = self.parent
|
||||||
|
|
||||||
logging.info(
|
logging.info(
|
||||||
ConColors.OKGREEN
|
ConColors.OKGREEN
|
||||||
|
|
@ -267,7 +221,112 @@ class PluginLoader(object):
|
||||||
logging.warning(f"Error loading plugin {plugin_map}. Moving on.")
|
logging.warning(f"Error loading plugin {plugin_map}. Moving on.")
|
||||||
|
|
||||||
|
|
||||||
class MicroscopePlugin:
|
class BasePlugin:
|
||||||
|
"""
|
||||||
|
Parent class for all plugins.
|
||||||
|
|
||||||
|
Handles binding route views and forms.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._views = {}
|
||||||
|
self._rules = {}
|
||||||
|
self._form = None
|
||||||
|
|
||||||
|
# If old api_views dictionary is found
|
||||||
|
if hasattr(self, "api_views"):
|
||||||
|
# Convert to new format
|
||||||
|
self._convert_old_api_views()
|
||||||
|
# If old api_form dictionary is found
|
||||||
|
if hasattr(self, "api_form"):
|
||||||
|
# Convert to new format
|
||||||
|
self._convert_old_api_form()
|
||||||
|
|
||||||
|
print("\n\n NAME: " + self._name + "\n\n")
|
||||||
|
print(self.views)
|
||||||
|
print(self.form)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def views(self):
|
||||||
|
return self._views
|
||||||
|
|
||||||
|
def add_view(self, rule, view_class):
|
||||||
|
# Remove all leading slashes from view route
|
||||||
|
cleaned_rule = rule
|
||||||
|
while cleaned_rule[0] == "/":
|
||||||
|
cleaned_rule = cleaned_rule[1:]
|
||||||
|
|
||||||
|
# Expand the rule to include plugin name
|
||||||
|
full_rule = "/{}/{}".format(self._name_uri_safe, cleaned_rule)
|
||||||
|
|
||||||
|
# Create a Python-safe route ID
|
||||||
|
view_id = cleaned_rule.replace("/", "_")
|
||||||
|
full_view_id = f"{self._name_python_safe}_{view_id}"
|
||||||
|
|
||||||
|
# Store route information in a dictionary
|
||||||
|
d = {
|
||||||
|
"rule": full_rule,
|
||||||
|
"view": view_class
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add view to private views dictionary
|
||||||
|
self._views[full_view_id] = d
|
||||||
|
# Store the rule expansion information
|
||||||
|
self._rules[rule] = self._views[full_view_id]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def form(self):
|
||||||
|
if not self._form:
|
||||||
|
return None
|
||||||
|
|
||||||
|
api_form_info = copy.deepcopy(self._form)
|
||||||
|
api_form_info["id"] = self._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 self._rules.keys():
|
||||||
|
form["route"] = self._rules[form["route"]]["rule"]
|
||||||
|
else:
|
||||||
|
logging.warn(
|
||||||
|
"No valid expandable route found for {}".format(
|
||||||
|
form["route"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return api_form_info
|
||||||
|
|
||||||
|
def set_form(self, form_dictionary: dict):
|
||||||
|
self._form = form_dictionary
|
||||||
|
|
||||||
|
def _convert_old_api_views(self):
|
||||||
|
for view_route, view_class in self.api_views.items():
|
||||||
|
self.add_view(view_route, view_class)
|
||||||
|
|
||||||
|
def _convert_old_api_form(self):
|
||||||
|
self.set_form(self.api_form)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _name(self):
|
||||||
|
return self.__class__.__name__
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _name_python_safe(self):
|
||||||
|
return camel_to_snake(self._name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _name_uri_safe(self):
|
||||||
|
return camel_to_spine(self._name)
|
||||||
|
|
||||||
|
def _full_name(self):
|
||||||
|
module = self.__class__.__module__
|
||||||
|
if module is None or module == str.__class__.__module__:
|
||||||
|
return self.__class__.__name__ # Avoid reporting __builtin__
|
||||||
|
else:
|
||||||
|
return module + '.' + self.__class__.__name__
|
||||||
|
|
||||||
|
|
||||||
|
class MicroscopePlugin(BasePlugin):
|
||||||
"""
|
"""
|
||||||
Parent class for all microscope plugins.
|
Parent class for all microscope plugins.
|
||||||
|
|
||||||
|
|
@ -278,6 +337,7 @@ class MicroscopePlugin:
|
||||||
api_views = {} # Initially empty dictionary of API views associated with the plugin
|
api_views = {} # Initially empty dictionary of API views associated with the plugin
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
BasePlugin.__init__(self)
|
||||||
self.microscope = (
|
self.microscope = (
|
||||||
None
|
None
|
||||||
) #: :py:class:`openflexure_microscope.microscope.Microscope`: Microscope object
|
) #: :py:class:`openflexure_microscope.microscope.Microscope`: Microscope object
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ class ExamplePlugin(MicroscopePlugin):
|
||||||
api_views = {"/do": DoAPI, "/task": TaskAPI}
|
api_views = {"/do": DoAPI, "/task": TaskAPI}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
MicroscopePlugin.__init__(self)
|
||||||
|
|
||||||
self.val_int = 10
|
self.val_int = 10
|
||||||
self.val_str = "Hello"
|
self.val_str = "Hello"
|
||||||
self.val_radio = "First"
|
self.val_radio = "First"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import re
|
||||||
import copy
|
import copy
|
||||||
import operator
|
import operator
|
||||||
from collections import abc
|
from collections import abc
|
||||||
|
|
@ -5,6 +6,14 @@ from functools import reduce
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
|
||||||
|
def camel_to_snake(name):
|
||||||
|
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
||||||
|
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
||||||
|
|
||||||
|
def camel_to_spine(name):
|
||||||
|
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1-\2', name)
|
||||||
|
return re.sub('([a-z0-9])([A-Z])', r'\1-\2', s1).lower()
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def set_properties(obj, **kwargs):
|
def set_properties(obj, **kwargs):
|
||||||
"""A context manager to set, then reset, certain properties of an object.
|
"""A context manager to set, then reset, certain properties of an object.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue