labthings_fastapi.thing ======================= .. py:module:: labthings_fastapi.thing .. autoapi-nested-parse:: A class to represent hardware or software Things. The `~lt.Thing` class enables most of the functionality of this library, and is the way in to most of its features. See :ref:`structure` for how it fits with the rest of the library. Classes ------- .. autoapisummary:: labthings_fastapi.thing.Thing Module Contents --------------- .. py:class:: Thing(thing_server_interface: labthings_fastapi.thing_server_interface.ThingServerInterface) Represents a Thing, as defined by the Web of Things standard. This class should encapsulate the code that runs a piece of hardware, or provides a particular function - it will correspond to a path on the server, and a Thing Description document. Subclassing Notes ----------------- * ``__init__``: You should accept any arguments you need to configure the Thing in ``__init__``. Don't initialise any hardware at this time, as your Thing may be instantiated quite early, or even at import time. You must make sure to call ``super().__init__(thing_server_interface)``\ . * ``__enter__(self)`` and ``__exit__(self, exc_t, exc_v, exc_tb)`` are where you should start and stop communications with the hardware. This is Python's "context manager" protocol. The arguments of ``__exit__`` will be ``None`` except after errors. You should be safe to ignore them, and just include code that will close down your hardware, which is equivalent to a ``finally:`` block. * Properties and Actions are defined using decorators: the :deco:`lt.action` decorator declares a method to be an action, which will run when it's triggered, and the :deco:`~lt.property` decorator does the same for a property. Properties may also be defined using dataclass-style syntax, if they do not need getter and setter functions. See the documentation on those functions for more detail. * `title` will be used in various places as the human-readable name of your Thing, so it makes sense to set this in a subclass. There are various LabThings methods that you should avoid overriding unless you know what you are doing: anything not mentioned above that's defined in `Thing` is probably best left alone. Initialise a Thing. The most important function of ``__init__`` is attaching the thing_server_interface, and setting the path. Note that `Thing` instances are usually created by a `~lt.ThingServer` and not instantiated directly: if you do make a `Thing` directly, you will need to supply a `~lt.ThingServerInterface` that is connected to a `~lt.ThingServer` or a suitable mock object. :param thing_server_interface: The interface to the server that is hosting this Thing. It will be supplied when the `Thing` is instantiated by the `~lt.ThingServer` or by `.create_thing_without_server` which generates a mock interface. .. py:attribute:: title :type: str A human-readable description of the Thing .. py:attribute:: _class_settings :type: labthings_fastapi.thing_class_settings.ThingClassSettings A dictionary of settings that affect how the Thing subclass works. Valid keys are listed below: ``validate_properties_on_set`` `bool` If this key is set to `True`\ , property values will be validated when they are set by Python code, as well as when they are set over HTTP. Currently, the default behaviour is only to validate values sent over HTTP, not set directly in Python. It is likely that validation in both cases will happen by default in a future release. .. note:: Class settings must not be changed after the class is defined. .. py:attribute:: _thing_server_interface :type: labthings_fastapi.thing_server_interface.ThingServerInterface Provide access to features of the server that this `Thing` is attached to. .. py:attribute:: _disable_saving_settings :type: bool :value: False .. py:method:: __init_subclass__(**kwargs: Any) -> None :classmethod: Validate the class settings. :param \**kwargs: are passed to the superclass. .. py:property:: path :type: str The path at which the `~lt.Thing` is exposed over HTTP. .. py:property:: name :type: str The name of this Thing, as known to the server. .. py:property:: logger :type: logging.Logger A logger, named after this Thing. .. py:method:: __aenter__() -> Self :async: Context management is used to set up/close the thing. As things (currently) do everything with threaded code, we define async ``__aenter__`` and ``__aexit__`` wrappers to call the synchronous code, if it exists. :return: this object. .. py:method:: __aexit__(exc_t: Any | None, exc_v: Any | None, exc_tb: Any) -> None :async: Wrap context management functions, if they exist. See ``__aenter__`` for more details. :param exc_t: The type of the exception, or ``None``. :param exc_v: The exception that occurred, or ``None``. :param exc_tb: The traceback for the exception, or ``None``. .. py:method:: attach_to_server(server: labthings_fastapi.server.ThingServer) -> None Attach this thing to the server. Things need to be attached to a server before use to function correctly. :param server: The server to attach this Thing to. Attaching the `~lt.Thing` to a `~lt.ThingServer` allows the `~lt.Thing` to start actions, load its settings from the correct place, and create HTTP endpoints to allow it to be accessed from the HTTP API. We create HTTP endpoints for all :ref:`wot_affordances` on the `Thing`, as well as any `.EndpointDescriptor` descriptors. .. py:method:: _read_settings_file() -> collections.abc.Mapping[str, Any] | None Read the settings file and return a mapping of saved settings or None. This function handles reading the settings from the disk. It is designed to be called by `load_settings`. Any exceptions caused by file handling or file corruption are caught and logged as warnings. :return: A Mapping of setting name to setting value, or None if no settings could be read from file. .. py:method:: load_settings() -> None Load settings from json. Read the JSON file and use it to populate settings. .. note:: Settings are loaded when the Thing is added to a server, so they will not be available while the ``__init__`` method is run. Note that no notifications will be triggered when the settings are set, so if action is needed (e.g. updating hardware with the loaded settings) it should be taken in ``__enter__``. .. py:method:: save_settings() -> None Save settings to JSON. This is called whenever a setting is updated. All settings are written to the settings file every time. .. py:attribute:: properties :type: labthings_fastapi.base_descriptor.OptionallyBoundDescriptor[Thing, labthings_fastapi.properties.PropertyCollection] Access to metadata and functions of this `~lt.Thing`\ 's properties. `~lt.Thing.properties` is a mapping of names to `.PropertyInfo` objects, which allows convenient access to the metadata related to its properties. Note that this includes settings, as they are a subclass of properties. .. py:attribute:: settings :type: labthings_fastapi.base_descriptor.OptionallyBoundDescriptor[Thing, labthings_fastapi.properties.SettingCollection] Access to settings-related metadata and functions. `~lt.Thing.settings` is a mapping of names to `.SettingInfo` objects that allows convenient access to metadata of the settings of this `~lt.Thing`\ . .. py:attribute:: actions :type: labthings_fastapi.base_descriptor.OptionallyBoundDescriptor[Thing, labthings_fastapi.actions.ActionCollection] Access to metadata for the actions of this `~lt.Thing`\ . `~lt.Thing.actions` is a mapping of names to `.ActionInfo` objects that allows convenient access to metadata of each action. .. py:attribute:: _labthings_thing_state :type: Optional[dict] :value: None .. py:property:: thing_state :type: collections.abc.Mapping Return a dictionary summarising our current state. This is intended to be an easy way to collect metadata from a Thing that summarises its state. It might be used, for example, to record metadata along with each reading/image/etc. when an instrument is saving data. It's best to populate this automatically so it can always be accessed. If it requires calls e.g. to a serial instrument, bear in mind it may be called quite often and shouldn't take too long. Some measure of caching here is a nice aim for the future, but not yet implemented. .. py:method:: validate_thing_description() -> None Raise an exception if the thing description is not valid. .. py:attribute:: _cached_thing_description :type: Optional[tuple[Optional[str], Optional[str], labthings_fastapi.thing_description._model.ThingDescription]] :value: None .. py:method:: thing_description(path: Optional[str] = None, base: Optional[str] = None) -> labthings_fastapi.thing_description._model.ThingDescription Generate a w3c Thing Description representing this thing. The w3c Web of Things working group defined a standard representation of a Thing, which provides a high-level description of the actions, properties, and events that it exposes. This endpoint delivers a JSON representation of the :ref:`wot_td` for this Thing. :param path: the URL pointing to this Thing. :param base: the base URL for all URLs in the thing description. :return: a Thing Description. .. py:method:: thing_description_dict(path: Optional[str] = None, base: Optional[str] = None) -> dict Describe this Thing with a Thing Description as a simple dict. See `~lt.Thing.thing_description`\ . This function converts the return value of that function into a simple dictionary. :param path: the URL pointing to this Thing. :param base: the base URL for all URLs in the thing description. :return: a Thing Description. .. py:method:: get_current_invocation_logs() -> list[logging.LogRecord] Get the log records for an on going action. This is useful if an action wishes to save its logs alongside any data. Note that only the last 1000 logs are returned so for long running tasks that log frequently this may want to be read periodically. This will error if it is called outside an action invocation. :return: a list of all logs from this action. :raises RuntimeError: If the server cannot be retrieved. This should never happen.