labthings_fastapi.properties ============================ .. py:module:: labthings_fastapi.properties .. autoapi-nested-parse:: Define properties of `~lt.Thing` objects. :ref:`properties` are attributes of a `~lt.Thing` that may be read or written to over HTTP, and they are described in :ref:`gen_docs`. They are implemented with a function `~lt.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: .. code-block:: python 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 `~lt.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 ---------- .. autoapisummary:: labthings_fastapi.properties.CONSTRAINT_ARGS labthings_fastapi.properties.Value labthings_fastapi.properties.Owner labthings_fastapi.properties.BasePropertyT Exceptions ---------- .. autoapisummary:: labthings_fastapi.properties.OverspecifiedDefaultError labthings_fastapi.properties.MissingDefaultError Classes ------- .. autoapisummary:: labthings_fastapi.properties.FieldConstraints labthings_fastapi.properties.BaseProperty labthings_fastapi.properties.DataProperty labthings_fastapi.properties.FunctionalProperty labthings_fastapi.properties.PropertyInfo labthings_fastapi.properties.PropertyCollection labthings_fastapi.properties.BaseSetting labthings_fastapi.properties.DataSetting labthings_fastapi.properties.FunctionalSetting labthings_fastapi.properties.SettingInfo labthings_fastapi.properties.SettingCollection Functions --------- .. autoapisummary:: labthings_fastapi.properties.default_factory_from_arguments labthings_fastapi.properties.property labthings_fastapi.properties.setting Module Contents --------------- .. py:data:: CONSTRAINT_ARGS The set of supported constraint arguments for properties. .. py:class:: FieldConstraints Bases: :py:obj:`TypedDict` Constraints that may be applied to a `~lt.property`\ . Initialize self. See help(type(self)) for accurate signature. .. py:attribute:: gt :type: int | float .. py:attribute:: ge :type: int | float .. py:attribute:: lt :type: int | float .. py:attribute:: le :type: int | float .. py:attribute:: multiple_of :type: int | float .. py:attribute:: allow_inf_nan :type: bool .. py:attribute:: min_length :type: int .. py:attribute:: max_length :type: int .. py:attribute:: pattern :type: str .. py:exception:: OverspecifiedDefaultError Bases: :py:obj:`ValueError` The default value has been specified more than once. This error is raised when a `~lt.DataProperty` is instantiated with both a ``default`` value and a ``default_factory`` provided. Initialize self. See help(type(self)) for accurate signature. .. py:exception:: MissingDefaultError Bases: :py:obj:`ValueError` The default value has not been specified. This error is raised when a `~lt.DataProperty` is instantiated without a ``default`` value or a ``default_factory`` function. Initialize self. See help(type(self)) for accurate signature. .. py:data:: Value The value returned by a property. .. py:data:: Owner The `~lt.Thing` instance on which a property is bound. .. py:data:: BasePropertyT An instance of (a subclass of) BaseProperty. .. py:function:: 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 `~lt.DataProperty` but it was needed in the `~lt.property` and `~lt.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). :param default: the default value, or an ellipsis if not specified. :param default_factory: a function that returns the default value. :return: a function that returns the default value. :raises OverspecifiedDefaultError: if both ``default`` and ``default_factory`` are specified. :raises MissingDefaultError: if neither ``default`` nor ``default_factory`` are specified. .. py:function:: property(getter: Callable[[Owner], Value]) -> FunctionalProperty[Owner, Value] property(*, default: Value, readonly: bool = False, use_global_lock: Literal[False] | None = None, **constraints: Any) -> Value property(*, default_factory: Callable[[], Value], readonly: bool = False, use_global_lock: Literal[False] | None = None, **constraints: Any) -> Value Define a Property on a `~lt.Thing`\ . This function may be used to define :ref:`properties` in two ways, as either a decorator or a field specifier. See the examples in the :ref:`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`. :param getter: is a method of a class that returns the value of this property. This is usually supplied by using ``property`` as a decorator. :param default: is the default value. Either this, ``getter`` or ``default_factory`` must be specified. Specifying both or neither will raise an exception. :param 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 `~lt.Thing`\ s with this property. :param readonly: whether the property should be read-only via the `~lt.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. :param 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. :param \**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. :return: a property descriptor, either a `.FunctionalProperty` if used as a decorator, or a `~lt.DataProperty` if used as a field. :raises MissingDefaultError: if no valid default value is supplied, and a getter is not in use. :raises OverspecifiedDefaultError: if the default is specified more than once (e.g. ``default``, ``default_factory``, or ``getter``). **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 `~lt.DataProperty` descriptor instance, which will determine its type when it is attached to the `~lt.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. .. py:class:: BaseProperty(constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None) Bases: :py:obj:`labthings_fastapi.base_descriptor.FieldTypedBaseDescriptor`\ [\ :py:obj:`Owner`\ , :py:obj:`Value`\ ], :py:obj:`Generic`\ [\ :py:obj:`Owner`\ , :py:obj:`Value`\ ] A descriptor that marks Properties on Things. This class is used to determine whether an attribute of a `~lt.Thing` should be treated as a Property (see :ref:`wot_properties` - essentially, it means the value should be available over HTTP). `.BaseProperty` should not be used directly, instead it is recommended to use `~lt.property` to declare properties on your `~lt.Thing` subclass. Initialise a BaseProperty. :param 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. :param 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. .. py:attribute:: _model :type: type[pydantic.BaseModel] | None :value: None .. py:attribute:: readonly :type: bool :value: False .. py:attribute:: _constraints :type: FieldConstraints .. py:attribute:: use_global_lock :value: None .. py:attribute:: observable :type: bool :value: 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. .. py:method:: _validate_constraints(constraints: collections.abc.Mapping[str, Any]) -> FieldConstraints :staticmethod: Validate an untyped dictionary of constraints. :param constraints: A mapping that will be validated against the `.FieldConstraints` typed dictionary. :return: A `.FieldConstraints` instance. :raises UnsupportedConstraintError: if the input is not valid. .. py:method:: 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 `~lt.Thing` instance from Python code. .. py:method:: 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. :return: a Pydantic model for the property's type. :raises UnserialisableTypeError: if the property can't be serialised by `pydantic` to JSON. .. py:method:: get_default(obj: Owner | None) -> Value Return the default value of this property. :param obj: the `~lt.Thing` instance on which we are looking for the default. or `None` if referring to the class. For now, this is ignored. :return: the default value of this property. :raises FeatureNotAvailableError: as this must be overridden. .. py:method:: 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. :param obj: the `~lt.Thing` instance we want to reset. :raises FeatureNotAvailableError: as only some subclasses implement resetting. .. py:method:: 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`\ . :param obj: the `~lt.Thing` instance we want to reset. :return: `True` if a call to ``reset()`` should work. .. py:method:: add_to_fastapi(app: fastapi.FastAPI, thing: Owner) -> None Add this action to a FastAPI app, bound to a particular Thing. :param app: The FastAPI application we are adding endpoints to. :param thing: The `~lt.Thing` we are adding the endpoints for. :raises NotConnectedToServerError: if the `~lt.Thing` does not have a ``path`` set. .. py:method:: property_affordance(thing: Owner, path: str | None = None) -> labthings_fastapi.thing_description._model.PropertyAffordance Represent the property in a Thing Description. :param thing: the `~lt.Thing` to which we are attached. :param path: the URL of the `~lt.Thing`. If not present, we will retrieve the ``path`` from ``thing``. :return: A description of the property in :ref:`wot_td` format. :raises NotConnectedToServerError: if the `~lt.Thing` does not have a ``path`` set. .. py:method:: __set__(obj: Owner, value: Any) -> None :abstractmethod: Set the property (stub method). This is a stub ``__set__`` method to mark this as a data descriptor. :param obj: The Thing on which we are setting the value. :param value: The new value for the Thing. :raises NotImplementedError: as this must be overridden by concrete classes. .. py:method:: descriptor_info(owner: Owner | None = None) -> PropertyInfo[Self, Owner, Value] Return an object that allows access to this descriptor's metadata. :param owner: An instance to bind the descriptor info to. If `None`\ , the returned object will be unbound and will only refer to the class. :return: A `PropertyInfo` instance describing this property. .. py:class:: DataProperty(default: Value, *, readonly: bool = False, constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None) DataProperty(*, default_factory: Callable[[], Value], readonly: bool = False, constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None) Bases: :py:obj:`BaseProperty`\ [\ :py:obj:`Owner`\ , :py:obj:`Value`\ ], :py:obj:`Generic`\ [\ :py:obj:`Owner`\ , :py:obj:`Value`\ ] A Property descriptor that acts like a regular variable. `~lt.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. `~lt.DataProperty` descriptors function just like variables, in that they can be read and written to as attributes of the `~lt.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. `~lt.DataProperty` instances may always be set, when they are accessed as an attribute of the `~lt.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__``. :param 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. :param 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. :param 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 `~lt.Thing` and not from a client. :param constraints: is passed as keyword arguments to `pydantic.Field` to add validation constraints to the property. See `pydantic.Field` for details. :param 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. .. py:attribute:: _default_factory .. py:attribute:: readonly :value: False .. py:method:: instance_get(obj: Owner) -> Value Return the property's value. This will supply a default if the property has not yet been set. :param obj: The `~lt.Thing` on which the property is being accessed. :return: the value of the property. .. py:method:: __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. :param obj: the `~lt.Thing` to which we are attached. :param value: the new value for the property. .. py:attribute:: observable :type: bool :value: 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. .. py:method:: get_default(obj: Owner | None) -> Value Return the default value of this property. Note that this implementation is independent of the `~lt.Thing` instance, as there's currently no way to specify a per-instance default. :param obj: the `~lt.Thing` instance we want to reset. :return: the default value of this property. .. py:method:: reset(obj: Owner) -> None Reset the property to its default value. This resets to the value returned by ``default`` for `~lt.DataProperty`\ . :param obj: the `~lt.Thing` instance we want to reset. .. py:class:: FunctionalProperty(fget: Callable[[Owner], Value], constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None) Bases: :py:obj:`BaseProperty`\ [\ :py:obj:`Owner`\ , :py:obj:`Value`\ ], :py:obj:`Generic`\ [\ :py:obj:`Owner`\ , :py:obj:`Value`\ ] A property that uses a getter and a setter. For properties that should work like variables, use `~lt.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. :param fget: the getter function, called when the property is read. :param constraints: is passed as keyword arguments to `pydantic.Field` to add validation constraints to the property. See `pydantic.Field` for details. :param 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. .. py:attribute:: _fget .. py:attribute:: _type .. py:attribute:: _fset :type: Callable[[Owner, Value], None] | None :value: None .. py:attribute:: _freset :type: Callable[[Owner], None] | None :value: None .. py:attribute:: _default_factory :type: Callable[[], Value] | None :value: None .. py:attribute:: readonly :type: bool :value: True .. py:method:: fget() -> Callable[[Owner], Value] The getter function. .. py:method:: fset() -> Callable[[Owner, Value], None] | None The setter function. .. py:method:: 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``. .. code-block:: python 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 `~lt.Thing` will have an additional method called ``_set_myprop`` in the example above. :param fset: The new setter function. :return: 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). .. py:method:: instance_get(obj: Owner) -> Value Get the value of the property. :param obj: the `~lt.Thing` on which the attribute is accessed. :return: the value of the property. .. py:method:: __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. :param obj: the `~lt.Thing` on which the attribute is accessed. :param value: the value of the property. :raises ReadOnlyPropertyError: if the property cannot be set. .. py:method:: 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. :return: the default value. .. py:method:: 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. :return: the default factory function, or `None` if it is not set. .. py:method:: get_default(obj: Owner | None) -> Value Return a default value, if available. :param obj: The Thing for which we are retrieving the default value, or `None` if we are referring only to the class. :return: the default value. :raises FeatureNotAvailable: if no default has been defined. .. py:method:: 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: .. code-block:: python 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 :param 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. :return: the decorated function (unchanged). Note that we don't return the property, so you must choose a different name for the reset function. .. py:method:: reset(obj: Owner) -> None Reset the property to its default value. This resets to the value returned by ``default`` for `~lt.DataProperty`\ . :param obj: the `~lt.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. .. py: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. :param obj: the object on which we are defined. :return: whether a call to ``reset()`` should succeed. .. py:class:: PropertyInfo(descriptor: Descriptor, obj: Owner | None, cls: type[Owner] | None = None) Bases: :py:obj:`labthings_fastapi.base_descriptor.FieldTypedBaseDescriptorInfo`\ [\ :py:obj:`BasePropertyT`\ , :py:obj:`Owner`\ , :py:obj:`Value`\ ], :py:obj:`Generic`\ [\ :py:obj:`BasePropertyT`\ , :py:obj:`Owner`\ , :py:obj:`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 `~lt.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``\ . :param descriptor: The descriptor that this object will describe. :param 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. :param cls: The class to which we are bound. Only required if ``obj`` is `None`\ . :raises ValueError: if both ``obj`` and ``cls`` are `None`\ . .. py:method:: model() -> type[pydantic.BaseModel] A `pydantic.BaseModel` describing this property's value. .. py:method:: 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. .. py:method:: default() -> Value The default value of this property. .. warning:: Note that this is an optional feature, so calling code must handle `.FeatureNotAvailableError` exceptions. .. py:method:: is_observable() -> bool Whether the property may be observed. .. py:method:: is_resettable() -> bool Whether the property may be reset using the ``reset()`` method. .. py: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. .. py:method:: 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. :param 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. :return: 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`` :raises TypeError: if the property has a ``model`` that's inconsistent with its value type. This should never happen. .. py:class:: PropertyCollection(obj: Owner | None, cls: type[Owner] | None = None) Bases: :py:obj:`labthings_fastapi.base_descriptor.DescriptorInfoCollection`\ [\ :py:obj:`Owner`\ , :py:obj:`PropertyInfo`\ ], :py:obj:`Generic`\ [\ :py:obj:`Owner`\ ] Access to metadata on all the properties of a `~lt.Thing` instance or subclass. This object may be used as a mapping, to retrieve `.PropertyInfo` objects for each Property of a `~lt.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`\ . :param 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. :param cls: The class to which this info object refers. May be omitted if `obj` is supplied. .. py:attribute:: _descriptorinfo_class The class of DescriptorInfo objects contained in this collection. This class attribute must be set in subclasses. .. py:function:: setting(getter: Callable[[Owner], Value]) -> FunctionalSetting[Owner, Value] setting(*, default: Value, readonly: bool = False, use_global_lock: Literal[False] | None = None, **constraints: Any) -> Value setting(*, default_factory: Callable[[], Value], readonly: bool = False, use_global_lock: Literal[False] | None = None, **constraints: Any) -> Value Define a Setting on a `~lt.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 `~lt.property`\ . A type annotation is required, and should follow the same constraints as for :deco:`~lt.property`. Every ``setting`` on a `~lt.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`_. .. _`#159`: https://github.com/labthings/labthings-fastapi/issues/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. :param getter: is a method of a class that returns the value of this property. This is usually supplied by using ``property`` as a decorator. :param default: is the default value. Either this, ``getter`` or ``default_factory`` must be specified. Specifying both or neither will raise an exception. :param 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 `~lt.Thing`\ s with this setting. :param readonly: whether the setting should be read-only via the `~lt.ThingClient` interface (i.e. over HTTP or via a `.DirectThingClient`). :param 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. :param \**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. :return: a setting descriptor. :raises MissingDefaultError: if no valid default or getter is supplied. :raises OverspecifiedDefaultError: if the default is specified more than once (e.g. ``default``, ``default_factory``, or ``getter``). **Typing Notes** See the typing notes on `~lt.property` as they all apply to `~lt.setting` as well. .. py:class:: BaseSetting(constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None) Bases: :py:obj:`BaseProperty`\ [\ :py:obj:`Owner`\ , :py:obj:`Value`\ ], :py:obj:`Generic`\ [\ :py:obj:`Owner`\ , :py:obj:`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 `~lt.setting` and the two concrete implementations: `.DataSetting` and `.FunctionalSetting`\ . Initialise a BaseProperty. :param 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. :param 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. .. py:method:: descriptor_info(owner: Owner | None = None) -> SettingInfo[Owner, Value] Return an object that allows access to this descriptor's metadata. :param owner: An instance to bind the descriptor info to. If `None`\ , the returned object will be unbound and will only refer to the class. :return: A `SettingInfo` instance describing this setting. .. py:class:: DataSetting(default: Value, *, readonly: bool = False, constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None) DataSetting(*, default_factory: Callable[[], Value], readonly: bool = False, constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None) Bases: :py:obj:`DataProperty`\ [\ :py:obj:`Owner`\ , :py:obj:`Value`\ ], :py:obj:`BaseSetting`\ [\ :py:obj:`Owner`\ , :py:obj:`Value`\ ], :py:obj:`Generic`\ [\ :py:obj:`Owner`\ , :py:obj:`Value`\ ] A `~lt.DataProperty` that persists on disk. A setting can be accessed via the HTTP API and is persistent between sessions. A `.DataSetting` is a `~lt.DataProperty` with extra functionality for triggering a `~lt.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. `~lt.DataProperty` descriptors function just like variables, in that they can be read and written to as attributes of the `~lt.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. `~lt.DataProperty` instances may always be set, when they are accessed as an attribute of the `~lt.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__``. :param 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. :param 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. :param 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 `~lt.Thing` and not from a client. :param constraints: is passed as keyword arguments to `pydantic.Field` to add validation constraints to the property. See `pydantic.Field` for details. :param 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. .. py:method:: __set__(obj: Owner, value: Value) -> None Set the setting's value. This will cause the settings to be saved to disk. :param obj: the `~lt.Thing` to which we are attached. :param value: the new value of the setting. .. py:class:: FunctionalSetting(fget: Callable[[Owner], Value], constraints: collections.abc.Mapping[str, Any] | None = None, use_global_lock: Literal[False] | None = None) Bases: :py:obj:`FunctionalProperty`\ [\ :py:obj:`Owner`\ , :py:obj:`Value`\ ], :py:obj:`BaseSetting`\ [\ :py:obj:`Owner`\ , :py:obj:`Value`\ ], :py:obj:`Generic`\ [\ :py:obj:`Owner`\ , :py:obj:`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 `~lt.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. :param fget: the getter function, called when the property is read. :param constraints: is passed as keyword arguments to `pydantic.Field` to add validation constraints to the property. See `pydantic.Field` for details. :param 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. .. py:method:: __set__(obj: Owner, value: Value) -> None Set the setting's value. This will cause the settings to be saved to disk. :param obj: the `~lt.Thing` to which we are attached. :param value: the new value of the setting. .. py:class:: SettingInfo(descriptor: Descriptor, obj: Owner | None, cls: type[Owner] | None = None) Bases: :py:obj:`PropertyInfo`\ [\ :py:obj:`BaseSetting`\ [\ :py:obj:`Owner`\ , :py:obj:`Value`\ ]\ , :py:obj:`Owner`\ , :py:obj:`Value`\ ], :py:obj:`Generic`\ [\ :py:obj:`Owner`\ , :py:obj:`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``\ . :param descriptor: The descriptor that this object will describe. :param 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. :param cls: The class to which we are bound. Only required if ``obj`` is `None`\ . :raises ValueError: if both ``obj`` and ``cls`` are `None`\ . .. py:class:: SettingCollection(obj: Owner | None, cls: type[Owner] | None = None) Bases: :py:obj:`labthings_fastapi.base_descriptor.DescriptorInfoCollection`\ [\ :py:obj:`Owner`\ , :py:obj:`SettingInfo`\ ], :py:obj:`Generic`\ [\ :py:obj:`Owner`\ ] Access to metadata on all the properties of a `~lt.Thing` instance or subclass. This object may be used as a mapping, to retrieve `.PropertyInfo` objects for each Property of a `~lt.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`\ . :param 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. :param cls: The class to which this info object refers. May be omitted if `obj` is supplied. .. py:attribute:: _descriptorinfo_class The class of DescriptorInfo objects contained in this collection. This class attribute must be set in subclasses. .. py:method:: 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`. .. py:method:: model_instance() -> pydantic.BaseModel An instance of ``self.model`` populated with the current setting values.