Blackened
This commit is contained in:
parent
36f195d6e7
commit
b430a2d34a
15 changed files with 72 additions and 54 deletions
|
|
@ -2,13 +2,19 @@ import logging
|
||||||
from werkzeug.exceptions import BadRequest
|
from werkzeug.exceptions import BadRequest
|
||||||
from flask import url_for, Blueprint
|
from flask import url_for, Blueprint
|
||||||
|
|
||||||
|
|
||||||
def blueprint_for_module(module_name, api_ver=2, suffix=""):
|
def blueprint_for_module(module_name, api_ver=2, suffix=""):
|
||||||
return Blueprint(blueprint_name_for_module(module_name, api_ver=api_ver, suffix=suffix), module_name)
|
return Blueprint(
|
||||||
|
blueprint_name_for_module(module_name, api_ver=api_ver, suffix=suffix),
|
||||||
|
module_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def blueprint_name_for_module(module_name, api_ver=2, suffix=""):
|
def blueprint_name_for_module(module_name, api_ver=2, suffix=""):
|
||||||
bp_name = module_name.split('.')[-1]
|
bp_name = module_name.split(".")[-1]
|
||||||
return f"v{api_ver}_{bp_name}_blueprint{suffix}"
|
return f"v{api_ver}_{bp_name}_blueprint{suffix}"
|
||||||
|
|
||||||
|
|
||||||
class JsonResponse:
|
class JsonResponse:
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from openflexure_microscope.api.views import MicroscopeView
|
||||||
|
|
||||||
from . import camera, stage, system
|
from . import camera, stage, system
|
||||||
|
|
||||||
|
|
||||||
class ActionsAPI(MicroscopeView):
|
class ActionsAPI(MicroscopeView):
|
||||||
def get(self):
|
def get(self):
|
||||||
return jsonify(actions_representation())
|
return jsonify(actions_representation())
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ class CaptureAPI(MicroscopeView):
|
||||||
"""
|
"""
|
||||||
Create a new image capture.
|
Create a new image capture.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def post(self):
|
def post(self):
|
||||||
"""
|
"""
|
||||||
Create a new image capture.
|
Create a new image capture.
|
||||||
|
|
@ -107,6 +108,7 @@ class GPUPreviewStartAPI(MicroscopeView):
|
||||||
"""
|
"""
|
||||||
Start the onboard GPU preview.
|
Start the onboard GPU preview.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def post(self, operation):
|
def post(self, operation):
|
||||||
"""
|
"""
|
||||||
Start the onboard GPU preview.
|
Start the onboard GPU preview.
|
||||||
|
|
@ -152,6 +154,7 @@ class GPUPreviewStopAPI(MicroscopeView):
|
||||||
"""
|
"""
|
||||||
Stop the onboard GPU preview.
|
Stop the onboard GPU preview.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def post(self, operation):
|
def post(self, operation):
|
||||||
"""
|
"""
|
||||||
Stop the onboard GPU preview.
|
Stop the onboard GPU preview.
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ class MoveStageAPI(MicroscopeView):
|
||||||
"""
|
"""
|
||||||
Handle stage movements.
|
Handle stage movements.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def post(self):
|
def post(self):
|
||||||
"""
|
"""
|
||||||
Set x, y and z positions of the stage.
|
Set x, y and z positions of the stage.
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ class ShutdownAPI(MicroscopeView):
|
||||||
"""
|
"""
|
||||||
Attempt to shutdown the device
|
Attempt to shutdown the device
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def post(self):
|
def post(self):
|
||||||
"""
|
"""
|
||||||
Attempt to shutdown the device
|
Attempt to shutdown the device
|
||||||
|
|
@ -23,6 +24,7 @@ class RebootAPI(MicroscopeView):
|
||||||
"""
|
"""
|
||||||
Attempt to reboot the device
|
Attempt to reboot the device
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def post(self):
|
def post(self):
|
||||||
"""
|
"""
|
||||||
Attempt to shutdown the device
|
Attempt to shutdown the device
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import copy
|
||||||
import logging
|
import logging
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
|
||||||
def plugins_representation(plugin_loader_object: PluginLoader):
|
def plugins_representation(plugin_loader_object: PluginLoader):
|
||||||
"""
|
"""
|
||||||
Generate a dictionary representation of all plugins, including Flask route URLs
|
Generate a dictionary representation of all plugins, including Flask route URLs
|
||||||
|
|
@ -33,16 +34,14 @@ def plugins_representation(plugin_loader_object: PluginLoader):
|
||||||
"plugin": str(plugin),
|
"plugin": str(plugin),
|
||||||
"views": {},
|
"views": {},
|
||||||
"gui": plugin.gui,
|
"gui": plugin.gui,
|
||||||
"description": get_docstring(plugin)
|
"description": get_docstring(plugin),
|
||||||
}
|
}
|
||||||
|
|
||||||
for view_id, view_data in plugin.views.items():
|
for view_id, view_data in plugin.views.items():
|
||||||
logging.debug(f"Representing view {view_id}")
|
logging.debug(f"Representing view {view_id}")
|
||||||
uri = url_for(f"v2_plugins_blueprint.{view_id}")
|
uri = url_for(f"v2_plugins_blueprint.{view_id}")
|
||||||
# Make links dictionary if it doesn't yet exist
|
# Make links dictionary if it doesn't yet exist
|
||||||
view_d = {
|
view_d = {"links": {"self": uri}}
|
||||||
"links": {"self": uri}
|
|
||||||
}
|
|
||||||
|
|
||||||
view_d.update(description_from_view(view_data["view"]))
|
view_d.update(description_from_view(view_data["view"]))
|
||||||
|
|
||||||
|
|
@ -87,9 +86,7 @@ def construct_blueprint(microscope_obj):
|
||||||
blueprint.add_url_rule(
|
blueprint.add_url_rule(
|
||||||
plugin_view["rule"],
|
plugin_view["rule"],
|
||||||
view_func=plugin_view["view"].as_view(
|
view_func=plugin_view["view"].as_view(
|
||||||
plugin_view_id,
|
plugin_view_id, microscope=microscope_obj, plugin=plugin
|
||||||
microscope=microscope_obj,
|
|
||||||
plugin=plugin,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,19 @@ from openflexure_microscope.api.views import MicroscopeView
|
||||||
|
|
||||||
from openflexure_microscope.utilities import get_docstring, bottom_level_name
|
from openflexure_microscope.utilities import get_docstring, bottom_level_name
|
||||||
from openflexure_microscope.api.utilities import blueprint_name_for_module
|
from openflexure_microscope.api.utilities import blueprint_name_for_module
|
||||||
from openflexure_microscope.api.v2.blueprints import settings, status, plugins, captures, actions, streams
|
from openflexure_microscope.api.v2.blueprints import (
|
||||||
|
settings,
|
||||||
|
status,
|
||||||
|
plugins,
|
||||||
|
captures,
|
||||||
|
actions,
|
||||||
|
streams,
|
||||||
|
)
|
||||||
|
|
||||||
# List of submodules containing create_blueprint methods using standard blueprint_for_module naming
|
# List of submodules containing create_blueprint methods using standard blueprint_for_module naming
|
||||||
_root_blueprint_modules = [settings, status, plugins, captures, actions, streams]
|
_root_blueprint_modules = [settings, status, plugins, captures, actions, streams]
|
||||||
|
|
||||||
|
|
||||||
def root_representation():
|
def root_representation():
|
||||||
"""
|
"""
|
||||||
Generate a dictionar representation of all top-level blueprint rules
|
Generate a dictionar representation of all top-level blueprint rules
|
||||||
|
|
@ -23,7 +31,7 @@ def root_representation():
|
||||||
d[module_short_name] = {
|
d[module_short_name] = {
|
||||||
"name": blueprint_module.__name__,
|
"name": blueprint_module.__name__,
|
||||||
"description": get_docstring(blueprint_module),
|
"description": get_docstring(blueprint_module),
|
||||||
"links": {"self": url_for(f"{blueprint_name}.{module_short_name}")}
|
"links": {"self": url_for(f"{blueprint_name}.{module_short_name}")},
|
||||||
}
|
}
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from openflexure_microscope.api.utilities import blueprint_for_module
|
||||||
|
|
||||||
from flask import Blueprint, jsonify
|
from flask import Blueprint, jsonify
|
||||||
|
|
||||||
|
|
||||||
class StatusAPI(MicroscopeView):
|
class StatusAPI(MicroscopeView):
|
||||||
def get(self):
|
def get(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@ Top-level description of routes related to live camera stream data
|
||||||
|
|
||||||
from openflexure_microscope.api.utilities import gen, JsonResponse
|
from openflexure_microscope.api.utilities import gen, JsonResponse
|
||||||
from openflexure_microscope.api.views import MicroscopeView
|
from openflexure_microscope.api.views import MicroscopeView
|
||||||
from openflexure_microscope.api.utilities import blueprint_for_module, blueprint_name_for_module
|
from openflexure_microscope.api.utilities import (
|
||||||
|
blueprint_for_module,
|
||||||
|
blueprint_name_for_module,
|
||||||
|
)
|
||||||
from openflexure_microscope.utilities import description_from_view
|
from openflexure_microscope.utilities import description_from_view
|
||||||
|
|
||||||
from flask import Response, Blueprint, jsonify, request, url_for
|
from flask import Response, Blueprint, jsonify, request, url_for
|
||||||
|
|
@ -19,6 +22,7 @@ class MjpegAPI(MicroscopeView):
|
||||||
"""
|
"""
|
||||||
Real-time MJPEG stream from the microscope camera
|
Real-time MJPEG stream from the microscope camera
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
"""
|
"""
|
||||||
Real-time MJPEG stream from the microscope camera
|
Real-time MJPEG stream from the microscope camera
|
||||||
|
|
@ -42,6 +46,7 @@ class SnapshotAPI(MicroscopeView):
|
||||||
"""
|
"""
|
||||||
Single JPEG snapshot from the camera stream
|
Single JPEG snapshot from the camera stream
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
"""
|
"""
|
||||||
Single snapshot from the camera stream
|
Single snapshot from the camera stream
|
||||||
|
|
@ -59,16 +64,8 @@ class SnapshotAPI(MicroscopeView):
|
||||||
|
|
||||||
|
|
||||||
_streams = {
|
_streams = {
|
||||||
"mjpeg": {
|
"mjpeg": {"rule": "/mjpeg", "view_class": MjpegAPI, "conditions": True},
|
||||||
"rule": "/mjpeg",
|
"snapshot": {"rule": "/snapshot", "view_class": SnapshotAPI, "conditions": True},
|
||||||
"view_class": MjpegAPI,
|
|
||||||
"conditions": True,
|
|
||||||
},
|
|
||||||
"snapshot": {
|
|
||||||
"rule": "/snapshot",
|
|
||||||
"view_class": SnapshotAPI,
|
|
||||||
"conditions": True,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -82,9 +79,7 @@ def streams_representation():
|
||||||
|
|
||||||
streams = {}
|
streams = {}
|
||||||
for name, stream in enabled_streams().items():
|
for name, stream in enabled_streams().items():
|
||||||
d = {
|
d = {"links": {"self": url_for(f".{name}")}}
|
||||||
"links": {"self": url_for(f".{name}")},
|
|
||||||
}
|
|
||||||
|
|
||||||
d.update(description_from_view(stream["view_class"]))
|
d.update(description_from_view(stream["view_class"]))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ from openflexure_microscope.camera.base import BaseCamera
|
||||||
PIL spams the logger with debug-level information. This is a pain when debugging api.app.
|
PIL spams the logger with debug-level information. This is a pain when debugging api.app.
|
||||||
We override the logging settings in api.app by setting a level for PIL here.
|
We override the logging settings in api.app by setting a level for PIL here.
|
||||||
"""
|
"""
|
||||||
pil_logger = logging.getLogger('PIL')
|
pil_logger = logging.getLogger("PIL")
|
||||||
pil_logger.setLevel(logging.INFO)
|
pil_logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
# MAIN CLASS
|
# MAIN CLASS
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ class AutofocusAPI(MicroscopeViewPlugin):
|
||||||
"""
|
"""
|
||||||
Run a standard autofocus
|
Run a standard autofocus
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def post(self):
|
def post(self):
|
||||||
payload = JsonResponse(request)
|
payload = JsonResponse(request)
|
||||||
|
|
||||||
|
|
@ -42,6 +43,7 @@ class FastAutofocusAPI(MicroscopeViewPlugin):
|
||||||
"""
|
"""
|
||||||
Run a fast autofocus
|
Run a fast autofocus
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def post(self):
|
def post(self):
|
||||||
payload = JsonResponse(request)
|
payload = JsonResponse(request)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,9 @@ class PluginLoader(object):
|
||||||
|
|
||||||
logging.info(
|
logging.info(
|
||||||
ConColors.OKGREEN
|
ConColors.OKGREEN
|
||||||
+ "Plugin {} loaded as {}.".format(plugin_map, plugin_object._name)
|
+ "Plugin {} loaded as {}.".format(
|
||||||
|
plugin_map, plugin_object._name
|
||||||
|
)
|
||||||
+ ConColors.ENDC
|
+ ConColors.ENDC
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -228,7 +230,9 @@ class BasePlugin:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._views = {} # Key: Full, Python-safe ID. Val: Original rule, and view class
|
self._views = (
|
||||||
|
{}
|
||||||
|
) # Key: Full, Python-safe ID. Val: Original rule, and view class
|
||||||
self._rules = {} # Key: Original rule. Val: View class
|
self._rules = {} # Key: Original rule. Val: View class
|
||||||
self._gui = None
|
self._gui = None
|
||||||
|
|
||||||
|
|
@ -258,10 +262,7 @@ class BasePlugin:
|
||||||
view_id = cleaned_rule.replace("/", "_")
|
view_id = cleaned_rule.replace("/", "_")
|
||||||
|
|
||||||
# Store route information in a dictionary
|
# Store route information in a dictionary
|
||||||
d = {
|
d = {"rule": full_rule, "view": view_class}
|
||||||
"rule": full_rule,
|
|
||||||
"view": view_class
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add view to private views dictionary
|
# Add view to private views dictionary
|
||||||
self._views[view_id] = d
|
self._views[view_id] = d
|
||||||
|
|
@ -276,17 +277,13 @@ class BasePlugin:
|
||||||
api_gui = copy.deepcopy(self._gui)
|
api_gui = copy.deepcopy(self._gui)
|
||||||
api_gui["id"] = self._name
|
api_gui["id"] = self._name
|
||||||
|
|
||||||
if "forms" in api_gui and isinstance(
|
if "forms" in api_gui and isinstance(api_gui["forms"], list):
|
||||||
api_gui["forms"], list
|
|
||||||
):
|
|
||||||
for form in api_gui["forms"]:
|
for form in api_gui["forms"]:
|
||||||
if "route" in form and form["route"] in self._rules.keys():
|
if "route" in form and form["route"] in self._rules.keys():
|
||||||
form["route"] = self._rules[form["route"]]["rule"]
|
form["route"] = self._rules[form["route"]]["rule"]
|
||||||
else:
|
else:
|
||||||
logging.warn(
|
logging.warn(
|
||||||
"No valid expandable route found for {}".format(
|
"No valid expandable route found for {}".format(form["route"])
|
||||||
form["route"]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return api_gui
|
return api_gui
|
||||||
|
|
||||||
|
|
@ -317,7 +314,7 @@ class BasePlugin:
|
||||||
if module is None or module == str.__class__.__module__:
|
if module is None or module == str.__class__.__module__:
|
||||||
return self.__class__.__name__ # Avoid reporting __builtin__
|
return self.__class__.__name__ # Avoid reporting __builtin__
|
||||||
else:
|
else:
|
||||||
return module + '.' + self.__class__.__name__
|
return module + "." + self.__class__.__name__
|
||||||
|
|
||||||
|
|
||||||
class MicroscopePlugin(BasePlugin):
|
class MicroscopePlugin(BasePlugin):
|
||||||
|
|
|
||||||
|
|
@ -213,7 +213,9 @@ class ExtensibleSerialInstrument(object):
|
||||||
return self.read_multiline(termination_line)
|
return self.read_multiline(termination_line)
|
||||||
else:
|
else:
|
||||||
logging.debug("Reading response...")
|
logging.debug("Reading response...")
|
||||||
line = self.readline().strip() # question: should we strip the final newline?
|
line = (
|
||||||
|
self.readline().strip()
|
||||||
|
) # question: should we strip the final newline?
|
||||||
logging.debug(f"Read finished. Got {line}")
|
logging.debug(f"Read finished. Got {line}")
|
||||||
return line
|
return line
|
||||||
|
|
||||||
|
|
@ -223,7 +225,7 @@ class ExtensibleSerialInstrument(object):
|
||||||
response_string=r"%d",
|
response_string=r"%d",
|
||||||
re_flags=0,
|
re_flags=0,
|
||||||
parse_function=None,
|
parse_function=None,
|
||||||
**kwargs
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Perform a query, returning a parsed form of the response.
|
Perform a query, returning a parsed form of the response.
|
||||||
|
|
|
||||||
|
|
@ -5,23 +5,23 @@ from collections import abc
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
|
||||||
def bottom_level_name(obj):
|
def bottom_level_name(obj):
|
||||||
return obj.__name__.split('.')[-1]
|
return obj.__name__.split(".")[-1]
|
||||||
|
|
||||||
|
|
||||||
def description_from_view(view_class):
|
def description_from_view(view_class):
|
||||||
methods = []
|
methods = []
|
||||||
for method_key in ["get", "post", "put", "delete"]:
|
for method_key in ["get", "post", "put", "delete"]:
|
||||||
if hasattr(view_class, method_key):
|
if hasattr(view_class, method_key):
|
||||||
methods.append(method_key.upper())
|
methods.append(method_key.upper())
|
||||||
brief_description = get_docstring(view_class).partition('\n')[0].strip()
|
brief_description = get_docstring(view_class).partition("\n")[0].strip()
|
||||||
|
|
||||||
d = {
|
d = {"methods": methods, "description": brief_description}
|
||||||
"methods": methods,
|
|
||||||
"description": brief_description
|
|
||||||
}
|
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
def get_docstring(obj):
|
def get_docstring(obj):
|
||||||
ds = obj.__doc__
|
ds = obj.__doc__
|
||||||
if ds:
|
if ds:
|
||||||
|
|
@ -29,13 +29,16 @@ def get_docstring(obj):
|
||||||
else:
|
else:
|
||||||
return ""
|
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()
|
||||||
|
|
||||||
|
|
||||||
def camel_to_spine(name):
|
def camel_to_spine(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()
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def set_properties(obj, **kwargs):
|
def set_properties(obj, **kwargs):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue