Switched to ActionView and PropertyView from decorators

This commit is contained in:
Joel Collins 2020-05-26 10:54:21 +01:00
parent 677b08d509
commit 1819ca6a9c
18 changed files with 57 additions and 98 deletions

View file

@ -9,12 +9,11 @@ Thing Actions "invoke a function of the Thing, which manipulates state (e.g., to
Actions should be *triggered* with POST requests *only*. Ideally, a view corresponding to an action should only support POST requests.
Like properties, we use a class decorator to identify a view as an action: ``@ThingAction``. For example, a view to perform a "quick-capture" action may look like:
Like properties, we use a special view class to identify a view as an action: ``ActionView``. For example, a view to perform a "quick-capture" action may look like:
.. code-block:: python
@ThingAction
class QuickCaptureAPI(View):
class QuickCaptureAPI(ActionView):
"""
Take an image capture and return it without saving
"""
@ -44,8 +43,6 @@ Like properties, we use a class decorator to identify a view as an action: ``@Th
In this example, we are also making use of the ``@doc_response`` decorator, to document that our successful response (HTTP code 200) will return data with a mimetype ``image/jpeg``, as well as ``@use_args`` to accept optional parameters with POST requests.
Again like properties, the ``@ThingAction`` decorator serves only to add documentation.
Complete example
----------------

View file

