Restructured labthings
This commit is contained in:
parent
206dd521ec
commit
a8a3cafa97
39 changed files with 275 additions and 269 deletions
|
|
@ -2,7 +2,7 @@ from openflexure_microscope.plugins import MicroscopePlugin
|
|||
from openflexure_microscope.api.views import MicroscopeViewPlugin
|
||||
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 time
|
||||
|
|
|
|||
|
|
@ -3,29 +3,24 @@
|
|||
import time
|
||||
import atexit
|
||||
import logging
|
||||
import sys
|
||||
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 flask_cors import CORS
|
||||
|
||||
from openflexure_microscope.api.exceptions import JSONExceptionHandler
|
||||
from openflexure_microscope.api.utilities import list_routes
|
||||
from openflexure_microscope.api.utilities import list_routes, init_default_plugins
|
||||
|
||||
from openflexure_microscope.config import (
|
||||
settings_file_path,
|
||||
JSONEncoder,
|
||||
USER_PLUGINS_PATH,
|
||||
)
|
||||
from openflexure_microscope.api import v2
|
||||
|
||||
from openflexure_microscope.common.labthings.labthing import LabThing
|
||||
from openflexure_microscope.common.labthings.find import registered_plugins
|
||||
from openflexure_microscope.common.labthings.plugins import find_plugins
|
||||
from openflexure_microscope.common.flask_labthings.labthing import LabThing
|
||||
from openflexure_microscope.common.flask_labthings.plugins import find_plugins
|
||||
|
||||
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")
|
||||
|
||||
logger = logging.getLogger()
|
||||
if (__name__ == "__main__") or (not is_gunicorn):
|
||||
# If imported, but not by gunicorn
|
||||
print("Letting sys handle logs")
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.DEBUG)
|
||||
else:
|
||||
# Direct standard Python logging to file and console
|
||||
root = logging.getLogger()
|
||||
error_formatter = logging.Formatter(
|
||||
"[%(asctime)s] [%(threadName)s] [%(levelname)s] %(message)s"
|
||||
)
|
||||
|
|
@ -56,9 +50,9 @@ else:
|
|||
|
||||
for handler in error_handlers:
|
||||
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
|
||||
|
|
@ -71,9 +65,6 @@ app.json_encoder = JSONEncoder
|
|||
# Enable CORS everywhere
|
||||
CORS(app, resources=r"*")
|
||||
|
||||
# Make errors more API friendly
|
||||
handler = JSONExceptionHandler(app)
|
||||
|
||||
# Build a labthing
|
||||
labthing = LabThing(app, prefix="/api/v2")
|
||||
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")
|
||||
|
||||
# Attach plugins
|
||||
if not os.path.isfile(USER_PLUGINS_PATH):
|
||||
init_default_plugins(USER_PLUGINS_PATH)
|
||||
for plugin in find_plugins(USER_PLUGINS_PATH):
|
||||
labthing.register_plugin(plugin)
|
||||
|
||||
|
|
@ -120,7 +113,7 @@ labthing.register_property(views.SnapshotStream)
|
|||
for name, action in views.enabled_root_actions().items():
|
||||
view_class = action["view_class"]
|
||||
rule = action["rule"]
|
||||
labthing.add_resource(view_class, "/actions{rule}")
|
||||
labthing.add_resource(view_class, f"/actions{rule}")
|
||||
labthing.register_action(view_class)
|
||||
|
||||
|
||||
|
|
@ -159,7 +152,6 @@ def err_log():
|
|||
|
||||
# Automatically clean up microscope at exit
|
||||
def cleanup():
|
||||
global api_microscope
|
||||
logging.debug("App teardown started...")
|
||||
logging.debug("Settling...")
|
||||
time.sleep(0.5)
|
||||
|
|
|
|||
|
|
@ -28,12 +28,8 @@ except Exception as e:
|
|||
|
||||
# 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()
|
||||
|
||||
api_stage = MockStage()
|
||||
|
||||
# Attach devices to microscope
|
||||
logging.debug("Attaching devices to microscope...")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import logging
|
||||
import os
|
||||
import errno
|
||||
from werkzeug.exceptions import BadRequest
|
||||
from flask import url_for, Blueprint
|
||||
|
||||
|
|
@ -95,3 +97,31 @@ def list_routes(app):
|
|||
output[url] = line
|
||||
|
||||
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]"""
|
||||
|
|
@ -2,11 +2,6 @@
|
|||
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
|
||||
|
||||
_actions = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from openflexure_microscope.api.utilities import get_bool, JsonResponse
|
||||
from openflexure_microscope.common.labthings.resource import Resource
|
||||
from openflexure_microscope.common.labthings.find import find_device
|
||||
from openflexure_microscope.common.flask_labthings.resource import Resource
|
||||
from openflexure_microscope.common.flask_labthings.find import find_device
|
||||
from openflexure_microscope.utilities import filter_dict
|
||||
|
||||
from openflexure_microscope.api.v2.views.captures import capture_schema
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from openflexure_microscope.api.utilities import JsonResponse
|
||||
from openflexure_microscope.common.labthings.resource import Resource
|
||||
from openflexure_microscope.common.labthings.find import find_device
|
||||
from openflexure_microscope.common.flask_labthings.resource import Resource
|
||||
from openflexure_microscope.common.flask_labthings.find import find_device
|
||||
from openflexure_microscope.utilities import axes_to_array, filter_dict
|
||||
|
||||
from flask import Blueprint, jsonify, request
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from openflexure_microscope.common.labthings.resource import Resource
|
||||
from openflexure_microscope.common.flask_labthings.resource import Resource
|
||||
import subprocess
|
||||
import os
|
||||
from sys import platform
|
||||
|
|
|
|||
|
|
@ -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.common.labthings.schema import Schema
|
||||
from openflexure_microscope.common.labthings import fields
|
||||
from openflexure_microscope.common.labthings.resource import Resource
|
||||
from openflexure_microscope.common.flask_labthings.schema import Schema
|
||||
from openflexure_microscope.common.flask_labthings import fields
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
from openflexure_microscope.common.flask_labthings.find import find_device
|
||||
|
||||
|
||||
class CaptureList(Resource):
|
||||
|
|
@ -182,6 +147,46 @@ class CaptureMetadata(Resource):
|
|||
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=""):
|
||||
"""
|
||||
Add all capture resources to a labthing
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from openflexure_microscope.api.utilities import JsonResponse
|
||||
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.labthings.resource import Resource
|
||||
from openflexure_microscope.common.flask_labthings.find import find_device
|
||||
from openflexure_microscope.common.flask_labthings.resource import Resource
|
||||
|
||||
from flask import jsonify, request, abort
|
||||
import logging
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
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.common.labthings.find import find_device
|
||||
from openflexure_microscope.common.labthings.resource import Resource
|
||||
from openflexure_microscope.common.flask_labthings.find import find_device
|
||||
from openflexure_microscope.common.flask_labthings.resource import Resource
|
||||
|
||||
from flask import jsonify, request, abort, Response
|
||||
import logging
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from abc import ABCMeta, abstractmethod
|
|||
|
||||
from .capture import CaptureObject
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
from . import tasks, labthings
|
||||
from . import flask_labthings
|
||||
from openflexure_microscope.common.labthings_core import tasks
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class JSONExceptionHandler(object):
|
|||
|
||||
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
|
||||
|
||||
def init_app(self, app):
|
||||
|
|
@ -2,6 +2,7 @@ from marshmallow.fields import *
|
|||
from marshmallow import missing
|
||||
import re
|
||||
from flask import url_for
|
||||
from flask.views import View
|
||||
|
||||
_tpl_pattern = re.compile(r"\s*<\s*(\S*)\s*>\s*")
|
||||
|
||||
|
|
@ -62,7 +63,16 @@ class URLFor(Field):
|
|||
_CHECK_ATTRIBUTE = False
|
||||
|
||||
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
|
||||
Field.__init__(self, **kwargs)
|
||||
|
||||
|
|
@ -70,6 +80,14 @@ class URLFor(Field):
|
|||
"""Output the URL for the endpoint, given the kwargs passed to
|
||||
``__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 = {}
|
||||
for name, attr_tpl in self.params.items():
|
||||
attr_name = _tpl(str(attr_tpl))
|
||||
|
|
@ -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 .views.plugins import PluginListResource
|
||||
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
|
||||
|
||||
|
||||
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.devices = {}
|
||||
|
|
@ -27,6 +28,11 @@ class LabThing(object):
|
|||
self.description = description
|
||||
self.title = title
|
||||
|
||||
if handle_errors:
|
||||
self.error_handler = JSONExceptionHandler()
|
||||
else:
|
||||
self.error_handler = None
|
||||
|
||||
if app is not None:
|
||||
self.init_app(app)
|
||||
|
||||
|
|
@ -35,21 +41,28 @@ class LabThing(object):
|
|||
def init_app(self, app):
|
||||
app.teardown_appcontext(self.teardown)
|
||||
|
||||
# Register Flask extension
|
||||
app.extensions = getattr(app, "extensions", {})
|
||||
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()
|
||||
|
||||
# Add resources, if registered before tying to a Flask app
|
||||
if len(self.resources) > 0:
|
||||
for resource, urls, endpoint, kwargs in self.resources:
|
||||
self._register_view(app, resource, *urls, endpoint=endpoint, **kwargs)
|
||||
|
||||
def teardown(self, exception):
|
||||
print(f"Tearing down devices: {self.devices}")
|
||||
pass
|
||||
|
||||
def _create_base_routes(self):
|
||||
# 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
|
||||
self.add_resource(PluginListResource, "/plugins")
|
||||
self.register_property(PluginListResource)
|
||||
|
|
@ -64,6 +77,7 @@ class LabThing(object):
|
|||
self.devices[device_name] = device_object
|
||||
|
||||
### Plugin stuff
|
||||
|
||||
def register_plugin(self, plugin_object):
|
||||
if isinstance(plugin_object, BasePlugin):
|
||||
self.plugins[plugin_object.name] = plugin_object
|
||||
|
|
@ -196,8 +210,25 @@ class LabThing(object):
|
|||
endpoint = resource.endpoint
|
||||
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
|
||||
def td(self):
|
||||
"""
|
||||
W3C-style Thing Description
|
||||
"""
|
||||
props = {}
|
||||
for key, prop in self.properties.items():
|
||||
props[key] = {}
|
||||
|
|
@ -221,3 +252,5 @@ class LabThing(object):
|
|||
}
|
||||
|
||||
return jsonify(td)
|
||||
|
||||
# TODO: Add a nicer root resource like the old self-documenting system
|
||||
|
|
@ -5,10 +5,8 @@ import copy
|
|||
from importlib import util
|
||||
import sys
|
||||
|
||||
from openflexure_microscope.config import USER_CONFIG_DIR
|
||||
from openflexure_microscope.utilities import (
|
||||
camel_to_snake,
|
||||
camel_to_spine,
|
||||
snake_to_spine,
|
||||
)
|
||||
|
||||
|
|
@ -125,11 +123,6 @@ class BasePlugin:
|
|||
|
||||
|
||||
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}")
|
||||
|
||||
spec = util.spec_from_file_location(module_name, plugin_path)
|
||||
13
openflexure_microscope/common/flask_labthings/utilities.py
Normal file
13
openflexure_microscope/common/flask_labthings/utilities.py
Normal 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
|
||||
|
|
@ -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()))
|
||||
|
|
@ -1,38 +1,10 @@
|
|||
import logging
|
||||
from flask import abort, request, redirect, url_for, send_file, jsonify
|
||||
from flask import abort
|
||||
|
||||
from openflexure_microscope.api.utilities import get_bool, JsonResponse
|
||||
|
||||
from openflexure_microscope.common.labthings.schema import Schema
|
||||
from openflexure_microscope.common.labthings import fields
|
||||
from openflexure_microscope.common.labthings.resource import Resource
|
||||
|
||||
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)
|
||||
from openflexure_microscope.common.flask_labthings.schema import Schema
|
||||
from openflexure_microscope.common.flask_labthings import fields
|
||||
from openflexure_microscope.common.labthings_core import tasks
|
||||
from openflexure_microscope.common.flask_labthings.resource import Resource
|
||||
from openflexure_microscope.common.flask_labthings.utilities import description_from_view
|
||||
|
||||
|
||||
class TaskList(Resource):
|
||||
|
|
@ -59,3 +31,29 @@ class TaskResource(Resource):
|
|||
task.terminate()
|
||||
|
||||
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)
|
||||
|
|
@ -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
|
||||
"""
|
||||
0
openflexure_microscope/common/labthings_core/__init__.py
Normal file
0
openflexure_microscope/common/labthings_core/__init__.py
Normal file
|
|
@ -8,7 +8,7 @@ as well as some Flask imports to simplify API route development
|
|||
from openflexure_microscope.api.utilities import JsonResponse
|
||||
|
||||
# Task management
|
||||
from openflexure_microscope.common.tasks import (
|
||||
from openflexure_microscope.common.labthings_core.tasks import (
|
||||
current_task,
|
||||
update_task_progress,
|
||||
update_task_data,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from openflexure_microscope.camera.base import BaseCamera
|
|||
from openflexure_microscope.camera.mock import MockStreamer
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,5 @@
|
|||
4100,
|
||||
3146
|
||||
],
|
||||
"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"
|
||||
]
|
||||
"jpeg_quality": 75
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
from openflexure_microscope.common.labthings.find import find_device
|
||||
from openflexure_microscope.common.labthings.plugins import BasePlugin
|
||||
from openflexure_microscope.common.flask_labthings.find import find_device
|
||||
from openflexure_microscope.common.flask_labthings.plugins import BasePlugin
|
||||
from openflexure_microscope.microscope import Microscope
|
||||
|
||||
from openflexure_microscope.devel import JsonResponse, request, jsonify, taskify, abort
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ from typing import Tuple
|
|||
from functools import reduce
|
||||
|
||||
from openflexure_microscope.camera.base import generate_basename
|
||||
from openflexure_microscope.common.labthings.find import find_device, find_plugin
|
||||
from openflexure_microscope.common.labthings.plugins import BasePlugin
|
||||
from openflexure_microscope.common.flask_labthings.find import find_device, find_plugin
|
||||
from openflexure_microscope.common.flask_labthings.plugins import BasePlugin
|
||||
|
||||
from openflexure_microscope.devel import (
|
||||
JsonResponse,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import numpy as np
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -41,26 +41,6 @@ def bottom_level_name(obj):
|
|||
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):
|
||||
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
||||
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue