Restructured labthings

This commit is contained in:
jtc42 2019-12-21 12:10:09 +00:00
parent 206dd521ec
commit a8a3cafa97
39 changed files with 275 additions and 269 deletions

View file

@ -2,7 +2,7 @@ from openflexure_microscope.plugins import MicroscopePlugin
from openflexure_microscope.api.views import MicroscopeViewPlugin from openflexure_microscope.api.views import MicroscopeViewPlugin
from openflexure_microscope.api.utilities import JsonResponse from openflexure_microscope.api.utilities import JsonResponse
from openflexure_microscope.common.tasks import taskify from openflexure_microscope.common.labthings_core.tasks import taskify
import os import os
import time import time

View file

@ -3,29 +3,24 @@
import time import time
import atexit import atexit
import logging import logging
import sys
import os import os
from flask import Flask, jsonify, send_file, url_for from flask import Flask, jsonify, send_file
from serial import SerialException
from datetime import datetime from datetime import datetime
from flask_cors import CORS from flask_cors import CORS
from openflexure_microscope.api.exceptions import JSONExceptionHandler from openflexure_microscope.api.utilities import list_routes, init_default_plugins
from openflexure_microscope.api.utilities import list_routes
from openflexure_microscope.config import ( from openflexure_microscope.config import (
settings_file_path, settings_file_path,
JSONEncoder, JSONEncoder,
USER_PLUGINS_PATH, USER_PLUGINS_PATH,
) )
from openflexure_microscope.api import v2
from openflexure_microscope.common.labthings.labthing import LabThing from openflexure_microscope.common.flask_labthings.labthing import LabThing
from openflexure_microscope.common.labthings.find import registered_plugins from openflexure_microscope.common.flask_labthings.plugins import find_plugins
from openflexure_microscope.common.labthings.plugins import find_plugins
from openflexure_microscope.api.microscope import default_microscope as api_microscope from openflexure_microscope.api.microscope import default_microscope as api_microscope
@ -36,14 +31,13 @@ is_gunicorn = "gunicorn" in os.environ.get("SERVER_SOFTWARE", "")
DEFAULT_LOGFILE = settings_file_path("openflexure_microscope.log") DEFAULT_LOGFILE = settings_file_path("openflexure_microscope.log")
logger = logging.getLogger()
if (__name__ == "__main__") or (not is_gunicorn): if (__name__ == "__main__") or (not is_gunicorn):
# If imported, but not by gunicorn # If imported, but not by gunicorn
print("Letting sys handle logs") print("Letting sys handle logs")
logger = logging.getLogger()
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
else: else:
# Direct standard Python logging to file and console # Direct standard Python logging to file and console
root = logging.getLogger()
error_formatter = logging.Formatter( error_formatter = logging.Formatter(
"[%(asctime)s] [%(threadName)s] [%(levelname)s] %(message)s" "[%(asctime)s] [%(threadName)s] [%(levelname)s] %(message)s"
) )
@ -56,9 +50,9 @@ else:
for handler in error_handlers: for handler in error_handlers:
handler.setFormatter(error_formatter) handler.setFormatter(error_formatter)
root.addHandler(handler) logger.addHandler(handler)
root.setLevel(logging.getLogger("gunicorn.error").level) logger.setLevel(logging.getLogger("gunicorn.error").level)
# Create flask app # Create flask app
@ -71,9 +65,6 @@ app.json_encoder = JSONEncoder
# Enable CORS everywhere # Enable CORS everywhere
CORS(app, resources=r"*") CORS(app, resources=r"*")
# Make errors more API friendly
handler = JSONExceptionHandler(app)
# Build a labthing # Build a labthing
labthing = LabThing(app, prefix="/api/v2") labthing = LabThing(app, prefix="/api/v2")
labthing.description = "Test LabThing-based API for OpenFlexure Microscope" labthing.description = "Test LabThing-based API for OpenFlexure Microscope"
@ -83,6 +74,8 @@ labthing.title = f"OpenFlexure Microscope {api_microscope.name}"
labthing.register_device(api_microscope, "openflexure_microscope") labthing.register_device(api_microscope, "openflexure_microscope")
# Attach plugins # Attach plugins
if not os.path.isfile(USER_PLUGINS_PATH):
init_default_plugins(USER_PLUGINS_PATH)
for plugin in find_plugins(USER_PLUGINS_PATH): for plugin in find_plugins(USER_PLUGINS_PATH):
labthing.register_plugin(plugin) labthing.register_plugin(plugin)
@ -120,7 +113,7 @@ labthing.register_property(views.SnapshotStream)
for name, action in views.enabled_root_actions().items(): for name, action in views.enabled_root_actions().items():
view_class = action["view_class"] view_class = action["view_class"]
rule = action["rule"] rule = action["rule"]
labthing.add_resource(view_class, "/actions{rule}") labthing.add_resource(view_class, f"/actions{rule}")
labthing.register_action(view_class) labthing.register_action(view_class)
@ -159,7 +152,6 @@ def err_log():
# Automatically clean up microscope at exit # Automatically clean up microscope at exit
def cleanup(): def cleanup():
global api_microscope
logging.debug("App teardown started...") logging.debug("App teardown started...")
logging.debug("Settling...") logging.debug("Settling...")
time.sleep(0.5) time.sleep(0.5)

