As well as properties, the OpenFlexure Microscope Server also supports Thing Actions.
Thing Actions "invoke a function of the Thing, which manipulates state (e.g., toggling a lamp on or off) or triggers a process on the Thing (e.g., dim a lamp over time)." For the microscope, this would include moving the stage or taking a capture. Both of these require internal logic, and cannot be performed by changing a simple property.
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:
.. code-block:: python
@ThingAction
class QuickCaptureAPI(View):
"""
Take an image capture and return it without saving
"""
# Expect a "use_video_port" boolean, which defaults to True if none is given
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
----------------
Adding this new view into our example extension, we now have:
As well as generating Swagger documentation, the server will generate a draft `W3C Thing Description <https://www.w3.org/TR/wot-thing-description/>`_ . This description allows the microscope's features to be understood in a common "Web of Things" language.
Thing Properties "expose state of the Thing. This state can then be retrieved (read) and optionally updated (write)." For the microscope, this includes the current read-only state, such as if the microscope has real camera or stage hardware attached, as well as read-write states like camera settings, and the microscope name.
The property description for a view will be generated automatically from your available view methods, any schema decorators used, and any docstrings added to the view.
Defining Thing Properties
-------------------------
In order to register a view as a Thing property, we use the ``@ThingProperty`` decorator, 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):
# Format our returned object using MicroscopeIdentifySchema
@marshal_with(MicroscopeIdentifySchema())
def get(self):
"""
Show identifying information about the current microscope object
This decorator serves only to add documentation: The view will be tagged with ``property`` in Swagger documentation, and will appear as a property in the microscopes Thing Description.
However, the additional ``@PropertySchema`` decorator allows for simplified argument parsing and object marshaling.
Property schema
---------------
For read-write properties, it is best practice for the expected request arguments, and the views responses, to follow the same format. In this way, by looking at the response of a GET request, one can know the type of data expected in by a PUT request.
For example, if your GET request returns the JSON:
.. code-block:: json
{
"name": "John Doe",
"age": 45,
"job": "Python developer"
}
and your property supports PUT requests (for updating data), then a valid PUT request could contain the data:
.. code-block:: json
{
"age": 46,
"job": "Landscape gardener"
}
This request would update the property, such that a GET request would *now* return:
.. code-block:: json
{
"name": "John Doe",
"age": 46,
"job": "Landscape gardener"
}
The ``@PropertySchema`` decorator can be applied to your view *class*, and essentially acts as shorthand for applying ``@marshal_with`` to all methods, and ``@use_args`` to all POST and PUT methods (or ``@use_body`` if only a single field is given).
We will implement the ``@PropertySchema`` decorator in our ``ExampleRenameView`` view from our previous example:
.. 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")})