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 _set_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

CONSTRAINT_ARGS

The set of supported constraint arguments for properties.

Value

The value returned by a property.

Owner

The Thing instance on which a property is bound.

BasePropertyT

An instance of (a subclass of) BaseProperty.

Exceptions

OverspecifiedDefaultError

The default value has been specified more than once.

MissingDefaultError

The default value has not been specified.

Classes

FieldConstraints

Constraints that may be applied to a property.

BaseProperty

A descriptor that marks Properties on Things.

DataProperty

A Property descriptor that acts like a regular variable.

FunctionalProperty

A property that uses a getter and a setter.

PropertyInfo

Access to the metadata of a Property.

PropertyCollection

Access to metadata on all the properties of a Thing instance or subclass.

BaseSetting

A base class for settings.

DataSetting

A DataProperty that persists on disk.

FunctionalSetting

A FunctionalProperty that persists on disk.

SettingInfo

Access to the metadata of a setting.

SettingCollection

Access to metadata on all the properties of a Thing instance or subclass.

Functions

default_factory_from_arguments(→ Callable[[], Value])

Process default arguments to get a default factory function.

property(…)

Define a Property on a Thing.

setting(…)

Define a Setting on a Thing.

Module Contents

labthings_fastapi.properties.CONSTRAINT_ARGS

The set of supported constraint arguments for properties.

class labthings_fastapi.properties.FieldConstraints

Bases: TypedDict

Constraints that may be applied to a property.

Initialize self. See help(type(self)) for accurate signature.

gt: int | float
ge: int | float
lt: int | float
le: int | float
multiple_of: int | float
allow_inf_nan: bool
min_length: int
max_length: int
pattern: str
exception labthings_fastapi.properties.OverspecifiedDefaultError

Bases: ValueError

The default value has been specified more than once.

This error is raised when a DataProperty is instantiated with both a default value and a default_factory provided.

Initialize self. See help(type(self)) for accurate signature.

exception labthings_fastapi.properties.MissingDefaultError

Bases: ValueError

The default value has not been specified.

This error is raised when a DataProperty is instantiated without a default value or a default_factory function.

Initialize self. See help(type(self)) for accurate signature.

labthings_fastapi.properties.Value

The value returned by a property.

labthings_fastapi.properties.Owner

The Thing instance on which a property is bound.

labthings_fastapi.properties.BasePropertyT

An instance of (a subclass of) BaseProperty.

labthings_fastapi.properties.default_factory_from_arguments(default: Value | types.EllipsisType = ..., default_factory: Callable[[], Value] | None = None) Callable[[], Value]

Process default arguments to get a default factory function.

