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

@ -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)

View file

@ -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...")

View file

@ -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]"""

View file

@ -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 = {

View file

@ -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

View file

@ -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

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 os
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.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

View file

@ -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

View file

@ -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

View file

@ -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")

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
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):

View file

@ -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))

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 .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

View file

@ -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)

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, 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)

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
# Task management
from openflexure_microscope.common.tasks import (
from openflexure_microscope.common.labthings_core.tasks import (
current_task,
update_task_progress,
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.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

View file

@ -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
}

View file

@ -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

View file

@ -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,

View file

@ -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):

View file

@ -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()