Commit 477abb69 authored by Joel Collins's avatar Joel Collins
Browse files

First draft of new API plugin system

parent 3a63f7d2
Loading
Loading
Loading
Loading
+28 −49
Original line number Diff line number Diff line
@@ -16,25 +16,18 @@ from flask_cors import CORS
from openflexure_microscope.api.exceptions import JSONExceptionHandler
from openflexure_microscope.api.utilities import list_routes

from openflexure_microscope import Microscope

from openflexure_microscope.camera.capture import build_captures_from_exif
from openflexure_microscope.config import settings_file_path, JSONEncoder
from openflexure_microscope.config import (
    settings_file_path,
    JSONEncoder,
    USER_PLUGINS_PATH,
)
from openflexure_microscope.api.v1 import blueprints
from openflexure_microscope.api import v2

# Import device modules
# NB this will eventually be handled by the RC file, so you can choose what device
# class should be attached.
try:
    from openflexure_microscope.camera.pi import PiCameraStreamer
except ImportError:
    logging.warning("Unable to import PiCameraStreamer")
from openflexure_microscope.camera.mock import MockStreamer

from openflexure_microscope.stage.sanga import SangaStage
from openflexure_microscope.stage.mock import MockStage
from openflexure_microscope.common.labthings import LabThing
from openflexure_microscope.common.labthings.plugins import find_plugins

from openflexure_microscope.api.microscope import default_microscope as api_microscope

# Handle logging
is_gunicorn = "gunicorn" in os.environ.get("SERVER_SOFTWARE", "")
@@ -54,7 +47,7 @@ else:
    )

    rotating_logfile = logging.handlers.RotatingFileHandler(
        DEFAULT_LOGFILE, maxBytes=1000000, backupCount=7
        DEFAULT_LOGFILE, maxBytes=1_000_000, backupCount=7
    )

    error_handlers = [rotating_logfile, logging.StreamHandler()]
@@ -65,39 +58,6 @@ else:

    root.setLevel(logging.getLogger("gunicorn.error").level)

# Create a dummy microscope object, with no hardware attachments
api_microscope = Microscope()

# Initialise camera
logging.debug("Creating camera object...")
try:
    api_camera = PiCameraStreamer()
except Exception as e:
    logging.error(e)
    logging.warning("No valid camera hardware found. Falling back to mock camera!")
    api_camera = MockStreamer()

# Initialise stage
logging.debug("Creating stage object...")
try:
    api_stage = SangaStage()
except Exception as e:
    logging.error(e)
    logging.warning("No valid stage hardware found. Falling back to mock stage!")
    api_stage = MockStage()

# Attach devices to microscope
logging.debug("Attaching devices to microscope...")
api_microscope.attach(api_camera, api_stage)

# Restore loaded capture array to camera object
logging.debug("Restoring captures...")
api_microscope.camera.images = build_captures_from_exif(
    api_microscope.camera.paths["default"]
)

logging.debug("Microscope successfully attached!")


# Generate API URI based on version from filename
def uri(suffix, api_version, base=None):
@@ -121,6 +81,10 @@ CORS(app, resources=r"*")
# Make errors more API friendly
handler = JSONExceptionHandler(app)

# Attach lab devices
labthing = LabThing(app)
labthing.register_device(api_microscope, "openflexure_microscope")

# WEBAPP ROUTES

# Base routes
@@ -176,6 +140,21 @@ v2_actions_blueprint = v2.blueprints.actions.construct_blueprint(api_microscope)
app.register_blueprint(v2_actions_blueprint, url_prefix=uri("/actions", "v2"))


plugins = find_plugins(USER_PLUGINS_PATH)
print(plugins.__plugins__)

for plugin_obj in plugins.__plugins__:
    for plugin_view_id, plugin_view in plugin_obj.views.items():
        # Add route to the plugins blueprint
        app.add_url_rule(
            "/new-plugins" + plugin_view["rule"],
            view_func=plugin_view["view"].as_view(
                f"{plugin_obj._name_python_safe}_{plugin_view_id}"
            ),
            **plugin_view["kwargs"],
        )


@app.route("/routes")
def routes():
    """
+48 −0
Original line number Diff line number Diff line
from openflexure_microscope import Microscope
from openflexure_microscope.camera.capture import build_captures_from_exif

import logging

# Import device modules
# NB this will eventually be handled by the RC file, so you can choose what device
# class should be attached.
try:
    from openflexure_microscope.camera.pi import PiCameraStreamer
except ImportError:
    logging.warning("Unable to import PiCameraStreamer")
from openflexure_microscope.camera.mock import MockStreamer

from openflexure_microscope.stage.sanga import SangaStage
from openflexure_microscope.stage.mock import MockStage

default_microscope = Microscope()

# Initialise camera
logging.debug("Creating camera object...")
try:
    api_camera = PiCameraStreamer()
except Exception as e:
    logging.error(e)
    logging.warning("No valid camera hardware found. Falling back to mock camera!")
    api_camera = MockStreamer()

# Initialise stage
logging.debug("Creating stage object...")
try:
    api_stage = SangaStage()
except Exception as e:
    logging.error(e)
    logging.warning("No valid stage hardware found. Falling back to mock stage!")
    api_stage = MockStage()

# Attach devices to microscope
logging.debug("Attaching devices to microscope...")
default_microscope.attach(api_camera, api_stage)

# Restore loaded capture array to camera object
logging.debug("Restoring captures...")
default_microscope.camera.images = build_captures_from_exif(
    default_microscope.camera.paths["default"]
)

logging.debug("Microscope successfully attached!")
+1 −0
Original line number Diff line number Diff line
from . import tasks, labthings
+37 −0
Original line number Diff line number Diff line
from flask import current_app, _app_ctx_stack

EXTENSION_NAME = "flask-lab"


class LabThing(object):
    def __init__(self, app=None):
        self.app = app
        self.devices = {}
        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        app.teardown_appcontext(self.teardown)

        app.extensions = getattr(app, "extensions", {})
        app.extensions[EXTENSION_NAME] = self

    def teardown(self, exception):
        print(f"Tearing down devices: {self.devices}")

    def register_device(self, device_object, device_name: str):
        self.devices[device_name] = device_object


def find_device(device_name):
    app = current_app

    if not app:
        return None

    pylot_instance = app.extensions[EXTENSION_NAME]

    if device_name in pylot_instance.devices:
        return pylot_instance.devices[device_name]
    else:
        return None
+0 −0

Empty file added.

Loading