This function takes the default and default_factory arguments and will either return the default_factory if 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 DataProperty but it was needed in the property and setting functions in order to correctly type them (so that specifying both or neither of the default and default_factory arguments 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:
labthings_fastapi.properties.property(getter: Callable[[Owner], Value]) FunctionalProperty[Owner, Value]
labthings_fastapi.properties.property(*, default: Value, readonly: bool = False, use_global_lock: Literal[False] | None = None, **constraints: Any) Value
labthings_fastapi.properties.property(*, default_factory: Callable[[], Value], readonly: bool = False, use_global_lock: Literal[False] | None = None, **constraints: Any) Value

Define a Property on a Thing.

This function may be used to define Properties in two ways, as either a decorator or a field specifier. See the examples in the Properties.

Properties should always have a type annotation. This type annotation will be used in automatic documentation and also to serialise the value to JSON when it is sent over the network. This mean that the type of your property should either be JSON serialisable (i.e. simple built-in types) or a subclass of pydantic.BaseModel.

Parameters:
  • getter – is a method of a class that returns the value of this property. This is usually supplied by using property as a decorator.

  • default – is the default value. Either this, getter or default_factory must be specified. Specifying both or neither will raise an exception.

  • default_factory – should return your default value. This may be used as an alternative to default if you need to use a mutable datatype. For example, it would be better to specify default_factory=list than default=[] because the second form would be shared between all Things with this property.

  • readonly – whether the property should be read-only via the ThingClient interface (i.e. over HTTP or via a DirectThingClient). This is automatically true if property is used as a decorator and no setter is specified.

  • use_global_lock – may be set to False to disable the global lock for setting this property. By default, if global locking is enabled, we hold the global lock while setting the property.

  • **constraints – additional keyword arguments are passed to pydantic.Field and allow constraints to be added to the property. For example, ge=0 constrains a numeric property to be non-negative. See pydantic.Field for the full range of constraint arguments.

Returns:

a property descriptor, either a FunctionalProperty if used as a decorator, or a DataProperty if used as a field.

Raises:

Typing Notes

This function has somewhat complicated type hints, for two reasons. Firstly, it may be used either as a decorator or as a field specifier, so default performs double duty as a default value or a getter. Secondly, when used as a field specifier the type hint for the property is attached to the attribute of the class to which the function’s output is assigned. This means property does not know its type hint until after it’s been called.

When used as a field specifier, property returns a generic DataProperty descriptor instance, which will determine its type when it is attached to the Thing. The type hint on the return value of property in that situation is a “white lie”: we annotate the return as having the same type as the default value (or the default_factory return value). This means that type checkers such as mypy will check that the default is valid for the type of the field, and won’t raise an error about assigning, for example, an instance of DataProperty[int] to a field annotated as int.

Finally, the type of the default argument includes EllipsisType so that we can use ... as its default value. This allows us to distinguish between default not being set (...) and a desired default value of None. Similarly, ... is the default value for getter so we can raise a more helpful error if a non-callable value is passed as the first argument.

class labthings_fastapi.properties.BaseProperty(constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None)

Bases: labthings_fastapi.base_descriptor.FieldTypedBaseDescriptor[Owner, Value], Generic[Owner, Value]

A descriptor that marks Properties on Things.

This class is used to determine whether an attribute of a Thing should be treated as a Property (see Properties - essentially, it means the value should be available over HTTP).

BaseProperty should not be used directly, instead it is recommended to use property to declare properties on your Thing subclass.

Initialise a BaseProperty.

Parameters:
  • constraints – is passed as keyword arguments to pydantic.Field to add validation constraints to the property. See pydantic.Field for details. The module-level constant CONSTRAINT_ARGS lists the supported constraint arguments.

  • use_global_lock – may be set to False to disable the global lock for setting this property. By default, if global locking is enabled, we hold the global lock while setting the property.

Raises:

UnsupportedConstraintError – if unsupported constraint arguments are supplied. See CONSTRAINT_ARGS for the supported arguments.

_model: type[pydantic.BaseModel] | None = None
readonly: bool = False
_constraints: FieldConstraints
use_global_lock = None
observable: bool = False

Whether or not the property may be observed.

If observable is True then a websocket connection can register to be notified when the property changes. By default this is True for data properties and False for functional properties.

static _validate_constraints(constraints: collections.abc.Mapping[str, Any]) FieldConstraints

Validate an untyped dictionary of constraints.

Parameters:

constraints – A mapping that will be validated against the FieldConstraints typed dictionary.

Returns:

A FieldConstraints instance.

Raises:

UnsupportedConstraintError – if the input is not valid.

constraints() FieldConstraints

Validation constraints applied to this property.

This mapping contains keyword arguments that will be passed to pydantic.Field to add validation constraints to the property. See pydantic.Field for details. The module-level constant CONSTRAINT_ARGS lists the supported constraint arguments.

Note that these constraints will be enforced when values are received over HTTP, but they are not automatically enforced when setting the property directly on the Thing instance from Python code.

model() type[pydantic.BaseModel]

A Pydantic model for the property’s type.

pydantic models are used to serialise and deserialise values from and to JSON. If the property is defined with a type hint that is not a pydantic.BaseModel subclass, this property will ensure it is wrapped in a pydantic.RootModel so it can be used with FastAPI.

If BaseProperty.value_type is already a pydantic.BaseModel subclass, this returns it unchanged.

Returns:

a Pydantic model for the property’s type.

Raises:

UnserialisableTypeError – if the property can’t be serialised by pydantic to JSON.

get_default(obj: Owner | None) Value

Return the default value of this property.

Parameters:

obj – the Thing instance on which we are looking for the default. or None if referring to the class. For now, this is ignored.

Returns:

the default value of this property.

Raises:

FeatureNotAvailableError – as this must be overridden.

reset(obj: Owner) None

Reset the property’s value to a default state.

If there is a defined default value for the property, this method should reset the property to that default.

Not every property is expected to implement reset so it is important to handle FeatureNotAvailableError exceptions, which will be raised if this method is not overridden.

Parameters:

obj – the Thing instance we want to reset.

Raises:

FeatureNotAvailableError – as only some subclasses implement resetting.

is_resettable(obj: Owner | None) bool

Determine if it’s possible to reset this property.

By default, this returns True if reset has been overridden. If you override reset but want more control over this behaviour, you probably need to override is_resettable.

Parameters:

obj – the Thing instance we want to reset.

Returns:

True if a call to reset() should work.

add_to_fastapi(app: fastapi.FastAPI, thing: Owner) 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 Thing we are adding the endpoints for.

Raises:

NotConnectedToServerError – if the Thing does not have a path set.

property_affordance(thing: Owner, path: str | None = None) labthings_fastapi.thing_description._model.PropertyAffordance

Represent the property in a Thing Description.

Parameters:
  • thing – the Thing to which we are attached.

  • path – the URL of the Thing. If not present, we will retrieve the path from thing.

Returns:

A description of the property in Thing Description format.

Raises:

NotConnectedToServerError – if the Thing does not have a path set.

abstract __set__(obj: Owner, 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.

descriptor_info(owner: Owner | None = None) PropertyInfo[Self, Owner, Value]

Return an object that allows access to this descriptor’s metadata.

Parameters:

owner – An instance to bind the descriptor info to. If None, the returned object will be unbound and will only refer to the class.

Returns:

A PropertyInfo instance describing this property.

class labthings_fastapi.properties.DataProperty(default: Value, *, readonly: bool = False, constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None)
class labthings_fastapi.properties.DataProperty(*, default_factory: Callable[[], Value], readonly: bool = False, constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None)

Bases: BaseProperty[Owner, Value], Generic[Owner, Value]

A Property descriptor that acts like a regular variable.

DataProperty descriptors remember their value, and can be read and written to like a regular Python variable.

Create a property that acts like a regular variable.

DataProperty descriptors function just like variables, in that they can be read and written to as attributes of the Thing and their value will be the same every time it is read (i.e. it changes only when it is set). This differs from FunctionalProperty which uses a “getter” function just like builtins.property and may return a different value each time.

DataProperty instances may always be set, when they are accessed as an attribute of the Thing instance. The readonly parameter applies only to client code, whether it is remote or a DirectThingClient wrapper.

The type of the property’s value will be inferred either from the type subscript or from an annotation on the class attribute. This is done in __get_name__ because neither is available during __init__.

Parameters:
  • default – the default value. This or default_factory must be provided. Note that, as None is a valid default value, this uses ... instead as a way of checking whether default has been set.

  • default_factory – a function that returns the default value. This is appropriate for datatypes such as lists, where using a mutable default value can lead to odd behaviour.

  • readonly – if True, the property may not be written to via HTTP, or via DirectThingClient objects, i.e. it may only be set as an attribute of the Thing and not from a client.

  • constraints – is passed as keyword arguments to pydantic.Field to add validation constraints to the property. See pydantic.Field for details.

  • use_global_lock – may be set to False to disable the global lock for setting this property. By default, if global locking is enabled, we hold the global lock while setting the property.

_default_factory
readonly = False
instance_get(obj: Owner) Value

Return the property’s value.

This will supply a default if the property has not yet been set.

Parameters:

obj – The Thing on which the property is being accessed.

Returns:

the value of the property.

__set__(obj: Owner, value: Value) None

Set the property’s value.

This sets the property’s value, and notifies any observers.

If property validation is enabled by lt.Thing._class_settings this will validate the value against the property’s model, and an error will be raised if the value is not valid.

Parameters:
  • obj – the Thing to which we are attached.

  • value – the new value for the property.

observable: bool = True

Whether or not the property may be observed.

If observable is True then a websocket connection can register to be notified when the property changes. By default this is True for data properties.

get_default(obj: Owner | None) Value

Return the default value of this property.

Note that this implementation is independent of the Thing instance, as there’s currently no way to specify a per-instance default.

Parameters:

obj – the Thing instance we want to reset.

Returns:

the default value of this property.

reset(obj: Owner) None

Reset the property to its default value.

This resets to the value returned by default for DataProperty.

Parameters:

obj – the Thing instance we want to reset.

class labthings_fastapi.properties.FunctionalProperty(fget: Callable[[Owner], Value], constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None)

Bases: BaseProperty[Owner, Value], Generic[Owner, 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.property except 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.property to 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.Field to add validation constraints to the property. See pydantic.Field for details.

  • use_global_lock – may be set to False to disable the global lock for setting this property. By default, if global locking is enabled, we hold the global lock while setting the property.

_fget
_type
_fset: Callable[[Owner, Value], None] | None = None
_freset: Callable[[Owner], None] | None = None
_default_factory: Callable[[], Value] | None = None
readonly: bool = True
fget() Callable[[Owner], Value]

The getter function.

fset() Callable[[Owner, Value], None] | None

The setter function.

setter(fset: Callable[[Owner, Value], None]) 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 set readonly back to True.

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 @property decorator, because our setter does not have the same name as the getter. Using a different name avoids type checkers such as mypy raising 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 the Thing will have an additional method called _set_myprop in 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 property is treated as a special case by mypy and others, and our descriptor is not treated in the same way. Naming the setter and getter the same is required by builtins.property because the property must be overwritten when the setter is added, as builtins.property is 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 a FunctionalProperty | Callable and thus typing errors would happen whenever it’s accessed).

instance_get(obj: Owner) Value

Get the value of the property.

Parameters:

obj – the Thing on which the attribute is accessed.

Returns:

the value of the property.

__set__(obj: Owner, value: Value) None

Set the value of the property.

If property validation is enabled by lt.Thing._class_settings this will validate the value against the property’s model, and an error will be raised if the value is not valid.

Parameters:
  • obj – the Thing on which the attribute is accessed.

  • value – the value of the property.

Raises:

ReadOnlyPropertyError – if the property cannot be set.

default() Value

The default value for this property.

This attribute is mostly provided to allow it to be set at class definition time - it should usually be retrieved through thing_instance.properties['name'].default.

Warning

The default is not guaranteed to be available! It should usually be accessed via a PropertyInfo object, as thing.properties['name'].default. If a default is not available, a FeatureNotAvailableError will be raised.

Raises:

FeatureNotAvailableError – if no default is defined.

Returns:

the default value.

default_factory() Callable[[], Value] | None

The default factory function, if available.

This property will be None if no default is set, or it will be a function that returns a default value.

Setting the default factory will also allow this property to be reset using its reset() method, which will call the property’s setter with the default value. If a reset function was already specified (e.g. with the resetter decorator), it will not be overwritten.

Returns:

the default factory function, or None if it is not set.

get_default(obj: Owner | None) Value

Return a default value, if available.

Parameters:

obj – The Thing for which we are retrieving the default value, or None if we are referring only to the class.

Returns:

the default value.

Raises:

FeatureNotAvailable – if no default has been defined.

resetter(freset: Callable[[Owner], None]) Callable[[Owner], None]

Decorate a method that resets the property to a default state.

Functional properties may optionally define a function that resets the property to a default state. This method is intended to be used as a decorator:

import labthings_fastapi as lt


class MyThing(lt.Thing):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._myprop = 42

    @lt.property
    def myprop(self) -> int:
        return self._myprop

    @myprop.setter
    def _set_myprop(self, val: int) -> None:
        self._myprop = val

    @myprop.resetter
    def _reset_myprop(self) -> None:
        self._myprop = 42
Parameters:

freset – The method being decorated. This should take one positional argument, self, which has the usual meaning for Python methods.

Raises:

PropertyRedefinitionError – if the decorated method has the same name as the property. Please use a different name, as shown in the example above.

Returns:

the decorated function (unchanged). Note that we don’t return the property, so you must choose a different name for the reset function.

reset(obj: Owner) None

Reset the property to its default value.

This resets to the value returned by default for DataProperty.

Parameters:

obj – the Thing instance we want to reset.

Raises:

FeatureNotAvailable – if no reset method is available, which means there is no default defined, and no resetter method.

is_resettable(obj: Owner | None) bool

Whether the property may be reset.

This will be true if a resetter function has been added, or if a default is defined and the property has a setter defined.

Parameters:

obj – the object on which we are defined.

Returns:

whether a call to reset() should succeed.

class labthings_fastapi.properties.PropertyInfo(descriptor: Descriptor, obj: Owner | None, cls: type[Owner] | None = None)

Bases: labthings_fastapi.base_descriptor.FieldTypedBaseDescriptorInfo[BasePropertyT, Owner, Value], Generic[BasePropertyT, Owner, Value]

Access to the metadata of a Property.

This class provides a way to access the metadata of a Property, without needing to retrieve the Descriptor object directly. It may be bound to a Thing instance, or may be accessed from the class.

Initialise an OptionallyBoundInfo object.

This sets up a BaseDescriptorInfo object, describing descriptor and optionally bound to obj.

Parameters:
  • descriptor – The descriptor that this object will describe.

  • obj – The object to which this BaseDescriptorInfo is bound. If it is None (default), the object will be unbound and will refer to the descriptor as attached to the class. This may mean that some methods are unavailable.

  • cls – The class to which we are bound. Only required if obj is None.

Raises:

ValueError – if both obj and cls are None.

model() type[pydantic.BaseModel]

A pydantic.BaseModel describing this property’s value.

model_instance() pydantic.BaseModel

An instance of self.model populated with the current value.

Raises:

TypeError – if the return value can’t be wrapped in a model.

default() Value

The default value of this property.

Warning

Note that this is an optional feature, so calling code must handle FeatureNotAvailableError exceptions.

is_observable() bool

Whether the property may be observed.

is_resettable() bool

Whether the property may be reset using the reset() method.

reset() None

Reset the property to a default value.

Warning

Note that this is an optional feature, so calling code must handle FeatureNotAvailableError exceptions.

validate(value: Any) Value

Use the validation logic in self.model.

This method should accept anything that pydantic can convert to the right type, and return a correctly-typed value. It also enforces any constraints that were set on the property.

Parameters:

value – The new value, in any form acceptable to the property’s model. Usually this means you can use either the correct type, or the value as loaded from JSON.

Returns:

the new value, with the correct type.

Raises:
  • ValidationError – if the supplied value can’t be loaded by the property’s model. This is the exception raised by model_validate

  • TypeError – if the property has a model that’s inconsistent with its value type. This should never happen.

class labthings_fastapi.properties.PropertyCollection(obj: Owner | None, cls: type[Owner] | None = None)

Bases: labthings_fastapi.base_descriptor.DescriptorInfoCollection[Owner, PropertyInfo], Generic[Owner]

Access to metadata on all the properties of a Thing instance or subclass.

This object may be used as a mapping, to retrieve PropertyInfo objects for each Property of a Thing by name. This allows easy access to metadata like their description and model.

Initialise the DescriptorInfoCollection.

This initialises the object, optionally binding it to obj if it is not None.

Parameters:
  • obj – The object to which this info object is bound. If it is None (default), the object will be unbound and will refer to the descriptor as attached to the class. This may mean that some methods are unavailable.

  • cls – The class to which this info object refers. May be omitted if obj is supplied.

_descriptorinfo_class

The class of DescriptorInfo objects contained in this collection.

This class attribute must be set in subclasses.

labthings_fastapi.properties.setting(getter: Callable[[Owner], Value]) FunctionalSetting[Owner, Value]
labthings_fastapi.properties.setting(*, default: Value, readonly: bool = False, use_global_lock: Literal[False] | None = None, **constraints: Any) Value
labthings_fastapi.properties.setting(*, default_factory: Callable[[], Value], readonly: bool = False, use_global_lock: Literal[False] | None = None, **constraints: Any) Value

Define a Setting on a Thing.

A setting is a property that is saved to disk.

This function defines a setting, which is a special Property that will be saved to disk, so it persists even when the LabThings server is restarted. It is otherwise very similar to property.

A type annotation is required, and should follow the same constraints as for @property.

Every setting on a Thing will be read each time the settings are saved, which may be quite frequent. This means your getter must not take too long to run, or have side-effects. Settings that use getters and setters may be removed in the future pending the outcome of #159.

If the type is a pydantic BaseModel, then the setter must also be able to accept the dictionary representation of this BaseModel as this is what will be used to set the Setting when loading from disk on starting the server.

Note

If a setting is mutated rather than set, this will not trigger saving. For example: if a Thing has a setting called dictsetting holding the dictionary {"a": 1, "b": 2} then self.dictsetting = {"a": 2, "b": 2} would trigger saving but self.dictsetting[a] = 2 would not, as the setter for dictsetting is never called.

Parameters:
  • getter – is a method of a class that returns the value of this property. This is usually supplied by using property as a decorator.

  • default – is the default value. Either this, getter or default_factory must be specified. Specifying both or neither will raise an exception.

  • default_factory – should return your default value. This may be used as an alternative to default if you need to use a mutable datatype. For example, it would be better to specify default_factory=list than default=[] because the second form would be shared between all Things with this setting.

  • readonly – whether the setting should be read-only via the ThingClient interface (i.e. over HTTP or via a DirectThingClient).

  • use_global_lock – may be set to False to disable the global lock for setting this setting. By default, if global locking is enabled, we hold the global lock while setting the setting.

  • **constraints – additional keyword arguments are passed to pydantic.Field and allow constraints to be added to the setting. For example, ge=0 constrains a numeric setting to be non-negative. See pydantic.Field for the full range of constraint arguments.

Returns:

a setting descriptor.

Raises:

Typing Notes

See the typing notes on property as they all apply to setting as well.

class labthings_fastapi.properties.BaseSetting(constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None)

Bases: BaseProperty[Owner, Value], Generic[Owner, Value]

A base class for settings.

This is a subclass of BaseProperty that is used to define settings. It is not intended to be used directly, but via setting and the two concrete implementations: DataSetting and FunctionalSetting.

Initialise a BaseProperty.

Parameters:
  • constraints – is passed as keyword arguments to pydantic.Field to add validation constraints to the property. See pydantic.Field for details. The module-level constant CONSTRAINT_ARGS lists the supported constraint arguments.

  • use_global_lock – may be set to False to disable the global lock for setting this property. By default, if global locking is enabled, we hold the global lock while setting the property.

Raises:

UnsupportedConstraintError – if unsupported constraint arguments are supplied. See CONSTRAINT_ARGS for the supported arguments.

descriptor_info(owner: Owner | None = None) SettingInfo[Owner, Value]

Return an object that allows access to this descriptor’s metadata.

Parameters:

owner – An instance to bind the descriptor info to. If None, the returned object will be unbound and will only refer to the class.

Returns:

A SettingInfo instance describing this setting.

class labthings_fastapi.properties.DataSetting(default: Value, *, readonly: bool = False, constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None)
class labthings_fastapi.properties.DataSetting(*, default_factory: Callable[[], Value], readonly: bool = False, constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None)

Bases: DataProperty[Owner, Value], BaseSetting[Owner, Value], Generic[Owner, Value]

A DataProperty that persists on disk.

A setting can be accessed via the HTTP API and is persistent between sessions.

A DataSetting is a DataProperty with extra functionality for triggering a Thing to 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 dictsetting holding the dictionary {"a": 1, "b": 2} then self.dictsetting = {"a": 2, "b": 2} would trigger saving but self.dictsetting[a] = 2 would not, as the setter for dictsetting is never called.

The setting otherwise acts just like a normal variable.

Create a property that acts like a regular variable.

DataProperty descriptors function just like variables, in that they can be read and written to as attributes of the Thing and their value will be the same every time it is read (i.e. it changes only when it is set). This differs from FunctionalProperty which uses a “getter” function just like builtins.property and may return a different value each time.

DataProperty instances may always be set, when they are accessed as an attribute of the Thing instance. The readonly parameter applies only to client code, whether it is remote or a DirectThingClient wrapper.

The type of the property’s value will be inferred either from the type subscript or from an annotation on the class attribute. This is done in __get_name__ because neither is available during __init__.

Parameters:
  • default – the default value. This or default_factory must be provided. Note that, as None is a valid default value, this uses ... instead as a way of checking whether default has been set.

  • default_factory – a function that returns the default value. This is appropriate for datatypes such as lists, where using a mutable default value can lead to odd behaviour.

  • readonly – if True, the property may not be written to via HTTP, or via DirectThingClient objects, i.e. it may only be set as an attribute of the Thing and not from a client.

  • constraints – is passed as keyword arguments to pydantic.Field to add validation constraints to the property. See pydantic.Field for details.

  • use_global_lock – may be set to False to disable the global lock for setting this property. By default, if global locking is enabled, we hold the global lock while setting the property.

__set__(obj: Owner, value: Value) None

Set the setting’s value.

This will cause the settings to be saved to disk.

Parameters:
  • obj – the Thing to which we are attached.

  • value – the new value of the setting.

class labthings_fastapi.properties.FunctionalSetting(fget: Callable[[Owner], Value], constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None)

Bases: FunctionalProperty[Owner, Value], BaseSetting[Owner, Value], Generic[Owner, Value]

A FunctionalProperty that persists on disk.

A setting can be accessed via the HTTP API and is persistent between sessions.

A FunctionalSetting is a FunctionalProperty with extra functionality for triggering a Thing to 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 dictsetting holding the dictionary {"a": 1, "b": 2} then self.dictsetting = {"a": 2, "b": 2} would trigger saving but self.dictsetting[a] = 2 would not, as the setter for dictsetting is 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.property to 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.Field to add validation constraints to the property. See pydantic.Field for details.

  • use_global_lock – may be set to False to disable the global lock for setting this property. By default, if global locking is enabled, we hold the global lock while setting the property.

__set__(obj: Owner, value: Value) None

Set the setting’s value.

This will cause the settings to be saved to disk.

Parameters:
  • obj – the Thing to which we are attached.

  • value – the new value of the setting.

class labthings_fastapi.properties.SettingInfo(descriptor: Descriptor, obj: Owner | None, cls: type[Owner] | None = None)

Bases: PropertyInfo[BaseSetting[Owner, Value], Owner, Value], Generic[Owner, Value]

Access to the metadata of a setting.

Initialise an OptionallyBoundInfo object.

This sets up a BaseDescriptorInfo object, describing descriptor and optionally bound to obj.

Parameters:
  • descriptor – The descriptor that this object will describe.

  • obj – The object to which this BaseDescriptorInfo is bound. If it is None (default), the object will be unbound and will refer to the descriptor as attached to the class. This may mean that some methods are unavailable.

  • cls – The class to which we are bound. Only required if obj is None.

Raises:

ValueError – if both obj and cls are None.

class labthings_fastapi.properties.SettingCollection(obj: Owner | None, cls: type[Owner] | None = None)

Bases: labthings_fastapi.base_descriptor.DescriptorInfoCollection[Owner, SettingInfo], Generic[Owner]

Access to metadata on all the properties of a Thing instance or subclass.

This object may be used as a mapping, to retrieve PropertyInfo objects for each Property of a Thing by name. This allows easy access to metadata like their description and model.

Initialise the DescriptorInfoCollection.

This initialises the object, optionally binding it to obj if it is not None.

Parameters:
  • obj – The object to which this info object is bound. If it is None (default), the object will be unbound and will refer to the descriptor as attached to the class. This may mean that some methods are unavailable.

  • cls – The class to which this info object refers. May be omitted if obj is supplied.

_descriptorinfo_class

The class of DescriptorInfo objects contained in this collection.

This class attribute must be set in subclasses.

model() type[pydantic.BaseModel]

A pydantic.BaseModel representing all the settings.

This pydantic.BaseModel is used to load and save the settings to a file. Note that it uses the model of each setting, so every field in this model will be either a BaseModel or a RootModel instance, unless it is missing.

Wrapping plain types in a RootModel makes no difference to the JSON, but it means that constraints will be applied and it makes it easier to distinguish between missing fields and fields that are set to None.

model_instance() pydantic.BaseModel

An instance of self.model populated with the current setting values.