Commit 6520db75 authored by jtc42's avatar jtc42
Browse files

Wrote W3C sections

parent 1115d00a
Loading
Loading
Loading
Loading
+54 −0
Original line number Diff line number Diff line
Thing Actions
=============

Introduction
------------

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
        @use_args({"use_video_port": fields.Boolean(missing=True)})
        # Our success response (200) returns an image (image/jpeg mimetype)
        @doc_response(200, mimetype="image/jpeg")
        def post(self, args):
            """
            Take a non-persistant image capture.
            """
            # Find our microscope component
            microscope = find_component("org.openflexure.microscope")

            # Open a BytesIO stream to be destroyed once request has returned
            with io.BytesIO() as stream:

                # Capture to our stream object
                microscope.camera.capture(stream, use_video_port=args.get("use_video_port"))

                # Rewind the stream
                stream.seek(0)

                # Return our image data using Flasks send_file function
                return send_file(io.BytesIO(stream.read()), mimetype="image/jpeg")

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:

.. literalinclude:: ./example_extension/05_actions.py
+117 −0
Original line number Diff line number Diff line
Thing Properties
================

Introduction
------------

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
            """
            # Find our microscope component
            microscope = find_component("org.openflexure.microscope")

            # Return our microscope object,
            # let @marshal_with handle formatting the output
            return microscope

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")})
    class ExampleRenameView(View):
        def get(self):
            """
            Show the current microscope name
            """
            # Find our microscope component
            microscope = find_component("org.openflexure.microscope")

            return microscope

        def post(self, args):
            """
            Change the current microscope name
            """
            # Look for our "name" parameter in the request arguments
            new_name = args.get("name")

            # Find our microscope component
            microscope = find_component("org.openflexure.microscope")

            # Pass microscope and new name to our rename function
            rename(microscope, new_name)

            # Return our microscope object,
            # let @marshal_with handle formatting the output
            return microscope

Complete example
----------------

Combining these into our example extension, we now have:

.. literalinclude:: ./example_extension/04_properties.py