Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ Documentation for LabThings-FastAPI
quickstart/quickstart.rst
tutorial/index.rst
structure.rst
examples.rst
documentation.rst
actions.rst
properties.rst
documentation.rst
thing_slots.rst
dependencies/dependencies.rst
blobs.rst
concurrency.rst
using_things.rst
see_also.rst
examples.rst
wot_core_concepts.rst

autoapi/index
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
.. _tutorial_properties:
.. _properties:

Properties
Expand Down Expand Up @@ -117,6 +116,53 @@ In the example above, ``twice_my_property`` may be set by code within ``MyThing`

Functional properties may not be observed, as they are not backed by a simple value. If you need to notify clients when the value changes, you can use a data property that is updated by the functional property. In the example above, ``my_property`` may be observed, while ``twice_my_property`` cannot be observed. It would be possible to observe changes in ``my_property`` and then query ``twice_my_property`` for its new value.

.. _property_constraints:

Property constraints
--------------------

It's often helpful to make it clear that there are limits on the values a property can take. For example, a temperature property might only be valid between -40 and 125 degrees Celsius. LabThings allows you to specify constraints on properties using the same arguments as `pydantic.Field` definitions. These constraints will be enforced when the property is written to via HTTP, and they will also appear in the :ref:`gen_td` and :ref:`gen_docs`. The module-level constant `.property.CONSTRAINT_ARGS` lists all supported constraint arguments.

We can modify the previous example to show how to add constraints to both data and functional properties:

.. code-block:: python

import labthings_fastapi as lt

class AirSensor(lt.Thing):
temperature: float = lt.property(
default=20.0,
ge=-40.0, # Greater than or equal to -40.0
le=125.0 # Less than or equal to 125.0
)
"""The current temperature in degrees Celsius."""

@lt.property
def humidity(self) -> float:
"""The current humidity percentage."""
return self._humidity

@humidity.setter
def humidity(self, value: float):
"""Set the current humidity percentage."""
self._humidity = value

# Add constraints to the functional property
humidity.constraints = {
"ge": 0.0, # Greater than or equal to 0.0
"le": 100.0 # Less than or equal to 100.0
}

sensor_name: str = lt.property(default="my_sensor", pattern="^[a-zA-Z0-9_]+$")

In the example above, the ``temperature`` property is a data property with constraints that limit its value to between -40.0 and 125.0 degrees Celsius. The ``humidity`` property is a functional property with constraints that limit its value to between 0.0 and 100.0 percent. The ``sensor_name`` property is a data property with a regex pattern constraint that only allows alphanumeric characters and underscores.

Note that the constraints for functional properties are set by assigning a dictionary to the property's ``constraints`` attribute. This dictionary should contain the same keys and values as the arguments to `pydantic.Field` definitions. The `.property` decorator does not currently accept arguments, so constraints may only be set this way for functional properties and settings.

.. note::

Property values are not validated when they are set directly, only via HTTP. This behaviour may change in the future.

HTTP interface
--------------

Expand Down
3 changes: 0 additions & 3 deletions docs/source/tutorial/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@ LabThings-FastAPI tutorial
installing_labthings.rst
running_labthings.rst
writing_a_thing.rst
properties.rst

..
In due course, these pages should exist...
writing_a_thing.rst
client_code.rst
blobs.rst
thing_dependencies.rst

In this tutorial, we'll cover how to start up and interact with a LabThings-FastAPI server.

Expand Down
34 changes: 32 additions & 2 deletions docs/source/tutorial/writing_a_thing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Our first Thing will pretend to be a light: we can set its brightness and turn i
class Light(lt.Thing):
"""A computer-controlled light, our first example Thing."""

brightness: int = lt.property(default=100)
brightness: int = lt.property(default=100, ge=0, le=100)
"""The brightness of the light, in % of maximum."""

is_on: bool = lt.property(default=False, readonly=true)
Expand All @@ -39,4 +39,34 @@ Our first Thing will pretend to be a light: we can set its brightness and turn i

If you visit `http://localhost:5000/light`, you will see the Thing Description. You can also interact with it using the OpenAPI documentation at `http://localhost:5000/docs`. If you visit `http://localhost:5000/light/brightness`, you can set the brightness of the light, and if you visit `http://localhost:5000/light/is_on`, you can see whether the light is on. Changing values on the server requires a ``PUT`` or ``POST`` request, which is easiest to do using the OpenAPI "Try it out" feature. Check that you can use a ``POST`` request to the ``toggle`` endpoint to turn the light on and off.

There are two types of :ref:`wot_affordances` in this example: properties and actions. Properties are used to read and write values, while actions are used to perform operations that change the state of the Thing. In this case, we have a property for the brightness of the light and a property to indicate whether the light is on or off. The action ``toggle`` changes the state of the light by toggling the ``is_on`` property between ``True`` and ``False``.
This example has both properties and actions. Properties are used to read and write values, while actions are used to perform operations that change the state of the Thing.

.. _tutorial_properties:

Properties
----------

We have a property for the brightness of the light and a property to indicate whether the light is on or off. These are both "data properties" because they function just like variables.

``is_on`` is a boolean value, so it may be either `True` or `False`. ``brightness`` is an integer, and the `ge` and `le` arguments constrain it to take a value between 0 and 100. See :ref:`property_constraints` for more details.

It's also possible to have properties defined using a function, for example we could add in:

.. code-block:: python

@lt.property
def status(self) -> str:
"""A human-readable status of the light."""
if self.is_on:
return f"The light is on at {self.brightness}% brightness."
else:
return "The light is off."

This is a "functional property" because its value is determined by a function. Functional properties may be read-only (as in this example) or read-write (see :ref:`properties`).

.. _tutorial_actions:

Actions
-----------

The action ``toggle`` changes the state of the light by toggling the ``is_on`` property between ``True`` and ``False``. For more detail on how actions work, see :ref:`actions`.
11 changes: 11 additions & 0 deletions src/labthings_fastapi/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,14 @@ class NoBlobManagerError(RuntimeError):
Any access to an invocation output must have BlobIOContextDep as a dependency, as
the output may be a blob, and the blob needs this context to resolve its URL.
"""


class UnsupportedConstraintError(ValueError):
"""A constraint argument is not supported.

This exception is raised when a constraint argument is passed to
a property that is not in the supported list. See
`labthings_fastapi.properties.CONSTRAINT_ARGS` for the list of
supported arguments. Their meaning is described in the `pydantic.Field`
documentation.
"""
Loading
Loading