View file

@ -28,12 +28,8 @@ except Exception as e:
# Initialise stage # Initialise stage
logging.debug("Creating stage object...") logging.debug("Creating stage object...")
try:
api_stage = SangaStage() api_stage = MockStage()
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 # Attach devices to microscope
logging.debug("Attaching devices to microscope...") logging.debug("Attaching devices to microscope...")

View file

@ -1,4 +1,6 @@
import logging import logging
import os
import errno
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
from flask import url_for, Blueprint from flask import url_for, Blueprint
@ -95,3 +97,31 @@ def list_routes(app):
output[url] = line output[url] = line
return output return output
def create_file(config_path):
if not os.path.exists(os.path.dirname(config_path)):
try:
os.makedirs(os.path.dirname(config_path))
except OSError as exc: # Guard against race condition
if exc.errno != errno.EEXIST:
raise
def init_default_plugins(plugin_path):
global _DEFAULT_PLUGIN_INIT
os.makedirs(os.path.dirname(plugin_path), exist_ok=True)
if not os.path.exists(plugin_path): # If user plugins file doesn't exist
logging.warning("No plugin file found at {}. Creating...".format(plugin_path))
create_file(plugin_path)
logging.info("Populating {}...".format(plugin_path))
with open(plugin_path, "w") as outfile:
outfile.write(_DEFAULT_PLUGIN_INIT)
_DEFAULT_PLUGIN_INIT = """from openflexure_microscope.plugins.v2.autofocus import autofocus_plugin_v2
from openflexure_microscope.plugins.v2.scan import scan_plugin_v2
__plugins__ = [autofocus_plugin_v2, scan_plugin_v2]"""

View file

@ -2,11 +2,6 @@
Top-level representation of enabled actions Top-level representation of enabled actions
""" """
from flask import Blueprint, url_for, jsonify
from openflexure_microscope.api.utilities import blueprint_for_module
from openflexure_microscope.utilities import get_docstring, description_from_view
from . import camera, stage, system from . import camera, stage, system
_actions = { _actions = {

View file

@ -1,6 +1,6 @@
from openflexure_microscope.api.utilities import get_bool, JsonResponse from openflexure_microscope.api.utilities import get_bool, JsonResponse
from openflexure_microscope.common.labthings.resource import Resource from openflexure_microscope.common.flask_labthings.resource import Resource
from openflexure_microscope.common.labthings.find import find_device from openflexure_microscope.common.flask_labthings.find import find_device
from openflexure_microscope.utilities import filter_dict from openflexure_microscope.utilities import filter_dict
from openflexure_microscope.api.v2.views.captures import capture_schema from openflexure_microscope.api.v2.views.captures import capture_schema

View file

@ -1,6 +1,6 @@
from openflexure_microscope.api.utilities import JsonResponse from openflexure_microscope.api.utilities import JsonResponse
from openflexure_microscope.common.labthings.resource import Resource from openflexure_microscope.common.flask_labthings.resource import Resource
from openflexure_microscope.common.labthings.find import find_device from openflexure_microscope.common.flask_labthings.find import find_device
from openflexure_microscope.utilities import axes_to_array, filter_dict from openflexure_microscope.utilities import axes_to_array, filter_dict
from flask import Blueprint, jsonify, request from flask import Blueprint, jsonify, request

View file

@ -1,4 +1,4 @@
from openflexure_microscope.common.labthings.resource import Resource from openflexure_microscope.common.flask_labthings.resource import Resource
import subprocess import subprocess
import os import os
from sys import platform from sys import platform

View file

@ -3,47 +3,12 @@ from flask import abort, request, redirect, url_for, send_file, jsonify
from openflexure_microscope.api.utilities import get_bool, JsonResponse from openflexure_microscope.api.utilities import get_bool, JsonResponse
from openflexure_microscope.common.labthings.schema import Schema from openflexure_microscope.common.flask_labthings.schema import Schema
from openflexure_microscope.common.labthings import fields from openflexure_microscope.common.flask_labthings import fields
from openflexure_microscope.common.labthings.resource import Resource from openflexure_microscope.common.flask_labthings.resource import Resource
from openflexure_microscope.common.flask_labthings.utilities import description_from_view
from openflexure_microscope.common.labthings.find import find_device from openflexure_microscope.common.flask_labthings.find import find_device
class CaptureSchema(Schema):
id = fields.String()
file = fields.String(data_key="path")
exists = fields.Bool(data_key="available")
filename = fields.String()
metadata = fields.Dict()
# TODO: Add HTTP methods
links = fields.Hyperlinks(
{
"self": {
"href": fields.AbsoluteUrlFor("CaptureResource", id="<id>"),
"mimetype": "application/json",
},
"tags": {
"href": fields.AbsoluteUrlFor("CaptureTags", id="<id>"),
"mimetype": "application/json",
},
"metadata": {
"href": fields.AbsoluteUrlFor("CaptureMetadata", id="<id>"),
"mimetype": "application/json",
},
"download": {
"href": fields.AbsoluteUrlFor(
"CaptureDownload", id="<id>", filename="<filename>"
),
"mimetype": "image/jpeg",
},
}
)
capture_schema = CaptureSchema()
capture_list_schema = CaptureSchema(many=True)
class CaptureList(Resource): class CaptureList(Resource):
@ -182,6 +147,46 @@ class CaptureMetadata(Resource):
return jsonify(capture_obj.metadata) return jsonify(capture_obj.metadata)
class CaptureSchema(Schema):
id = fields.String()
file = fields.String(data_key="path")
exists = fields.Bool(data_key="available")
filename = fields.String()
metadata = fields.Dict()
# TODO: Add HTTP methods
links = fields.Hyperlinks(
{
"self": {
"href": fields.AbsoluteUrlFor(CaptureResource, id="<id>"),
"mimetype": "application/json",
**description_from_view(CaptureResource)
},
"tags": {
"href": fields.AbsoluteUrlFor(CaptureTags, id="<id>"),
"mimetype": "application/json",
**description_from_view(CaptureTags)
},
"metadata": {
"href": fields.AbsoluteUrlFor(CaptureMetadata, id="<id>"),
"mimetype": "application/json",
**description_from_view(CaptureMetadata)
},
"download": {
"href": fields.AbsoluteUrlFor(
CaptureDownload, id="<id>", filename="<filename>"
),
"mimetype": "image/jpeg",
**description_from_view(CaptureDownload)
},
}
)
capture_schema = CaptureSchema()
capture_list_schema = CaptureSchema(many=True)
def add_captures_to_labthing(labthing, prefix=""): def add_captures_to_labthing(labthing, prefix=""):
""" """
Add all capture resources to a labthing Add all capture resources to a labthing

View file

@ -1,8 +1,8 @@
from openflexure_microscope.api.utilities import JsonResponse from openflexure_microscope.api.utilities import JsonResponse
from openflexure_microscope.utilities import get_by_path, set_by_path, create_from_path from openflexure_microscope.utilities import get_by_path, set_by_path, create_from_path
from openflexure_microscope.common.labthings.find import find_device from openflexure_microscope.common.flask_labthings.find import find_device
from openflexure_microscope.common.labthings.resource import Resource from openflexure_microscope.common.flask_labthings.resource import Resource
from flask import jsonify, request, abort from flask import jsonify, request, abort
import logging import logging

View file

@ -1,8 +1,8 @@
from openflexure_microscope.api.utilities import gen, JsonResponse from openflexure_microscope.api.utilities import gen, JsonResponse
from openflexure_microscope.utilities import get_by_path, set_by_path, create_from_path from openflexure_microscope.utilities import get_by_path, set_by_path, create_from_path
from openflexure_microscope.common.labthings.find import find_device from openflexure_microscope.common.flask_labthings.find import find_device
from openflexure_microscope.common.labthings.resource import Resource from openflexure_microscope.common.flask_labthings.resource import Resource
from flask import jsonify, request, abort, Response from flask import jsonify, request, abort, Response
import logging import logging

View file

@ -10,7 +10,7 @@ from abc import ABCMeta, abstractmethod
from .capture import CaptureObject from .capture import CaptureObject
from openflexure_microscope.utilities import entry_by_id from openflexure_microscope.utilities import entry_by_id
from openflexure_microscope.common.lock import StrictLock from openflexure_microscope.common.labthings_core.lock import StrictLock
BASE_CAPTURE_PATH = os.path.join(os.path.expanduser("~"), "micrographs") BASE_CAPTURE_PATH = os.path.join(os.path.expanduser("~"), "micrographs")

View file

@ -1 +1,2 @@
from . import tasks, labthings from . import flask_labthings
from openflexure_microscope.common.labthings_core import tasks

View file

@ -24,7 +24,7 @@ class JSONExceptionHandler(object):
status_code = error.code if isinstance(error, HTTPException) else 500 status_code = error.code if isinstance(error, HTTPException) else 500
response = {"status_code": status_code, "message": escape(message)} response = {"code": status_code, "message": escape(message)}
return jsonify(response), status_code return jsonify(response), status_code
def init_app(self, app): def init_app(self, app):

View file

@ -2,6 +2,7 @@ from marshmallow.fields import *
from marshmallow import missing from marshmallow import missing
import re import re
from flask import url_for from flask import url_for
from flask.views import View
_tpl_pattern = re.compile(r"\s*<\s*(\S*)\s*>\s*") _tpl_pattern = re.compile(r"\s*<\s*(\S*)\s*>\s*")
@ -62,7 +63,16 @@ class URLFor(Field):
_CHECK_ATTRIBUTE = False _CHECK_ATTRIBUTE = False
def __init__(self, endpoint, **kwargs): def __init__(self, endpoint, **kwargs):
self.endpoint = endpoint # Handle the case where endpoint is an attached flask View of any kind
if isinstance(endpoint, type) and issubclass(endpoint, View):
self.view_class = endpoint
self.endpoint = None
# Handle cases where endpoint is passed directly as a string
elif type(endpoint) == str:
self.view_class = None
self.endpoint = endpoint
else:
raise RuntimeError(f"Endpoint {endpoint} is not a valid Flask view or endpoint string.")
self.params = kwargs self.params = kwargs
Field.__init__(self, **kwargs) Field.__init__(self, **kwargs)
@ -70,6 +80,14 @@ class URLFor(Field):
"""Output the URL for the endpoint, given the kwargs passed to """Output the URL for the endpoint, given the kwargs passed to
``__init__``. ``__init__``.
""" """
# Get endpoint from view_class, if needed
if self.view_class and not self.endpoint:
if hasattr(self.view_class, "endpoint"):
self.endpoint = self.view_class.endpoint
else:
raise RuntimeError(f"Resource {self.endpoint} has not been added to a LabThing application. Unable to generate URL.")
# Generate URL for
param_values = {} param_values = {}
for name, attr_tpl in self.params.items(): for name, attr_tpl in self.params.items():
attr_name = _tpl(str(attr_tpl)) attr_name = _tpl(str(attr_tpl))

View file

@ -1,16 +1,17 @@
from flask import current_app, _app_ctx_stack, request, url_for, jsonify from flask import url_for, jsonify
from .plugins import BasePlugin from .plugins import BasePlugin
from .views.plugins import PluginListResource from .views.plugins import PluginListResource
from .views.tasks import TaskList, TaskResource from .views.tasks import TaskList, TaskResource
from ..utilities import get_docstring from openflexure_microscope.common.labthings_core.utilities import get_docstring
from .exceptions import JSONExceptionHandler
from . import EXTENSION_NAME from . import EXTENSION_NAME
class LabThing(object): class LabThing(object):
def __init__(self, app=None, prefix="", title="", description=""): def __init__(self, app=None, prefix: str = "", title: str = "", description: str = "", handle_errors: bool = True):
self.app = app self.app = app
self.devices = {} self.devices = {}
@ -27,6 +28,11 @@ class LabThing(object):
self.description = description self.description = description
self.title = title self.title = title
if handle_errors:
self.error_handler = JSONExceptionHandler()
else:
self.error_handler = None
if app is not None: if app is not None:
self.init_app(app) self.init_app(app)
@ -35,21 +41,28 @@ class LabThing(object):
def init_app(self, app): def init_app(self, app):
app.teardown_appcontext(self.teardown) app.teardown_appcontext(self.teardown)
# Register Flask extension
app.extensions = getattr(app, "extensions", {}) app.extensions = getattr(app, "extensions", {})
app.extensions[EXTENSION_NAME] = self app.extensions[EXTENSION_NAME] = self
# Register error handler if one exists
if self.error_handler:
self.error_handler.init_app(self.app)
# Create base routes
self._create_base_routes() self._create_base_routes()
# Add resources, if registered before tying to a Flask app
if len(self.resources) > 0: if len(self.resources) > 0:
for resource, urls, endpoint, kwargs in self.resources: for resource, urls, endpoint, kwargs in self.resources:
self._register_view(app, resource, *urls, endpoint=endpoint, **kwargs) self._register_view(app, resource, *urls, endpoint=endpoint, **kwargs)
def teardown(self, exception): def teardown(self, exception):
print(f"Tearing down devices: {self.devices}") pass
def _create_base_routes(self): def _create_base_routes(self):
# Add thing description to root # Add thing description to root
self.app.add_url_rule(self._complete_url("/", ""), "td", self.td) self.app.add_url_rule(self._complete_url("/td", ""), "td", self.td)
# Add plugin overview # Add plugin overview
self.add_resource(PluginListResource, "/plugins") self.add_resource(PluginListResource, "/plugins")
self.register_property(PluginListResource) self.register_property(PluginListResource)
@ -64,6 +77,7 @@ class LabThing(object):
self.devices[device_name] = device_object self.devices[device_name] = device_object
### Plugin stuff ### Plugin stuff
def register_plugin(self, plugin_object): def register_plugin(self, plugin_object):
if isinstance(plugin_object, BasePlugin): if isinstance(plugin_object, BasePlugin):
self.plugins[plugin_object.name] = plugin_object self.plugins[plugin_object.name] = plugin_object
@ -196,8 +210,25 @@ class LabThing(object):
endpoint = resource.endpoint endpoint = resource.endpoint
return url_for(endpoint, **values) return url_for(endpoint, **values)
def owns_endpoint(self, endpoint):
"""Tests if an endpoint name (not path) belongs to this Api. Takes
in to account the Blueprint name part of the endpoint name.
:param endpoint: The name of the endpoint being checked
:return: bool
"""
if self.blueprint:
if endpoint.startswith(self.blueprint.name):
endpoint = endpoint.split(self.blueprint.name + '.', 1)[-1]
else:
return False
return endpoint in self.endpoints
### Description ### Description
def td(self): def td(self):
"""
W3C-style Thing Description
"""
props = {} props = {}
for key, prop in self.properties.items(): for key, prop in self.properties.items():
props[key] = {} props[key] = {}
@ -221,3 +252,5 @@ class LabThing(object):
} }
return jsonify(td) return jsonify(td)
# TODO: Add a nicer root resource like the old self-documenting system

