Skip to content

Commit 8bdb9ad

Browse files
authored
Merge pull request #208 from labthings/constraints-on-dataproperty
Accept constraint arguments in property.
2 parents b1ad015 + 94de953 commit 8bdb9ad

File tree

9 files changed

+450
-29
lines changed

9 files changed

+450
-29
lines changed

docs/source/index.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ Documentation for LabThings-FastAPI
88
quickstart/quickstart.rst
99
tutorial/index.rst
1010
structure.rst
11-
examples.rst
12-
documentation.rst
1311
actions.rst
12+
properties.rst
13+
documentation.rst
1414
thing_slots.rst
1515
dependencies/dependencies.rst
1616
blobs.rst
1717
concurrency.rst
1818
using_things.rst
1919
see_also.rst
20+
examples.rst
2021
wot_core_concepts.rst
2122

2223
autoapi/index
Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
.. _tutorial_properties:
21
.. _properties:
32

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

118117
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.
119118

119+
.. _property_constraints:
120+
121+
Property constraints
122+
--------------------
123+
124+
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.
125+
126+
We can modify the previous example to show how to add constraints to both data and functional properties:
127+
128+
.. code-block:: python
129+
130+
import labthings_fastapi as lt
131+
132+
class AirSensor(lt.Thing):
133+
temperature: float = lt.property(
134+
default=20.0,
135+
ge=-40.0, # Greater than or equal to -40.0
136+
le=125.0 # Less than or equal to 125.0
137+
)
138+
"""The current temperature in degrees Celsius."""
139+
140+
@lt.property
141+
def humidity(self) -> float:
142+
"""The current humidity percentage."""
143+
return self._humidity
144+
145+
@humidity.setter
146+
def humidity(self, value: float):
147+
"""Set the current humidity percentage."""
148+
self._humidity = value
149+
150+
# Add constraints to the functional property
151+
humidity.constraints = {
152+
"ge": 0.0, # Greater than or equal to 0.0
153+
"le": 100.0 # Less than or equal to 100.0
154+
}
155+
156+
sensor_name: str = lt.property(default="my_sensor", pattern="^[a-zA-Z0-9_]+$")
157+
158+
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.
159+
160+
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.
161+
162+
.. note::
163+
164+
Property values are not validated when they are set directly, only via HTTP. This behaviour may change in the future.
165+
120166
HTTP interface
121167
--------------
122168

docs/source/tutorial/index.rst

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,11 @@ LabThings-FastAPI tutorial
88
installing_labthings.rst
99
running_labthings.rst
1010
writing_a_thing.rst
11-
properties.rst
1211

1312
..
1413
In due course, these pages should exist...
15-
writing_a_thing.rst
1614
client_code.rst
1715
blobs.rst
18-
thing_dependencies.rst
1916
2017
In this tutorial, we'll cover how to start up and interact with a LabThings-FastAPI server.
2118

docs/source/tutorial/writing_a_thing.rst

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Our first Thing will pretend to be a light: we can set its brightness and turn i
1818
class Light(lt.Thing):
1919
"""A computer-controlled light, our first example Thing."""
2020
21-
brightness: int = lt.property(default=100)
21+
brightness: int = lt.property(default=100, ge=0, le=100)
2222
"""The brightness of the light, in % of maximum."""
2323
2424
is_on: bool = lt.property(default=False, readonly=true)
@@ -39,4 +39,34 @@ Our first Thing will pretend to be a light: we can set its brightness and turn i
3939
4040
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.
4141

42-
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``.
42+
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.
43+
44+
.. _tutorial_properties:
45+
46+
Properties
47+
----------
48+
49+
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.
50+
51+
``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.
52+
53+
It's also possible to have properties defined using a function, for example we could add in:
54+
55+
.. code-block:: python
56+
57+
@lt.property
58+
def status(self) -> str:
59+
"""A human-readable status of the light."""
60+
if self.is_on:
61+
return f"The light is on at {self.brightness}% brightness."
62+
else:
63+
return "The light is off."
64+
65+
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`).
66+
67+
.. _tutorial_actions:
68+
69+
Actions
70+
-----------
71+
72+
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`.

src/labthings_fastapi/exceptions.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,14 @@ class NoBlobManagerError(RuntimeError):
145145
Any access to an invocation output must have BlobIOContextDep as a dependency, as
146146
the output may be a blob, and the blob needs this context to resolve its URL.
147147
"""
148+
149+
150+
class UnsupportedConstraintError(ValueError):
151+
"""A constraint argument is not supported.
152+
153+
This exception is raised when a constraint argument is passed to
154+
a property that is not in the supported list. See
155+
`labthings_fastapi.properties.CONSTRAINT_ARGS` for the list of
156+
supported arguments. Their meaning is described in the `pydantic.Field`
157+
documentation.
158+
"""

0 commit comments

Comments
 (0)