Commit 3c096e9b authored by jtc42's avatar jtc42
Browse files

Overhauled v2 plugin attaching

parent b8f6193f
Loading
Loading
Loading
Loading
+1 −4
Original line number Diff line number Diff line
@@ -39,10 +39,7 @@ def construct_blueprint(microscope_obj):
    all_routes = []

    # For each plugin attached to the microscope object
    for plugin_representation in microscope_obj.plugins.active:

        plugin_obj = plugin_representation["plugin"]
        plugin_name = plugin_representation["name"]
    for plugin_name, plugin_obj in microscope_obj.plugins._legacy_plugins.items():

        # If plugin contains valid endpoints
        if hasattr(plugin_obj, "api_views") and isinstance(plugin_obj.api_views, dict):
+36 −74
Original line number Diff line number Diff line
@@ -20,20 +20,34 @@ def plugins_representation(plugin_loader_object: PluginLoader):
        dict: Dictionary representation of all plugins
    """
    plugins = []

    print(plugin_loader_object.active)

    for plugin in plugin_loader_object.active:
        logging.info(f"Representing plugin {plugin._name}")
        d = {
            "name": plugin["name"],
            "plugin": str(plugin["plugin"]),
            "routes": plugin["routes"],
            "form": plugin["form"],
            "name": plugin._name,
            "plugin": str(plugin),
            "views": {},
            "form": plugin.form,
        }

        for view_id, view in plugin.views.items():
            logging.info(f"Representing view {view_id}")
            uri = url_for(f"v2_plugins_blueprint.{view_id}")
            # Make links dictionary if it doesn't yet exist
            view_d = {
                "links": {"self": uri}
            }

        for route in d["routes"]:
            route_id = route["id"]
            uri = url_for(f"v2_plugins_blueprint.{route_id}")
            route["links"]["self"] = uri
            d["views"][view_id] = view_d

        print("\n")
        print(d)
        print("\n")
        plugins.append(d)

    print(plugins)
    return plugins


@@ -66,69 +80,20 @@ def construct_blueprint(microscope_obj):
    all_routes = []

    # For each plugin attached to the microscope object
    for plugin_representation in microscope_obj.plugins.active:

        plugin_obj = plugin_representation["plugin"]
        plugin_name = plugin_representation["name"]

        # If plugin contains valid endpoints
        if hasattr(plugin_obj, "api_views") and isinstance(plugin_obj.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_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(
                        "/", "_"
                    )
    for plugin in microscope_obj.plugins.active:

        for plugin_view_id, plugin_view in plugin.views.items():
            # Add route to the plugins blueprint
            blueprint.add_url_rule(
                        full_view_route,
                        view_func=view_class.as_view(
                            plugin_route_id,
                plugin_view["rule"],
                view_func=plugin_view["view"].as_view(
                    plugin_view_id,
                    microscope=microscope_obj,
                            plugin=plugin_obj,
                    plugin=plugin,
                ),
            )

                    # 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 hasattr(plugin_obj, "api_form") and isinstance(
                plugin_obj.api_form, dict
@@ -152,9 +117,6 @@ def construct_blueprint(microscope_obj):
                # Store the complete form in Microscope().plugin.form
                plugin_representation["form"] = api_form_info
                print(microscope_obj.plugins.forms)
            """

        else:
            warnings.warn(
                "No valid 'api_views' dictionary found in {}".format(plugin_obj)
            )
    return blueprint
+2 −0
Original line number Diff line number Diff line
@@ -58,6 +58,8 @@ class ScanPlugin(MicroscopePlugin):
    api_views = {"/tile": TileScanAPI}

    def __init__(self):
        MicroscopePlugin.__init__(self)

        self.images_to_be_captured: int = 1
        update_task_data({"images_to_be_captured": self.images_to_be_captured})

+118 −58
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ import os
import inspect
import logging

from openflexure_microscope.utilities import camel_to_snake, camel_to_spine
from openflexure_microscope.api.views import MicroscopeViewPlugin


@@ -83,7 +84,6 @@ def name_from_module(plugin_path):


def load_plugin_module(plugin_path):

    # If the loader was found (i.e. plugin probably exists)
    if check_module(plugin_path):
        try:
@@ -140,50 +140,6 @@ 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.
@@ -195,6 +151,7 @@ class PluginLoader(object):
    def __init__(self, parent):
        self.parent = parent
        self._plugins = []  # List of plugin objects
        self._legacy_plugins = {}  # DEPRECATED: Dictionary of plugins with old names
        self.forms = []  # List of plugin forms
        logging.info("Creating plugin mount")

@@ -221,6 +178,8 @@ class PluginLoader(object):

        if plugin_class is not None:

            logging.debug(f"Loading plugin class {plugin_class}, {plugin_name}")

            plugin_name_python_safe = plugin_name.replace("/", "_")

            if plugin_class and plugin_name:
@@ -238,23 +197,18 @@ class PluginLoader(object):
                    )

                elif isinstance(
                    plugin_object, MicroscopePlugin
                        plugin_object, BasePlugin
                ):  # If plugin_object is an instance of MicroscopePlugin
                    # Attach plugin_object to the plugin mount
                    setattr(self, plugin_name_python_safe, plugin_object)

                    # Store the plugin object, and it's properties
                    self._plugins.append(
                        {
                            "name": plugin_name,
                            "python_name": plugin_name_python_safe,
                            "plugin": plugin_object,
                            "routes": [],
                            "form": None,
                        }
                    )
                    self._plugins.append(plugin_object)
                    # DEPRECATED: Store the plugin with it's old name
                    self._legacy_plugins[plugin_name_python_safe] = plugin_object

                    # Grant plugin access to the hardware
                    if isinstance(plugin_object, MicroscopePlugin):
                        plugin_object.microscope = self.parent

                    logging.info(
@@ -267,7 +221,112 @@ class PluginLoader(object):
            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.

@@ -278,6 +337,7 @@ class MicroscopePlugin:
    api_views = {}  # Initially empty dictionary of API views associated with the plugin

    def __init__(self):
        BasePlugin.__init__(self)
        self.microscope = (
            None
        )  #: :py:class:`openflexure_microscope.microscope.Microscope`: Microscope object
+2 −0
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ class ExamplePlugin(MicroscopePlugin):
    api_views = {"/do": DoAPI, "/task": TaskAPI}

    def __init__(self):
        MicroscopePlugin.__init__(self)

        self.val_int = 10
        self.val_str = "Hello"
        self.val_radio = "First"
Loading