View file

@ -5,10 +5,8 @@ import copy
from importlib import util from importlib import util
import sys import sys
from openflexure_microscope.config import USER_CONFIG_DIR
from openflexure_microscope.utilities import ( from openflexure_microscope.utilities import (
camel_to_snake, camel_to_snake,
camel_to_spine,
snake_to_spine, snake_to_spine,
) )
@ -125,11 +123,6 @@ class BasePlugin:
def find_plugins(plugin_path, module_name="plugins"): def find_plugins(plugin_path, module_name="plugins"):
print(f"Loading plugins from {plugin_path}")
# plugin_path = os.path.join(USER_CONFIG_DIR, "microscope_plugins")
# plugins = importlib.machinery.SourceFileLoader(
# module_name, plugin_path
# ).exec_module()
logging.debug(f"Loading plugins from {plugin_path}") logging.debug(f"Loading plugins from {plugin_path}")
spec = util.spec_from_file_location(module_name, plugin_path) spec = util.spec_from_file_location(module_name, plugin_path)

View file

@ -0,0 +1,13 @@
from openflexure_microscope.common.labthings_core.utilities import get_docstring
def description_from_view(view_class):
methods = []
for method_key in ["get", "post", "put", "delete"]:
if hasattr(view_class, method_key):
methods.append(method_key.upper())
brief_description = get_docstring(view_class).partition("\n")[0].strip()
d = {"methods": methods, "description": brief_description}
return d

