Skip to content

Commit 542e066

Browse files
committed
Add resettability and defaults to documentation.
1 parent f05c13d commit 542e066

1 file changed

Lines changed: 46 additions & 2 deletions

File tree

docs/source/properties.rst

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,15 @@ It is a good idea to make sure there is a docstring for your property. This will
4545
4646
You don't need to include the type in the docstring, as it will be inferred from the type hint. However, you can include additional information about the property, such as its units or any constraints on its value.
4747

48+
If your property's default value is a mutable datatype, like a list or dictionary, it's a good idea to use a *default factory* instead of a default value, in order to prevent strange behaviour. This may be done as shown:
49+
50+
.. code-block:: python
51+
52+
class MyThing(lt.Thing):
53+
my_list: list[int] = lt.property(default_factory=list)
54+
55+
The example above will have its default value set to the empty list, as that's what is returned when ``list()`` is called. It's often convenient to use a "lambda function" as a default factory, for example `lambda: [1,2,3]` is a function that returns the list `[1,2,3]`\ . This is better than specifying a default value, because it returns a fresh copy of the object every time - using a list as a default value can lead to multiple `.Thing` instances changing in sync unexpectedly, which gets very confusing.
56+
4857
Data properties may be *observed*, which means notifications will be sent when the property is written to (see below).
4958

5059
Functional properties
@@ -93,7 +102,7 @@ Adding a setter makes the property read-write (if only a getter is present, it m
93102

94103
The setter method for regular Python properties is usually named the same as the property itself (e.g. ``def twice_my_property(self, value: int)``). Unfortunately, doing this with LabThings properties causes problems for static type checkers such as `mypy`\ . We therefore recommend you prefix setters with ``_set_`` (e.g. ``def _set_twice_my_property(self, value: int)``). This is optional, and doesn't change the way the property works - but it is useful if you need `mypy` to work on your code, and don't want to ignore every property setter.
95104

96-
It is possible to make a property read-only for clients by setting its ``readonly`` attribute: this has the same behaviour as for data properties.
105+
It is possible to make a property read-only for clients by setting its ``readonly`` attribute: this has the same behaviour as for data properties. A default can also be specified in the same way:
97106

98107
.. code-block:: python
99108
@@ -115,11 +124,41 @@ It is possible to make a property read-only for clients by setting its ``readonl
115124
116125
# Make the property read-only for clients
117126
twice_my_property.readonly = True
127+
# Add a default to the Thing Description
128+
twice_my_property.default = 84
118129
119-
In the example above, ``twice_my_property`` may be set by code within ``MyThing`` but cannot be written to via HTTP requests or `.DirectThingClient` instances.
130+
In the example above, ``twice_my_property`` may be set by code within ``MyThing`` but cannot be written to via HTTP requests or `.DirectThingClient` instances. It's worth noting that you may assign to ``twice_my_property.default_factory`` instead, just like using the ``default_factory`` argument of ``lt.property``\ .
120131

121132
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.
122133

134+
Functional properties may define a "resetter" method, which resets them to some initial state. This may be done even if a default has not been defined. To do this, you may use the property as a decorator, just like adding a setter:
135+
136+
.. code-block:: python
137+
138+
import labthings_fastapi as lt
139+
140+
class MyThing(lt.Thing):
141+
def __init__(self, **kwargs):
142+
super().__init__(self, **kwargs)
143+
self._hardware = MyHardwareClass()
144+
145+
@lt.property
146+
def setpoint(self) -> int:
147+
"""The hardware's setpoint."""
148+
return self._hardware.get_setpoint()
149+
150+
@setpoint.setter
151+
def _set_setpoint(self, value: int):
152+
"""Change the hardware setpoint."""
153+
self._hardware.set_setpoint(value)
154+
155+
@setpoint.resetter
156+
def _reset_setpoint(self):
157+
"""Reset the hardware's setpoint."""
158+
self._hardware.reset_setpoint()
159+
160+
A resetter method, if defined, will take precedence over a default value if both are present. If a default value (or factory) is set and there is no resetter method, resetting the property will simply call the property's setter with the default value.
161+
123162
.. _property_constraints:
124163

125164
Property constraints
@@ -167,6 +206,11 @@ Note that the constraints for functional properties are set by assigning a dicti
167206

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

209+
Property metadata
210+
-----------------
211+
212+
Properties in LabThings are intended to work very much like native Python properties. This means that getting and setting the attributes of a `.Thing` get and set the value of the property. Other operations, like reading the default value or resetting to default, need a different interface. For this, we use `.Thing.properties` which is a mapping of names to `.PropertyInfo` objects. These expose the extra functionality of properties in a convenient way. For example, I can reset a property by calling ``self.properties["myprop"].reset()`` or get its default by reading ``self.properties["myprop"].default``\ . See the `.PropertyInfo` API documentation for a full list of available properties and methods.
213+
170214
HTTP interface
171215
--------------
172216

0 commit comments

Comments
 (0)