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

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

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