View file

@ -0,0 +1,64 @@
"""
Top-level representation of attached and enabled plugins
"""
from openflexure_microscope.common.labthings_core.utilities import get_docstring
from ..utilities import description_from_view
from openflexure_microscope.common.flask_labthings.find import registered_plugins
from openflexure_microscope.common.flask_labthings.resource import Resource
from flask import jsonify, url_for
import logging
def plugins_representation(plugin_dict):
"""
Generate a dictionary representation of all plugins, including Flask route URLs
Args:
plugin_dict (dict): Dictionary of plugin objects
Returns:
dict: Dictionary representation of all plugins
"""
plugins = {}
for plugin_name, plugin in plugin_dict.items():
logging.debug(f"Representing plugin {plugin._name}")
d = {
"python_name": plugin._name_python_safe,
"plugin": str(plugin),
"links": {},
"gui": plugin.gui,
"description": get_docstring(plugin),
}
for view_id, view_data in plugin.views.items():
uri = url_for(f"PluginListResource", _external=True) + "/" + view_data["rule"][1:]
# Make links dictionary if it doesn't yet exist
view_d = {"href": uri, **description_from_view(view_data["view"])}
d["links"][view_id] = view_d
plugins[plugin_name] = d
return plugins
class PluginListResource(Resource):
def get(self):
"""
Return the current plugin forms
.. :quickref: Plugin; Get forms
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.
"""
return jsonify(plugins_representation(registered_plugins()))

View file

@ -1,38 +1,10 @@
import logging from flask import abort
from flask import abort, request, redirect, url_for, send_file, jsonify
from openflexure_microscope.api.utilities import get_bool, JsonResponse from openflexure_microscope.common.flask_labthings.schema import Schema
from openflexure_microscope.common.flask_labthings import fields
from openflexure_microscope.common.labthings.schema import Schema from openflexure_microscope.common.labthings_core import tasks
from openflexure_microscope.common.labthings import fields from openflexure_microscope.common.flask_labthings.resource import Resource
from openflexure_microscope.common.labthings.resource import Resource from openflexure_microscope.common.flask_labthings.utilities import description_from_view
from openflexure_microscope.common import tasks
class TaskSchema(Schema):
_ID = fields.String(data_key="id")
target_string = fields.String(data_key="function")
_status = fields.String(data_key="status")
progress = fields.String()
data = fields.Raw()
_return_value = fields.Raw(data_key="return")
_start_time = fields.String(data_key="start_time")
_end_time = fields.String(data_key="end_time")
# TODO: Add HTTP methods
links = fields.Hyperlinks(
{
"self": {
"href": fields.AbsoluteUrlFor("TaskResource", id="<id>"),
"mimetype": "application/json",
}
}
)
task_schema = TaskSchema()
task_list_schema = TaskSchema(many=True)
class TaskList(Resource): class TaskList(Resource):
@ -59,3 +31,29 @@ class TaskResource(Resource):
task.terminate() task.terminate()
return task_schema.jsonify(task) return task_schema.jsonify(task)
class TaskSchema(Schema):
_ID = fields.String(data_key="id")
target_string = fields.String(data_key="function")
_status = fields.String(data_key="status")
progress = fields.String()
data = fields.Raw()
_return_value = fields.Raw(data_key="return")
_start_time = fields.String(data_key="start_time")
_end_time = fields.String(data_key="end_time")
# TODO: Add HTTP methods
links = fields.Hyperlinks(
{
"self": {
"href": fields.AbsoluteUrlFor(TaskResource, id="<id>"),
"mimetype": "application/json",
**description_from_view(TaskResource)
}
}
)
task_schema = TaskSchema()
task_list_schema = TaskSchema(many=True)

