Added basic thing description to root
This commit is contained in:
parent
d52453849c
commit
f2af359b8b
24 changed files with 677 additions and 183 deletions
|
|
@ -150,4 +150,4 @@ class Hyperlinks(Field):
|
|||
Field.__init__(self, **kwargs)
|
||||
|
||||
def _serialize(self, value, attr, obj):
|
||||
return _rapply(self.schema, _url_val, key=attr, obj=obj)
|
||||
return _rapply(self.schema, _url_val, key=attr, obj=obj)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from flask import current_app
|
|||
from . import EXTENSION_NAME
|
||||
|
||||
|
||||
def _current_labthing():
|
||||
def current_labthing():
|
||||
app = current_app._get_current_object()
|
||||
if not app:
|
||||
return None
|
||||
|
|
@ -17,31 +17,32 @@ def _current_labthing():
|
|||
|
||||
def registered_plugins(labthing_instance=None):
|
||||
if not labthing_instance:
|
||||
labthing_instance = _current_labthing()
|
||||
labthing_instance = current_labthing()
|
||||
return labthing_instance.plugins
|
||||
|
||||
|
||||
def registered_devices(labthing_instance=None):
|
||||
if not labthing_instance:
|
||||
labthing_instance = _current_labthing()
|
||||
labthing_instance = current_labthing()
|
||||
return labthing_instance.devices
|
||||
|
||||
|
||||
def find_device(device_name, labthing_instance=None):
|
||||
if not labthing_instance:
|
||||
labthing_instance = _current_labthing()
|
||||
labthing_instance = current_labthing()
|
||||
|
||||
if device_name in labthing_instance.devices:
|
||||
return labthing_instance.devices[device_name]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def find_plugin(plugin_name, labthing_instance=None):
|
||||
if not labthing_instance:
|
||||
labthing_instance = _current_labthing()
|
||||
labthing_instance = current_labthing()
|
||||
|
||||
logging.debug("Current labthing:")
|
||||
logging.debug(_current_labthing())
|
||||
logging.debug(current_labthing())
|
||||
|
||||
if plugin_name in labthing_instance.plugins:
|
||||
return labthing_instance.plugins[plugin_name]
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
from flask import current_app, _app_ctx_stack, request
|
||||
from flask import current_app, _app_ctx_stack, request, url_for, jsonify
|
||||
|
||||
from .plugins import BasePlugin
|
||||
from .views.plugins import PluginListResource
|
||||
|
||||
from .resource import Resource
|
||||
|
||||
from ..utilities import get_docstring
|
||||
|
||||
from . import EXTENSION_NAME
|
||||
|
||||
|
||||
class LabThing(object):
|
||||
def __init__(self, app=None, prefix="", description=""):
|
||||
def __init__(self, app=None, prefix="", title="", description=""):
|
||||
self.app = app
|
||||
|
||||
self.devices = {}
|
||||
|
|
@ -15,10 +19,14 @@ class LabThing(object):
|
|||
self.plugins = {}
|
||||
|
||||
self.resources = []
|
||||
self.properties = {}
|
||||
self.actions = {}
|
||||
|
||||
self.endpoints = set()
|
||||
|
||||
self.url_prefix = prefix
|
||||
self.description = description
|
||||
self.title = title
|
||||
|
||||
if app is not None:
|
||||
self.init_app(app)
|
||||
|
|
@ -33,10 +41,17 @@ class LabThing(object):
|
|||
|
||||
self._create_base_routes()
|
||||
|
||||
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}")
|
||||
|
||||
def _create_base_routes(self):
|
||||
# Add thing description to root
|
||||
self.app.add_url_rule(self._complete_url("/", ""), "td", self.td)
|
||||
# Add plugin overview
|
||||
self.add_resource(PluginListResource, "/plugins")
|
||||
|
||||
### Device stuff
|
||||
|
|
@ -51,8 +66,6 @@ class LabThing(object):
|
|||
else:
|
||||
raise TypeError("Plugin object must be an instance of BasePlugin")
|
||||
|
||||
# TODO: Add plugin routes
|
||||
|
||||
for plugin_view_id, plugin_view in plugin_object.views.items():
|
||||
# Add route to the plugins blueprint
|
||||
self.add_resource(
|
||||
|
|
@ -61,6 +74,12 @@ class LabThing(object):
|
|||
**plugin_view["kwargs"],
|
||||
)
|
||||
|
||||
for prop in plugin_object.properties:
|
||||
self.register_property(prop)
|
||||
|
||||
for action in plugin_object.actions:
|
||||
self.register_action(action)
|
||||
|
||||
### Resource stuff
|
||||
|
||||
def _complete_url(self, url_part, registration_prefix):
|
||||
|
|
@ -73,7 +92,23 @@ class LabThing(object):
|
|||
parts = [registration_prefix, self.url_prefix, url_part]
|
||||
return "".join([part for part in parts if part])
|
||||
|
||||
def add_resource(self, resource, *urls, **kwargs):
|
||||
def register_property(self, resource):
|
||||
if hasattr(resource, "endpoint"):
|
||||
self.properties[resource.endpoint] = resource
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"Resource {resource} has not yet been added. Cannot set as a property."
|
||||
)
|
||||
|
||||
def register_action(self, resource):
|
||||
if hasattr(resource, "endpoint"):
|
||||
self.actions[resource.endpoint] = resource
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"Resource {resource} has not yet been added. Cannot set as an action."
|
||||
)
|
||||
|
||||
def add_resource(self, resource, *urls, endpoint=None, **kwargs):
|
||||
"""Adds a resource to the api.
|
||||
:param resource: the class name of your resource
|
||||
:type resource: :class:`Type[Resource]`
|
||||
|
|
@ -81,7 +116,7 @@ class LabThing(object):
|
|||
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`
|
||||
:param endpoint: endpoint name (defaults to :meth:`Resource.__name__`
|
||||
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
|
||||
|
|
@ -97,10 +132,11 @@ class LabThing(object):
|
|||
api.add_resource(Foo, '/foo', endpoint="foo")
|
||||
api.add_resource(FooSpecial, '/special/foo', endpoint="foo")
|
||||
"""
|
||||
endpoint = endpoint or resource.__name__
|
||||
if self.app is not None:
|
||||
self._register_view(self.app, resource, *urls, **kwargs)
|
||||
self._register_view(self.app, resource, *urls, endpoint=endpoint, **kwargs)
|
||||
else:
|
||||
self.resources.append((resource, urls, kwargs))
|
||||
self.resources.append((resource, urls, endpoint, kwargs))
|
||||
|
||||
def resource(self, *urls, **kwargs):
|
||||
"""Wraps a :class:`~flask_restful.Resource` class, adding it to the
|
||||
|
|
@ -120,8 +156,8 @@ class LabThing(object):
|
|||
|
||||
return decorator
|
||||
|
||||
def _register_view(self, app, resource, *urls, **kwargs):
|
||||
endpoint = kwargs.pop("endpoint", None) or resource.__name__.lower()
|
||||
def _register_view(self, app, resource, *urls, endpoint=None, **kwargs):
|
||||
endpoint = endpoint or resource.__name__
|
||||
self.endpoints.add(endpoint)
|
||||
resource_class_args = kwargs.pop("resource_class_args", ())
|
||||
resource_class_kwargs = kwargs.pop("resource_class_kwargs", {})
|
||||
|
|
@ -147,3 +183,36 @@ class LabThing(object):
|
|||
rule = self._complete_url(url, "")
|
||||
# Add the url to the application or blueprint
|
||||
app.add_url_rule(rule, view_func=resource_func, **kwargs)
|
||||
|
||||
### Utilities
|
||||
|
||||
def url_for(self, resource, **values):
|
||||
"""Generates a URL to the given resource.
|
||||
Works like :func:`flask.url_for`."""
|
||||
endpoint = resource.endpoint
|
||||
return url_for(endpoint, **values)
|
||||
|
||||
### Description
|
||||
def td(self):
|
||||
props = {}
|
||||
for key, prop in self.properties.items():
|
||||
props[key] = {}
|
||||
props[key]["title"] = prop.__name__
|
||||
props[key]["description"] = get_docstring(prop)
|
||||
props[key]["links"] = [{"href": self.url_for(prop, _external=True)}]
|
||||
|
||||
actions = {}
|
||||
for key, prop in self.actions.items():
|
||||
actions[key] = {}
|
||||
actions[key]["title"] = prop.__name__
|
||||
actions[key]["description"] = get_docstring(prop)
|
||||
actions[key]["links"] = [{"href": self.url_for(prop, _external=True)}]
|
||||
|
||||
td = {
|
||||
"title": self.title,
|
||||
"description": self.description,
|
||||
"properties": props,
|
||||
"actions": actions,
|
||||
}
|
||||
|
||||
return jsonify(td)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ class BasePlugin:
|
|||
self._rules = {} # Key: Original rule. Val: View class
|
||||
self._gui = None
|
||||
|
||||
self.actions = []
|
||||
self.properties = []
|
||||
|
||||
self.name = name
|
||||
|
||||
self.methods = {}
|
||||
|
|
@ -35,7 +38,7 @@ class BasePlugin:
|
|||
def views(self):
|
||||
return self._views
|
||||
|
||||
def add_view(self, rule, view_class, **kwargs):
|
||||
def add_view(self, view_class, rule, **kwargs):
|
||||
# Remove all leading slashes from view route
|
||||
cleaned_rule = rule
|
||||
while cleaned_rule[0] == "/":
|
||||
|
|
@ -57,6 +60,12 @@ class BasePlugin:
|
|||
# Store the rule expansion information
|
||||
self._rules[rule] = self._views[view_id]
|
||||
|
||||
def register_action(self, view_class):
|
||||
self.actions.append(view_class)
|
||||
|
||||
def register_property(self, view_class):
|
||||
self.properties.append(view_class)
|
||||
|
||||
@property
|
||||
def gui(self):
|
||||
print(self._gui)
|
||||
|
|
@ -104,13 +113,16 @@ class BasePlugin:
|
|||
def _name_uri_safe(self):
|
||||
return snake_to_spine(self._name_python_safe)
|
||||
|
||||
def add_method(self, method_name, method):
|
||||
def add_method(self, method, method_name):
|
||||
self.methods[method_name] = method
|
||||
|
||||
if not hasattr(self, method_name):
|
||||
setattr(self, method_name, method)
|
||||
else:
|
||||
logging.warning("Unable to bind method to plugin. Method name already exists.")
|
||||
logging.warning(
|
||||
"Unable to bind method to plugin. Method name already exists."
|
||||
)
|
||||
|
||||
|
||||
def find_plugins(plugin_path, module_name="plugins"):
|
||||
print(f"Loading plugins from {plugin_path}")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
__all__ = [
|
||||
"taskify",
|
||||
"tasks",
|
||||
"dict",
|
||||
"states",
|
||||
"current_task",
|
||||
"update_task_progress",
|
||||
|
|
@ -12,6 +13,7 @@ __all__ = [
|
|||
|
||||
from .pool import (
|
||||
tasks,
|
||||
dict,
|
||||
states,
|
||||
current_task,
|
||||
update_task_progress,
|
||||
|
|
|
|||
|
|
@ -6,12 +6,21 @@ from .thread import TaskThread
|
|||
|
||||
from flask import copy_current_request_context
|
||||
|
||||
|
||||
class TaskMaster:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._tasks = []
|
||||
|
||||
@property
|
||||
def tasks(self):
|
||||
"""
|
||||
Returns:
|
||||
list: List of TaskThread objects.
|
||||
"""
|
||||
return self._tasks
|
||||
|
||||
@property
|
||||
def dict(self):
|
||||
"""
|
||||
Returns:
|
||||
dict: Dictionary of TaskThread objects. Key is TaskThread ID.
|
||||
|
|
@ -28,7 +37,9 @@ class TaskMaster:
|
|||
|
||||
def new(self, f, *args, **kwargs):
|
||||
# copy_current_request_context allows threads to access flask current_app
|
||||
task = TaskThread(target=copy_current_request_context(f), args=args, kwargs=kwargs)
|
||||
task = TaskThread(
|
||||
target=copy_current_request_context(f), args=args, kwargs=kwargs
|
||||
)
|
||||
self._tasks.append(task)
|
||||
return task
|
||||
|
||||
|
|
@ -47,13 +58,23 @@ class TaskMaster:
|
|||
|
||||
|
||||
def tasks():
|
||||
"""
|
||||
List of tasks in default taskmaster
|
||||
Returns:
|
||||
list: List of tasks in default taskmaster
|
||||
"""
|
||||
global _default_task_master
|
||||
return _default_task_master.tasks
|
||||
|
||||
|
||||
def dict():
|
||||
"""
|
||||
Dictionary of tasks in default taskmaster
|
||||
Returns:
|
||||
dict: Dictionary of tasks in default taskmaster
|
||||
"""
|
||||
global _default_task_master
|
||||
return _default_task_master.tasks
|
||||
return _default_task_master.dict
|
||||
|
||||
|
||||
def states():
|
||||
|
|
|
|||
6
openflexure_microscope/common/utilities.py
Normal file
6
openflexure_microscope/common/utilities.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
def get_docstring(obj):
|
||||
ds = obj.__doc__
|
||||
if ds:
|
||||
return ds.strip()
|
||||
else:
|
||||
return ""
|
||||
Loading…
Add table
Add a link
Reference in a new issue