labthings_fastapi.properties
Define properties of Thing objects.
Properties are attributes of a Thing that may be read or written to
over HTTP, and they are described in Generated documentation. They are implemented with
a function property (usually referenced as lt.property), which is
intentionally similar to Python’s built in property.
Properties can be defined in two ways as shown below:
import labthings_fastapi as lt
class Counter(lt.Thing):
"A counter that knows what's remaining."
count: int = lt.property(default=0, readonly=True)
"The number of times we've incremented the counter."
target: int = lt.property(default=10)
"The number of times to increment before we stop."
@lt.property
def remaining(self) -> int:
"The number of steps remaining."
return self.target - self.count
@remaining.setter
def remaining(self, value: int) -> None:
self.target = self.count + value
The first two properties are simple variables: they may be read and assigned
to, and will behave just like a regular variable. Their syntax is similar to
dataclasses or pydantic in that property is used as a “field specifier”
to set options like the default value, and the type annotation is on the
class attribute. Documentation is in strings immediately following the
properties, which is understood by most automatic documentation tools.
remaining is defined using a “getter” function, meaning this code will
be run each time counter.remaining is accessed. Its type will be the
return type of the function, and its docstring will come from the function
too. Setters with only a getter are read-only.
Adding a “setter” to properties is optional, and makes them read-write.
Attributes
The set of supported constraint arguments for properties. |
|
Exceptions
The default value has been specified more than once. |
|
The default value has not been specified. |
Classes
A descriptor that marks Properties on Things. |
|
A property that uses a getter and a setter. |
|
A base class for settings. |
|
A |
Functions
|
Process default arguments to get a default factory function. |
Module Contents
- labthings_fastapi.properties.CONSTRAINT_ARGS
The set of supported constraint arguments for properties.
- exception labthings_fastapi.properties.OverspecifiedDefaultError
Bases:
ValueErrorThe default value has been specified more than once.
This error is raised when a
DataPropertyis instantiated with both adefaultvalue and adefault_factoryprovided.Initialize self. See help(type(self)) for accurate signature.
- exception labthings_fastapi.properties.MissingDefaultError
Bases:
ValueErrorThe default value has not been specified.
This error is raised when a
DataPropertyis instantiated without adefaultvalue or adefault_factoryfunction.Initialize self. See help(type(self)) for accurate signature.
- labthings_fastapi.properties.Value
- type labthings_fastapi.properties.ValueFactory = Callable[[], Value]
- labthings_fastapi.properties.default_factory_from_arguments(default: Value | types.EllipsisType = ..., default_factory: ValueFactory | None = None) ValueFactory
Process default arguments to get a default factory function.
This function takes the
defaultanddefault_factoryarguments and will either return thedefault_factoryif it is provided, or will wrap the default value provided in a factory function.Note that this wrapping does not copy the default value each time it is called, so mutable default values are only safe if supplied as a factory function.
This is used to avoid repeating the logic of checking whether a default value or a factory function has been provided, and it returns a factory rather than a default value so that it may be called multiple times to get copies of the default value.
This function also ensures the default is specified exactly once, and raises exceptions if it is not.
This logic originally lived only in the initialiser of
DataPropertybut it was needed in thepropertyandsettingfunctions in order to correctly type them (so that specifying both or neither of thedefaultanddefault_factoryarguments would raise an error with mypy).- Parameters:
default – the default value, or an ellipsis if not specified.
default_factory – a function that returns the default value.
- Returns:
a function that returns the default value.
- Raises:
OverspecifiedDefaultError – if both
defaultanddefault_factoryare specified.MissingDefaultError – if neither
defaultnordefault_factoryare specified.
- class labthings_fastapi.properties.BaseProperty(constraints: collections.abc.Mapping[str, Any] | None = None)
Bases:
labthings_fastapi.base_descriptor.FieldTypedBaseDescriptor[Value],Generic[Value]A descriptor that marks Properties on Things.
This class is used to determine whether an attribute of a
Thingshould be treated as a Property (see Properties - essentially, it means the value should be available over HTTP).BasePropertyshould not be used directly, instead it is recommended to usepropertyto declare properties on yourThingsubclass.Initialise a BaseProperty.
- Parameters:
constraints – is passed as keyword arguments to
pydantic.Fieldto add validation constraints to the property. Seepydantic.Fieldfor details. The module-level constantCONSTRAINT_ARGSlists the supported constraint arguments.- Raises:
UnsupportedConstraintError – if unsupported constraint arguments are supplied. See
CONSTRAINT_ARGSfor the supported arguments.
- _model: type[pydantic.BaseModel] | None = None
- constraints
- model() type[pydantic.BaseModel]
A Pydantic model for the property’s type.
pydanticmodels are used to serialise and deserialise values from and to JSON. If the property is defined with a type hint that is not apydantic.BaseModelsubclass, this property will ensure it is wrapped in apydantic.RootModelso it can be used with FastAPI.If
BaseProperty.value_typeis already apydantic.BaseModelsubclass, this returns it unchanged.- Returns:
a Pydantic model for the property’s type.
- add_to_fastapi(app: fastapi.FastAPI, thing: labthings_fastapi.thing.Thing) None
Add this action to a FastAPI app, bound to a particular Thing.
- Parameters:
app – The FastAPI application we are adding endpoints to.
thing – The
Thingwe are adding the endpoints for.
- Raises:
NotConnectedToServerError – if the
Thingdoes not have apathset.
- property_affordance(thing: labthings_fastapi.thing.Thing, path: str | None = None) labthings_fastapi.thing_description._model.PropertyAffordance
Represent the property in a Thing Description.
- Parameters:
- Returns:
A description of the property in Thing Description format.
- Raises:
NotConnectedToServerError – if the
Thingdoes not have apathset.
- abstract __set__(obj: labthings_fastapi.thing.Thing, value: Any) None
Set the property (stub method).
This is a stub
__set__method to mark this as a data descriptor.- Parameters:
obj – The Thing on which we are setting the value.
value – The new value for the Thing.
- Raises:
NotImplementedError – as this must be overridden by concrete classes.
- class labthings_fastapi.properties.FunctionalProperty(fget: ValueGetter, constraints: collections.abc.Mapping[str, Any] | None = None)
Bases:
BaseProperty[Value],Generic[Value]A property that uses a getter and a setter.
For properties that should work like variables, use
DataProperty. For properties that need to run code every time they are read, use this class.Functional properties should work very much like Python’s
builtins.propertyexcept that they are also available over HTTP.Set up a FunctionalProperty.
Create a descriptor for a property that uses a getter function.
This class also inherits from
builtins.propertyto help type checking tools understand that it functions like a property.- Parameters:
fget – the getter function, called when the property is read.
constraints – is passed as keyword arguments to
pydantic.Fieldto add validation constraints to the property. Seepydantic.Fieldfor details.
- Raises:
MissingTypeError – if the getter does not have a return type annotation.
- _fget: ValueGetter
- _type
- fget() ValueGetter
The getter function.
- getter(fget: ValueGetter) typing_extensions.Self
Set the getter function of the property.
This function returns the descriptor, so it may be used as a decorator. If the function has a docstring, it will be used as the property docstring.
- Parameters:
fget – The new getter function.
- Returns:
this descriptor (i.e.
self). This allows use as a decorator.
- setter(fset: ValueSetter) typing_extensions.Self
Set the setter function of the property.
This function returns the descriptor, so it may be used as a decorator.
Once a setter has been added to a property, it will automatically become writeable from client code (over HTTP and via
DirectThingClient). To override this behaviour you may setreadonlyback toTrue.class MyThing(lt.Thing): def __init__(self, thing_server_interface): super().__init__(thing_server_interface=thing_server_interface) self._myprop: int = 0 @lt.property def myprop(self) -> int: "An example property that is an integer" return self._myprop @myprop.setter def set_myprop(self, val: int) -> None: self._myprop = val myprop.readonly = True # Prevent client code from setting it
Note
The example code above is not quite what would be done for the built-in
@propertydecorator, because our setter does not have the same name as the getter. Using a different name avoids type checkers such asmypyraising an error that the getter has been redefined with a different type. The behaviour is identical whether the setter and getter have the same name or not. The only difference is that theThingwill have an additional method calledset_mypropin the example above.- Parameters:
fset – The new setter function.
- Returns:
this descriptor (i.e.
self). This allows use as a decorator.
Typing Notes
Python’s built-in
propertyis treated as a special case bymypyand others, and our descriptor is not treated in the same way. Naming the setter and getter the same is required bybuiltins.propertybecause the property must be overwritten when the setter is added, asbuiltins.propertyis not mutable.Our descriptor is mutable, so the setter may be added without having to overwrite the object. While it would be nice to use exactly the same conventions as
builtins.property, it currently causes type errors that must be silenced manually. We suggest using a different name for the setter as an alternative to adding# type: ignore[no-redef]to the setter function.It will cause problems elsewhere in the code if descriptors are assigned to more than one attribute, and this is checked in
BaseDescriptor.__set_name__. We therefore return the setter rather than the descriptor if the names don’t match. The type hint does not reflect this, as it would cause problems when the names do match (the descriptor would become aFunctionalProperty | Callableand thus typing errors would happen whenever it’s accessed).
- instance_get(obj: labthings_fastapi.thing.Thing) Value
Get the value of the property.
- Parameters:
obj – the
Thingon which the attribute is accessed.- Returns:
the value of the property.
- __set__(obj: labthings_fastapi.thing.Thing, value: Value) None
Set the value of the property.
- Parameters:
obj – the
Thingon which the attribute is accessed.value – the value of the property.
- Raises:
ReadOnlyPropertyError – if the property cannot be set.
- class labthings_fastapi.properties.BaseSetting(constraints: collections.abc.Mapping[str, Any] | None = None)
Bases:
BaseProperty[Value],Generic[Value]A base class for settings.
This is a subclass of
BasePropertythat is used to define settings. It is not intended to be used directly, but viasettingand the two concrete implementations:DataSettingandFunctionalSetting.Initialise a BaseProperty.
- Parameters:
constraints – is passed as keyword arguments to
pydantic.Fieldto add validation constraints to the property. Seepydantic.Fieldfor details. The module-level constantCONSTRAINT_ARGSlists the supported constraint arguments.- Raises:
UnsupportedConstraintError – if unsupported constraint arguments are supplied. See
CONSTRAINT_ARGSfor the supported arguments.
- abstract set_without_emit(obj: labthings_fastapi.thing.Thing, value: Value) None
Set the setting’s value without emitting an event.
This is used to set the setting’s value without notifying observers. It is used during initialisation to set the value from disk before the server is fully started.
- Parameters:
obj – the
Thingto which we are attached.value – the new value of the setting.
- Raises:
NotImplementedError – this method should be implemented in subclasses.
- class labthings_fastapi.properties.FunctionalSetting(fget: ValueGetter, constraints: collections.abc.Mapping[str, Any] | None = None)
Bases:
FunctionalProperty[Value],BaseSetting[Value],Generic[Value]A
FunctionalPropertythat persists on disk.A setting can be accessed via the HTTP API and is persistent between sessions.
A
FunctionalSettingis aFunctionalPropertywith extra functionality for triggering aThingto save its settings.Note: If a setting is mutated rather than assigned to, this will not trigger saving. For example: if a Thing has a setting called
dictsettingholding the dictionary{"a": 1, "b": 2}thenself.dictsetting = {"a": 2, "b": 2}would trigger saving butself.dictsetting[a] = 2would not, as the setter fordictsettingis never called.The setting otherwise acts just like a
FunctionalProperty`, i.e. it uses a getter and a setter function.Set up a FunctionalProperty.
Create a descriptor for a property that uses a getter function.
This class also inherits from
builtins.propertyto help type checking tools understand that it functions like a property.- Parameters:
fget – the getter function, called when the property is read.
constraints – is passed as keyword arguments to
pydantic.Fieldto add validation constraints to the property. Seepydantic.Fieldfor details.
- Raises:
MissingTypeError – if the getter does not have a return type annotation.
- __set__(obj: labthings_fastapi.thing.Thing, value: Value) None
Set the setting’s value.
This will cause the settings to be saved to disk.
- Parameters:
obj – the
Thingto which we are attached.value – the new value of the setting.
- set_without_emit(obj: labthings_fastapi.thing.Thing, value: Value) None
Set the property’s value, but do not emit event to notify the server.
This function is not expected to be used externally. It is called during initial setup so that the setting can be set from disk before the server is fully started.
- Parameters:
obj – the
Thingto which we are attached.value – the new value of the setting.