View file

@ -1,106 +0,0 @@
"""
Top-level representation of attached and enabled plugins
"""
from openflexure_microscope.utilities import get_docstring, description_from_view
from openflexure_microscope.api.utilities import blueprint_for_module
from openflexure_microscope.common.labthings.find import registered_plugins
from openflexure_microscope.common.labthings.resource import Resource
from flask import Blueprint, jsonify, url_for
import copy
import logging
import warnings
def plugins_representation(plugin_dict):
"""
Generate a dictionary representation of all plugins, including Flask route URLs
Args:
plugin_loader_object (:py:class:`openflexure_microscope.plugins.PluginLoader`): Microscope plugin loader
Returns:
dict: Dictionary representation of all plugins
"""
plugins = {}
for plugin_name, plugin in plugin_dict.items():
logging.debug(f"Representing plugin {plugin._name}")
d = {
"python_name": plugin._name_python_safe,
"plugin": str(plugin),
"views": {},
"gui": plugin.gui,
"description": get_docstring(plugin),
}
for view_id, view_data in plugin.views.items():
logging.debug(f"Representing view {view_id}")
uri = url_for(f"PluginListResource") + "/" + view_data["rule"][1:]
# uri = view_data["rule"]
# Make links dictionary if it doesn't yet exist
view_d = {"links": {"self": uri}}
view_d.update(description_from_view(view_data["view"]))
d["views"][view_id] = view_d
plugins[plugin_name] = d
return plugins
class PluginListResource(Resource):
def get(self):
"""
Return the current plugin forms
.. :quickref: Plugin; Get forms
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.
"""
return jsonify(plugins_representation(registered_plugins()))
"""
def construct_blueprint():
blueprint = blueprint_for_module(__name__)
for plugin_obj in registered_plugins():
for plugin_view_id, plugin_view in plugin_obj.views.items():
# Add route to the plugins blueprint
blueprint.add_url_rule(
plugin_view["rule"],
view_func=plugin_view["view"].as_view(
f"{plugin_obj._name_python_safe}_{plugin_view_id}"
),
**plugin_view["kwargs"],
)
# Create a base route to return plugin API forms, if any exist
blueprint.add_url_rule("/", view_func=PluginFormAPI.as_view("plugins"))
# For each plugin attached to the microscope object
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(
plugin_view["rule"],
view_func=plugin_view["view"].as_view(
f"{plugin._name_python_safe}_{plugin_view_id}", plugin=plugin
),
**plugin_view["kwargs"],
)
return blueprint
"""

View file

@ -8,7 +8,7 @@ as well as some Flask imports to simplify API route development
from openflexure_microscope.api.utilities import JsonResponse from openflexure_microscope.api.utilities import JsonResponse
# Task management # Task management
from openflexure_microscope.common.tasks import ( from openflexure_microscope.common.labthings_core.tasks import (
current_task, current_task,
update_task_progress, update_task_progress,
update_task_data, update_task_data,

View file

@ -12,7 +12,7 @@ from openflexure_microscope.camera.base import BaseCamera
from openflexure_microscope.camera.mock import MockStreamer from openflexure_microscope.camera.mock import MockStreamer
from openflexure_microscope.utilities import serialise_array_b64 from openflexure_microscope.utilities import serialise_array_b64
from openflexure_microscope.common.lock import CompositeLock from openflexure_microscope.common.labthings_core.lock import CompositeLock
from openflexure_microscope.config import user_settings from openflexure_microscope.config import user_settings

View file

@ -3,11 +3,5 @@
4100, 4100,
3146 3146
], ],
"jpeg_quality": 75, "jpeg_quality": 75
"plugins": [
"openflexure_microscope.plugins.default.autofocus:AutofocusPlugin",
"openflexure_microscope.plugins.default.scan:ScanPlugin",
"openflexure_microscope.plugins.default.camera_calibration:AutocalibrationPlugin",
"openflexure_microscope.plugins.default.zip_builder:ZipBuilderPlugin"
]
} }

View file

@ -1,5 +1,5 @@
from openflexure_microscope.common.labthings.find import find_device from openflexure_microscope.common.flask_labthings.find import find_device
from openflexure_microscope.common.labthings.plugins import BasePlugin from openflexure_microscope.common.flask_labthings.plugins import BasePlugin
from openflexure_microscope.microscope import Microscope from openflexure_microscope.microscope import Microscope
from openflexure_microscope.devel import JsonResponse, request, jsonify, taskify, abort from openflexure_microscope.devel import JsonResponse, request, jsonify, taskify, abort

View file

@ -6,8 +6,8 @@ from typing import Tuple
from functools import reduce from functools import reduce
from openflexure_microscope.camera.base import generate_basename from openflexure_microscope.camera.base import generate_basename
from openflexure_microscope.common.labthings.find import find_device, find_plugin from openflexure_microscope.common.flask_labthings.find import find_device, find_plugin
from openflexure_microscope.common.labthings.plugins import BasePlugin from openflexure_microscope.common.flask_labthings.plugins import BasePlugin
from openflexure_microscope.devel import ( from openflexure_microscope.devel import (
JsonResponse, JsonResponse,

View file

@ -1,6 +1,6 @@
import numpy as np import numpy as np
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from openflexure_microscope.common.lock import StrictLock from openflexure_microscope.common.labthings_core.lock import StrictLock
class BaseStage(metaclass=ABCMeta): class BaseStage(metaclass=ABCMeta):

View file

@ -41,26 +41,6 @@ def bottom_level_name(obj):
return obj.__name__.split(".")[-1] return obj.__name__.split(".")[-1]
def description_from_view(view_class):
methods = []
for method_key in ["get", "post", "put", "delete"]:
if hasattr(view_class, method_key):
methods.append(method_key.upper())
brief_description = get_docstring(view_class).partition("\n")[0].strip()
d = {"methods": methods, "description": brief_description}
return d
def get_docstring(obj):
ds = obj.__doc__
if ds:
return ds.strip()
else:
return ""
def camel_to_snake(name): def camel_to_snake(name):
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", 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() return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()