@ -1,6 +1,6 @@
from labthings.server.extensions import BaseExtension
from labthings.server.find import find_component
from labthings.server.view import View
from labthings.server.view import View, PropertyView
from labthings.server.decorators import (
use_args,
@ -36,8 +36,7 @@ def rename(microscope, new_name):
## Extension views
# Since we only have a GET method here, it'll register as a read-only property
@ThingProperty
class ExampleIdentifyView(View):
class ExampleIdentifyView(PropertyView):
# Format our returned object using MicroscopeIdentifySchema
@marshal_with(MicroscopeIdentifySchema())
def get(self):
@ -52,11 +51,10 @@ class ExampleIdentifyView(View):
return microscope
@ThingProperty
# We can use a single schema for all methods if the input and output will be formatted identically
# Eg. Here, we will always expect a "name" string argument, and always return a "name" string attribute
@PropertySchema({"name": fields.String(required=True, example="My Example Microscope")})
class ExampleRenameView(View):
class ExampleRenameView(PropertyView):
def get(self):
"""
Show the current microscope name

View file

@ -1,6 +1,6 @@
from labthings.server.extensions import BaseExtension
from labthings.server.find import find_component
from labthings.server.view import View
from labthings.server.view import View, ActionView, PropertyView
from labthings.server.decorators import (
use_args,
@ -41,8 +41,7 @@ def rename(microscope, new_name):
## Extension views
# Since we only have a GET method here, it'll register as a read-only property
@ThingProperty
class ExampleIdentifyView(View):
class ExampleIdentifyView(PropertyView):
# Format our returned object using MicroscopeIdentifySchema
@marshal_with(MicroscopeIdentifySchema())
def get(self):
@ -57,11 +56,10 @@ class ExampleIdentifyView(View):
return microscope
@ThingProperty
# We can use a single schema for all methods if the input and output will be formatted identically
# Eg. Here, we will always expect a "name" string argument, and always return a "name" string attribute
@PropertySchema({"name": fields.String(required=True, example="My Example Microscope")})
class ExampleRenameView(View):
class ExampleRenameView(PropertyView):
def get(self):
"""
Show the current microscope name
@ -89,8 +87,7 @@ class ExampleRenameView(View):
return microscope
@ThingAction
class QuickCaptureAPI(View):
class QuickCaptureAPI(ActionView):
"""
Take an image capture and return it without saving
"""

View file

@ -1,6 +1,6 @@
from labthings.server.extensions import BaseExtension
from labthings.server.find import find_component
from labthings.server.view import View
from labthings.server.view import View, ActionView
from labthings.server.decorators import (
use_args,
@ -66,8 +66,7 @@ def timelapse(microscope, n_images, t_between):
## Extension views
@ThingAction
class TimelapseAPI(View):
class TimelapseAPI(ActionView):
"""
Take a series of images in a timelapse
"""

View file

@ -1,6 +1,6 @@
from labthings.server.extensions import BaseExtension
from labthings.server.find import find_component
from labthings.server.view import View
from labthings.server.view import View, ActionView
from labthings.server.decorators import (
use_args,
@ -68,8 +68,7 @@ def timelapse(microscope, n_images, t_between):
## Extension views
@ThingAction
class TimelapseAPI(View):
class TimelapseAPI(ActionView):
"""
Take a series of images in a timelapse, running as a background task
"""

View file

@ -14,13 +14,12 @@ The property description for a view will be generated automatically from your av
Defining Thing Properties
-------------------------
In order to register a view as a Thing property, we use the ``@ThingProperty`` decorator, like so:
In order to register a view as a Thing property, we use the ``PropertyView`` class, like so:
.. code-block:: python
# Since we only have a GET method here, it'll register as a read-only property
@ThingProperty
class ExampleIdentifyView(View):
class ExampleIdentifyView(PropertyView):
# Format our returned object using MicroscopeIdentifySchema
@marshal_with(MicroscopeIdentifySchema())
def get(self):
@ -78,11 +77,10 @@ We will implement the ``@PropertySchema`` decorator in our ``ExampleRenameView``
.. code-block:: python
@ThingProperty
# We can use a single schema for all methods if the input and output will be formatted identically
# Eg. Here, we will always expect a "name" string argument, and always return a "name" string attribute
@PropertySchema({"name": fields.String(required=True, example="My Example Microscope")})
class ExampleRenameView(View):
class ExampleRenameView(PropertyView):
def get(self):
"""
Show the current microscope name

View file

@ -15,7 +15,7 @@ We get around these issues by making use of background tasks, and component lock
Background tasks
----------------
Tasks are introduced to manage long-running functions in a way that does not block HTTP requests. Any API Action will automatically run as a background task (that is, any View that subclasses `ActionView`, or uses the `@ThingAction` decorator).
Tasks are introduced to manage long-running functions in a way that does not block HTTP requests. Any API Action will automatically run as a background task (that is, any View that subclasses `ActionView`).
Internally, the ``tasks`` submodule stores a list of all requested tasks, and their states. This state stores the running status of the task (if itis idle, running, error, or success), information about the start and end times, a unique task ID, and, upon completion, the return value of the long-running function.
@ -30,11 +30,9 @@ An example of a long running task may look like:
.. code-block:: python
...
from labthings.tasks import taskify
from labthings.server.decorators import ThingAction
from labthings.server.view import ActionView
@ThingAction
class SlowAPI(View):
class SlowAPI(ActionView):
def post(self):
# Return the task object.
return long_running_function(function_argument_1, function_argument_2)

View file

@ -1,6 +1,6 @@
from labthings.server.find import find_component
from labthings.server.extensions import BaseExtension
from labthings.server.view import View
from labthings.server.view import View, ActionView, PropertyView
from labthings.server.decorators import ThingAction, ThingProperty
from openflexure_microscope.devel import JsonResponse, request, abort
@ -332,8 +332,7 @@ class MeasureSharpnessAPI(View):
return {"sharpness": measure_sharpness(microscope)}
@ThingAction
class AutofocusAPI(View):
class AutofocusAPI(ActionView):
"""
Run a standard autofocus
"""
@ -357,8 +356,7 @@ class AutofocusAPI(View):
abort(503, "No stage connected. Unable to autofocus.")
@ThingAction
class FastAutofocusAPI(View):
class FastAutofocusAPI(ActionView):
"""
Run a fast autofocus
"""

View file

@ -1,5 +1,5 @@
from labthings.server.extensions import BaseExtension
from labthings.server.view import View
from labthings.server.view import View, PropertyView
from labthings.server.decorators import ThingProperty, PropertySchema, use_args
from labthings.server import fields
from labthings.server.find import find_component
@ -190,8 +190,7 @@ class AutostorageExtension(BaseExtension):
autostorage_extension_v2 = AutostorageExtension()
@ThingProperty
class GetLocationsView(View):
class GetLocationsView(PropertyView):
def get(self):
global autostorage_extension_v2
@ -199,9 +198,8 @@ class GetLocationsView(View):
return autostorage_extension_v2.get_locations()
@ThingProperty
@PropertySchema(fields.String(required=True, example="Default"))
class PreferredLocationView(View):
class PreferredLocationView(PropertyView):
def get(self):
global autostorage_extension_v2

View file

@ -3,7 +3,7 @@ API extension for stage calibration
This file contains the HTTP API for camera/stage calibration.
"""
from labthings.server.view import View
from labthings.server.view import View, ActionView, PropertyView
from labthings.server.find import find_component
from labthings.server.extensions import BaseExtension
from labthings.server.decorators import (
@ -139,8 +139,7 @@ class CSMExtension(BaseExtension):
csm_extension = CSMExtension()
@ThingAction
class Calibrate1DView(View):
class Calibrate1DView(ActionView):
@use_args(
{"direction": fields.List(fields.Float(), required=True, example=[1, 0, 0])}
)
@ -155,8 +154,7 @@ class Calibrate1DView(View):
csm_extension.add_view(Calibrate1DView, "/calibrate_1d", endpoint="calibrate_1d")
@ThingAction
class CalibrateXYView(View):
class CalibrateXYView(ActionView):
def post(self):
"""Calibrate both axes of the microscope stage against the camera."""
return csm_extension.calibrate_xy()
@ -165,8 +163,7 @@ class CalibrateXYView(View):
csm_extension.add_view(CalibrateXYView, "/calibrate_xy", endpoint="calibrate_xy")
@ThingAction
class MoveInImageCoordinatesView(View):
class MoveInImageCoordinatesView(ActionView):
@use_args(
{
"x": fields.Float(
@ -194,8 +191,7 @@ class MoveInImageCoordinatesView(View):
csm_extension.add_view(MoveInImageCoordinatesView, "/move_in_image_coordinates", endpoint="move_in_image_coordinates")
@ThingProperty
class GetCalibrationFile(View):
class GetCalibrationFile(PropertyView):
def get(self):
"""Get the calibration data in JSON format."""
datafile_name = CSM_DATAFILE_NAME

View file

@ -1,4 +1,4 @@
from labthings.server.view import View
from labthings.server.view import View, ActionView
from labthings.server.find import find_component
from labthings.server.extensions import BaseExtension
from labthings.server.decorators import ThingAction
@ -58,8 +58,7 @@ def recalibrate(microscope):
microscope.save_settings()
@ThingAction
class RecalibrateView(View):
class RecalibrateView(ActionView):
def post(self):
microscope = find_component("org.openflexure.microscope")
@ -71,8 +70,7 @@ class RecalibrateView(View):
return recalibrate(microscope)
@ThingAction
class FlattenLSTView(View):
class FlattenLSTView(ActionView):
def post(self):
microscope = find_component("org.openflexure.microscope")
@ -95,8 +93,7 @@ class FlattenLSTView(View):
)
@ThingAction
class DeleteLSTView(View):
class DeleteLSTView(ActionView):
def post(self):
microscope = find_component("org.openflexure.microscope")

View file

@ -13,7 +13,7 @@ import tempfile
import logging
from labthings.server.find import find_component
from labthings.server.view import View
from labthings.server.view import View, ActionView, PropertyView
from labthings.server.schema import Schema
from labthings.server import fields
from labthings.server.extensions import BaseExtension
@ -138,8 +138,7 @@ class ZipManager:
default_zip_manager = ZipManager()
@ThingAction
class ZipBuilderAPIView(View):
class ZipBuilderAPIView(ActionView):
def post(self):
ids = list(JsonResponse(request).json)
@ -151,8 +150,7 @@ class ZipBuilderAPIView(View):
)
@ThingProperty
class ZipListAPIView(View):
class ZipListAPIView(PropertyView):
@marshal_with(ZipObjectSchema(many=True))
def get(self):
return default_zip_manager.session_zips.values()

View file

@ -1,5 +1,5 @@
from openflexure_microscope.api.utilities import get_bool, JsonResponse
from labthings.server.view import View
from labthings.server.view import View, ActionView
from labthings.server.find import find_component
from labthings.server.decorators import (
use_args,
@ -20,8 +20,7 @@ import io
from flask import request, abort, url_for, redirect, send_file
@ThingAction
class CaptureAPI(View):
class CaptureAPI(ActionView):
"""
Create a new image capture.
"""
@ -75,8 +74,7 @@ class CaptureAPI(View):
@ThingAction
class RAMCaptureAPI(View):
class RAMCaptureAPI(ActionView):
"""
Take a non-persistant image capture.
"""
@ -124,8 +122,7 @@ class RAMCaptureAPI(View):
return send_file(io.BytesIO(stream.getbuffer()), mimetype="image/jpeg")
@ThingAction
class GPUPreviewStartAPI(View):
class GPUPreviewStartAPI(ActionView):
"""
Start the onboard GPU preview.
Optional "window" parameter can be passed to control the position and size of the preview window,
@ -157,8 +154,7 @@ class GPUPreviewStartAPI(View):
return microscope.state
@ThingAction
class GPUPreviewStopAPI(View):
class GPUPreviewStopAPI(ActionView):
def post(self):
"""
Stop the onboard GPU preview.

View file

@ -1,5 +1,5 @@
from openflexure_microscope.api.utilities import JsonResponse
from labthings.server.view import View
from labthings.server.view import View, ActionView
from labthings.server.find import find_component
from labthings.server.decorators import use_args, marshal_with, doc, ThingAction
from labthings.server import fields
@ -11,8 +11,7 @@ from flask import Blueprint, request
import logging
@ThingAction
class MoveStageAPI(View):
class MoveStageAPI(ActionView):
@use_args(
{
"absolute": fields.Boolean(
@ -56,8 +55,7 @@ class MoveStageAPI(View):
return microscope.state["stage"]["position"]
@ThingAction
class ZeroStageAPI(View):
class ZeroStageAPI(ActionView):
def post(self):
"""
Zero the stage coordinates.

View file

@ -1,4 +1,4 @@
from labthings.server.view import View
from labthings.server.view import View, ActionView
import subprocess
import os
from sys import platform
@ -14,8 +14,7 @@ def is_raspberrypi(raise_on_errors=False):
return os.path.exists("/usr/bin/raspi-config")
@ThingAction
class ShutdownAPI(View):
class ShutdownAPI(ActionView):
"""
Attempt to shutdown the device
"""
@ -35,8 +34,7 @@ class ShutdownAPI(View):
return {"out": out, "err": err}, 201
@ThingAction
class RebootAPI(View):
class RebootAPI(ActionView):
"""
Attempt to reboot the device
"""

View file

@ -5,7 +5,7 @@ from openflexure_microscope.api.utilities import get_bool, JsonResponse
from labthings.server.schema import Schema
from labthings.server import fields
from labthings.server.view import View
from labthings.server.view import View, PropertyView
from labthings.server.utilities import description_from_view
from labthings.server.decorators import marshal_with, doc_response, Tag, ThingProperty
@ -91,9 +91,8 @@ capture_list_schema = CaptureSchema(many=True)
from pprint import pprint
@ThingProperty
@Tag("captures")
class CaptureList(View):
class CaptureList(PropertyView):
@marshal_with(CaptureSchema(many=True))
def get(self):
"""

View file

@ -3,7 +3,7 @@ from openflexure_microscope.api.utilities import JsonResponse
from labthings.core.utilities import get_by_path, set_by_path, create_from_path
from labthings.server.find import find_component
from labthings.server.view import View
from labthings.server.view import View, PropertyView
from labthings.server.decorators import ThingProperty, Tag, doc_response
@ -11,8 +11,7 @@ from flask import request, abort
import logging
@ThingProperty
class SettingsProperty(View):
class SettingsProperty(PropertyView):
def get(self):
"""
Current microscope settings, including camera and stage
@ -71,8 +70,7 @@ class NestedSettingsProperty(View):
return self.get(route)
@ThingProperty
class StateProperty(View):
class StateProperty(PropertyView):
def get(self):
"""
Show current read-only state of the microscope
@ -99,8 +97,7 @@ class NestedStateProperty(View):
return value
@ThingProperty
class ConfigurationProperty(View):
class ConfigurationProperty(PropertyView):
def get(self):
"""
Show current read-only state of the microscope

View file

@ -3,14 +3,13 @@ from openflexure_microscope.api.utilities import gen, JsonResponse
from labthings.core.utilities import get_by_path, set_by_path, create_from_path
from labthings.server.find import find_component
from labthings.server.view import View
from labthings.server.view import View, PropertyView
from labthings.server.decorators import doc_response, ThingProperty
from flask import Response
@ThingProperty
class MjpegStream(View):
class MjpegStream(PropertyView):
"""
Real-time MJPEG stream from the microscope camera
"""
@ -38,8 +37,7 @@ class MjpegStream(View):
)
@ThingProperty
class SnapshotStream(View):
class SnapshotStream(PropertyView):
"""
Single JPEG snapshot from the camera stream
"""