Added base plugin resource
This commit is contained in:
parent
469e8218e0
commit
2b5b0b8744
8 changed files with 299 additions and 60 deletions
|
|
@ -20,7 +20,10 @@ from openflexure_microscope.config import settings_file_path, JSONEncoder
|
|||
from openflexure_microscope.api.v1 import blueprints
|
||||
from openflexure_microscope.api import v2
|
||||
|
||||
from openflexure_microscope.common.labthings import LabThing
|
||||
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.config import USER_PLUGINS_PATH
|
||||
|
||||
from openflexure_microscope.api.microscope import default_microscope as api_microscope
|
||||
|
||||
|
|
@ -78,30 +81,16 @@ handler = JSONExceptionHandler(app)
|
|||
|
||||
# Attach lab devices
|
||||
labthing = LabThing(app)
|
||||
labthing.url_prefix = "/api/v2b"
|
||||
labthing.description = "Test LabThing-based API for OpenFlexure Microscope"
|
||||
|
||||
labthing.register_device(api_microscope, "openflexure_microscope")
|
||||
|
||||
for _plugin in find_plugins(USER_PLUGINS_PATH):
|
||||
labthing.register_plugin(_plugin)
|
||||
|
||||
# WEBAPP ROUTES
|
||||
|
||||
# Base routes
|
||||
base_blueprint = blueprints.base.construct_blueprint(api_microscope)
|
||||
app.register_blueprint(base_blueprint, url_prefix=uri("", "v1"))
|
||||
|
||||
# Stage routes
|
||||
stage_blueprint = blueprints.stage.construct_blueprint(api_microscope)
|
||||
app.register_blueprint(stage_blueprint, url_prefix=uri("/stage", "v1"))
|
||||
|
||||
# Camera routes
|
||||
camera_blueprint = blueprints.camera.construct_blueprint(api_microscope)
|
||||
app.register_blueprint(camera_blueprint, url_prefix=uri("/camera", "v1"))
|
||||
|
||||
# Plugin routes
|
||||
plugin_blueprint = blueprints.plugins.construct_blueprint(api_microscope)
|
||||
app.register_blueprint(plugin_blueprint, url_prefix=uri("/plugin", "v1"))
|
||||
|
||||
# Task routes
|
||||
task_blueprint = blueprints.task.construct_blueprint(api_microscope)
|
||||
app.register_blueprint(task_blueprint, url_prefix=uri("/task", "v1"))
|
||||
|
||||
### V2
|
||||
# Root routes
|
||||
v2_root_blueprint = v2.blueprints.root.construct_blueprint(api_microscope)
|
||||
|
|
@ -123,8 +112,8 @@ v2_status_blueprint = v2.blueprints.status.construct_blueprint(api_microscope)
|
|||
app.register_blueprint(v2_status_blueprint, url_prefix=uri("/status", "v2"))
|
||||
|
||||
# Plugins routes
|
||||
v2_plugin_blueprint = v2.blueprints.plugins.construct_blueprint(api_microscope)
|
||||
app.register_blueprint(v2_plugin_blueprint, url_prefix=uri("/plugins", "v2"))
|
||||
# v2_plugin_blueprint = v2.blueprints.plugins.construct_blueprint(api_microscope)
|
||||
# app.register_blueprint(v2_plugin_blueprint, url_prefix=uri("/plugins", "v2"))
|
||||
|
||||
# Tasks routes
|
||||
v2_tasks_blueprint = v2.blueprints.tasks.construct_blueprint(api_microscope)
|
||||
|
|
|
|||
|
|
@ -1,37 +1 @@
|
|||
from flask import current_app, _app_ctx_stack
|
||||
|
||||
EXTENSION_NAME = "flask-lab"
|
||||
|
||||
|
||||
class LabThing(object):
|
||||
def __init__(self, app=None):
|
||||
self.app = app
|
||||
self.devices = {}
|
||||
if app is not None:
|
||||
self.init_app(app)
|
||||
|
||||
def init_app(self, app):
|
||||
app.teardown_appcontext(self.teardown)
|
||||
|
||||
app.extensions = getattr(app, "extensions", {})
|
||||
app.extensions[EXTENSION_NAME] = self
|
||||
|
||||
def teardown(self, exception):
|
||||
print(f"Tearing down devices: {self.devices}")
|
||||
|
||||
def register_device(self, device_object, device_name: str):
|
||||
self.devices[device_name] = device_object
|
||||
|
||||
|
||||
def find_device(device_name):
|
||||
app = current_app
|
||||
|
||||
if not app:
|
||||
return None
|
||||
|
||||
pylot_instance = app.extensions[EXTENSION_NAME]
|
||||
|
||||
if device_name in pylot_instance.devices:
|
||||
return pylot_instance.devices[device_name]
|
||||
else:
|
||||
return None
|
||||
|
|
|
|||
32
openflexure_microscope/common/labthings/find.py
Normal file
32
openflexure_microscope/common/labthings/find.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
from flask import current_app
|
||||
|
||||
from . import EXTENSION_NAME
|
||||
|
||||
|
||||
def _current_labthing():
|
||||
app = current_app
|
||||
if not app:
|
||||
return None
|
||||
return app.extensions[EXTENSION_NAME]
|
||||
|
||||
|
||||
def registered_plugins(labthing_instance=None):
|
||||
if not labthing_instance:
|
||||
labthing_instance = _current_labthing()
|
||||
return labthing_instance.plugins
|
||||
|
||||
|
||||
def registered_devices(labthing_instance=None):
|
||||
if not labthing_instance:
|
||||
labthing_instance = _current_labthing()
|
||||
return labthing_instance.devices
|
||||
|
||||
|
||||
def find_device(device_name, labthing_instance=None):
|
||||
if not labthing_instance:
|
||||
labthing_instance = _current_labthing()
|
||||
|
||||
if device_name in labthing_instance.devices:
|
||||
return labthing_instance.devices[device_name]
|
||||
else:
|
||||
return None
|
||||
139
openflexure_microscope/common/labthings/labthing.py
Normal file
139
openflexure_microscope/common/labthings/labthing.py
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
from flask import current_app, _app_ctx_stack, request
|
||||
|
||||
from .plugins import BasePlugin
|
||||
from .views.plugins import PluginListResource
|
||||
|
||||
from . import EXTENSION_NAME
|
||||
|
||||
|
||||
class LabThing(object):
|
||||
def __init__(self, app=None, url_prefix="", description=""):
|
||||
self.app = app
|
||||
|
||||
self.devices = {}
|
||||
|
||||
self.plugins = {}
|
||||
|
||||
self.resources = []
|
||||
self.endpoints = set()
|
||||
|
||||
self.url_prefix = url_prefix
|
||||
self.description = description
|
||||
|
||||
if app is not None:
|
||||
self.init_app(app)
|
||||
|
||||
### Flask stuff
|
||||
|
||||
def init_app(self, app):
|
||||
app.teardown_appcontext(self.teardown)
|
||||
|
||||
app.extensions = getattr(app, "extensions", {})
|
||||
app.extensions[EXTENSION_NAME] = self
|
||||
|
||||
self._create_base_routes()
|
||||
|
||||
def teardown(self, exception):
|
||||
print(f"Tearing down devices: {self.devices}")
|
||||
|
||||
def _create_base_routes(self):
|
||||
self.add_resource(PluginListResource, "/plugins")
|
||||
|
||||
### Device stuff
|
||||
|
||||
def register_device(self, device_object, device_name: str):
|
||||
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
|
||||
else:
|
||||
raise TypeError("Plugin object must be an instance of BasePlugin")
|
||||
|
||||
### Resource stuff
|
||||
|
||||
def _complete_url(self, url_part, registration_prefix):
|
||||
"""This method is used to defer the construction of the final url in
|
||||
the case that the Api is created with a Blueprint.
|
||||
:param url_part: The part of the url the endpoint is registered with
|
||||
:param registration_prefix: The part of the url contributed by the
|
||||
blueprint. Generally speaking, BlueprintSetupState.url_prefix
|
||||
"""
|
||||
parts = [registration_prefix, self.url_prefix, url_part]
|
||||
return "".join([part for part in parts if part])
|
||||
|
||||
def add_resource(self, resource, *urls, **kwargs):
|
||||
"""Adds a resource to the api.
|
||||
:param resource: the class name of your resource
|
||||
:type resource: :class:`Type[Resource]`
|
||||
:param urls: one or more url routes to match for the resource, standard
|
||||
flask routing rules apply. Any url variables will be
|
||||
passed to the resource method as args.
|
||||
:type urls: str
|
||||
:param endpoint: endpoint name (defaults to :meth:`Resource.__name__.lower`
|
||||
Can be used to reference this route in :class:`fields.Url` fields
|
||||
:type endpoint: str
|
||||
:param resource_class_args: args to be forwarded to the constructor of
|
||||
the resource.
|
||||
:type resource_class_args: tuple
|
||||
:param resource_class_kwargs: kwargs to be forwarded to the constructor
|
||||
of the resource.
|
||||
:type resource_class_kwargs: dict
|
||||
Additional keyword arguments not specified above will be passed as-is
|
||||
to :meth:`flask.Flask.add_url_rule`.
|
||||
Examples::
|
||||
api.add_resource(HelloWorld, '/', '/hello')
|
||||
api.add_resource(Foo, '/foo', endpoint="foo")
|
||||
api.add_resource(FooSpecial, '/special/foo', endpoint="foo")
|
||||
"""
|
||||
if self.app is not None:
|
||||
self._register_view(self.app, resource, *urls, **kwargs)
|
||||
else:
|
||||
self.resources.append((resource, urls, kwargs))
|
||||
|
||||
def resource(self, *urls, **kwargs):
|
||||
"""Wraps a :class:`~flask_restful.Resource` class, adding it to the
|
||||
api. Parameters are the same as :meth:`~flask_restful.Api.add_resource`.
|
||||
Example::
|
||||
app = Flask(__name__)
|
||||
api = restful.Api(app)
|
||||
@api.resource('/foo')
|
||||
class Foo(Resource):
|
||||
def get(self):
|
||||
return 'Hello, World!'
|
||||
"""
|
||||
|
||||
def decorator(cls):
|
||||
self.add_resource(cls, *urls, **kwargs)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
def _register_view(self, app, resource, *urls, **kwargs):
|
||||
endpoint = kwargs.pop("endpoint", None) or resource.__name__.lower()
|
||||
self.endpoints.add(endpoint)
|
||||
resource_class_args = kwargs.pop("resource_class_args", ())
|
||||
resource_class_kwargs = kwargs.pop("resource_class_kwargs", {})
|
||||
|
||||
# NOTE: 'view_functions' is cleaned up from Blueprint class in Flask 1.0
|
||||
if endpoint in getattr(app, "view_functions", {}):
|
||||
previous_view_class = app.view_functions[endpoint].__dict__["view_class"]
|
||||
|
||||
# if you override the endpoint with a different class, avoid the collision by raising an exception
|
||||
if previous_view_class != resource:
|
||||
raise ValueError(
|
||||
"This endpoint (%s) is already set to the class %s."
|
||||
% (endpoint, previous_view_class.__name__)
|
||||
)
|
||||
|
||||
resource.endpoint = endpoint
|
||||
resource_func = resource.as_view(
|
||||
endpoint, *resource_class_args, **resource_class_kwargs
|
||||
)
|
||||
|
||||
for url in urls:
|
||||
# If we've got no Blueprint, just build a url with no prefix
|
||||
rule = self._complete_url(url, "")
|
||||
# Add the url to the application or blueprint
|
||||
app.add_url_rule(rule, view_func=resource_func, **kwargs)
|
||||
9
openflexure_microscope/common/labthings/resource.py
Normal file
9
openflexure_microscope/common/labthings/resource.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from flask.views import MethodView
|
||||
|
||||
|
||||
class Resource(MethodView):
|
||||
"""Currently identical to MethodView
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
MethodView.__init__(self, *args, **kwargs)
|
||||
106
openflexure_microscope/common/labthings/views/plugins.py
Normal file
106
openflexure_microscope/common/labthings/views/plugins.py
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
"""
|
||||
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
|
||||
"""
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
from openflexure_microscope.common.labthings import find_device
|
||||
from openflexure_microscope.common.labthings.find import find_device
|
||||
from openflexure_microscope.common.labthings.plugins import BasePlugin
|
||||
from openflexure_microscope.microscope import Microscope
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue