Restructured labthings
This commit is contained in:
parent
206dd521ec
commit
a8a3cafa97
39 changed files with 275 additions and 269 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
from flask import jsonify, escape
|
||||
from werkzeug.exceptions import default_exceptions
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
|
||||
class JSONExceptionHandler(object):
|
||||
|
||||
"""
|
||||
A class to be registered as a Flask error handler,
|
||||
converts error codes into a JSON response
|
||||
"""
|
||||
|
||||
def __init__(self, app=None):
|
||||
if app:
|
||||
self.init_app(app)
|
||||
|
||||
def std_handler(self, error):
|
||||
if isinstance(error, HTTPException):
|
||||
message = error.description
|
||||
elif hasattr(error, "message"):
|
||||
message = error.message
|
||||
else:
|
||||
message = str(error)
|
||||
|
||||
status_code = error.code if isinstance(error, HTTPException) else 500
|
||||
|
||||
response = {"status_code": status_code, "message": escape(message)}
|
||||
return jsonify(response), status_code
|
||||
|
||||
def init_app(self, app):
|
||||
self.app = app
|
||||
self.register(HTTPException)
|
||||
for code, v in default_exceptions.items():
|
||||
self.register(code)
|
||||
|
||||
def register(self, exception_or_code, handler=None):
|
||||
self.app.errorhandler(exception_or_code)(handler or self.std_handler)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue