labthings_fastapi ================= .. py:module:: labthings_fastapi .. autoapi-nested-parse:: LabThings-FastAPI. This is the top level module for LabThings-FastAPI, a library for building :ref:`wot_cc` devices using Python. There is documentation on readthedocs_, and the recommended place to start is :doc:`index`\ . .. _readthedocs: https://labthings-fastapi.readthedocs.io/ This module contains a number of convenience imports and is intended to be imported using: .. code-block:: python import labthings_fastapi as lt The example code elsewhere in the documentation generally follows this convention. Symbols in the top-level module mostly exist elsewhere in the package, but should be imported from here as a preference, to ensure code does not break if modules are rearranged. Submodules ---------- .. toctree:: :maxdepth: 1 /autoapi/labthings_fastapi/actions/index /autoapi/labthings_fastapi/client/index /autoapi/labthings_fastapi/decorators/index /autoapi/labthings_fastapi/dependencies/index /autoapi/labthings_fastapi/deps/index /autoapi/labthings_fastapi/descriptors/index /autoapi/labthings_fastapi/example_things/index /autoapi/labthings_fastapi/exceptions/index /autoapi/labthings_fastapi/notifications/index /autoapi/labthings_fastapi/outputs/index /autoapi/labthings_fastapi/server/index /autoapi/labthings_fastapi/thing/index /autoapi/labthings_fastapi/thing_description/index /autoapi/labthings_fastapi/types/index /autoapi/labthings_fastapi/utilities/index /autoapi/labthings_fastapi/websockets/index Classes ------- .. autoapisummary:: labthings_fastapi.Thing labthings_fastapi.ThingProperty labthings_fastapi.ThingSetting labthings_fastapi.ThingServer labthings_fastapi.ThingClient Functions --------- .. autoapisummary:: labthings_fastapi.thing_property labthings_fastapi.thing_setting labthings_fastapi.thing_action labthings_fastapi.fastapi_endpoint labthings_fastapi.get_blocking_portal Package Contents ---------------- .. py:class:: Thing 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. * ``__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:`.thing_action` decorator declares a method to be an action, which will run when it's triggered, and the :deco:`.thing_property` decorator (or `.ThingProperty` descriptor) does the same for a property. 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. They may in time be collected together into a single object to avoid namespace clashes. .. py:attribute:: title :type: str A human-readable description of the Thing .. py:attribute:: _labthings_blocking_portal :type: Optional[anyio.from_thread.BlockingPortal] :value: None See :ref:`concurrency` for why blocking portal is needed. .. py:attribute:: path :type: Optional[str] The path at which the `.Thing` is exposed over HTTP. .. py:method:: __aenter__() -> typing_extensions.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, path: str, setting_storage_path: str) -> 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. :param path: The root URL for the Thing. :param setting_storage_path: The path on disk to save the any Thing Settings to. This should be the path to a json file. If it does not exist it will be created. Attaching the `.Thing` to a `.ThingServer` allows the `.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:attribute:: _settings_store :type: Optional[dict[str, labthings_fastapi.descriptors.ThingSetting]] :value: None .. py:property:: _settings :type: dict[str, labthings_fastapi.descriptors.ThingSetting] A private property that returns a dict of all settings for this Thing. Each dict key is the name of the setting, the corresponding value is the ThingSetting class (a descriptor). This can be used to directly get the descriptor so that the value can be set without emitting signals, such as on startup. .. py:attribute:: _setting_storage_path :type: Optional[str] :value: None .. py:property:: setting_storage_path :type: Optional[str] The storage path for settings. .. note:: This is set in `.Thing.attach_to_server`. It is ``None`` during the ``__init__`` method, so it is best to avoid using settings until the `.Thing` is set up in ``__enter__``. .. py:method:: load_settings(setting_storage_path: str) -> 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__``. :param setting_storage_path: The path where the settings should be stored. .. py:method:: save_settings() Save settings to JSON. This is called whenever a setting is updated. All settings are written to the settings file every time. .. 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 `.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:: observe_property(property_name: str, stream: anyio.abc.ObjectSendStream) -> None Register a stream to receive property change notifications. :param property_name: the property to register for. :param stream: the stream used to send events. :raise KeyError: if the requested name is not defined on this Thing. .. py:method:: observe_action(action_name: str, stream: anyio.abc.ObjectSendStream) Register a stream to receive action status change notifications. :param action_name: the action to register for. :param stream: the stream used to send events. :raise KeyError: if the requested name is not defined on this Thing. .. py:class:: ThingProperty(model: type, initial_value: Any = None, readonly: bool = False, observable: bool = False, description: Optional[str] = None, title: Optional[str] = None, getter: Optional[Callable] = None, setter: Optional[Callable] = None) A property that can be accessed via the HTTP API. By default, a ThingProperty acts like a normal variable, but functionality can be added in several ways. Create a property that can be accessed via the HTTP API. `.ThingProperty` is a descriptor that functions like a variable, optionally with notifications when it is set. It may also have a getter and setter, which work in a similar way to Python properties. `.ThingProperty` can behave in several different ways: * If no ``getter`` or ``setter`` is specified, it will behave like a simple data attribute (i.e. a variable). If ``observable`` is ``True``, it is possible to register for notifications when the value is set. In this case, an ``initial_value`` is required. * If a ``getter`` is specified and ``observable`` is ``False``, the ``getter`` will be called when the property is accessed, and its return value will be the property's value, just like the builtin ``property``. The property will be read-only both locally and via HTTP. * If a ``getter`` is specified and ``observable`` is ``True``, the ``getter`` is used instead of ``initial_value`` but thereafter the property behaves like a variable. The ``getter`` is only on first access. The property may be written to locally, and whether it's writable via HTTP depends on the ``readonly`` argument. * If both a ``getter`` and ``setter`` are specified and ``observable`` is ``False``, the property behaves like a Python property, with the ``getter`` being called when the property is accessed, and the ``setter`` being called when the property is set. The property is read-only via HTTP if ``readonly`` is ``True``. It may always be written to locally. * If ``observable`` is ``True`` and a ``setter`` is specified, the property will behave like a variable, but will call the ``setter`` when the property is set. The ``setter`` may perform tasks like sending the updated value to the hardware, but it is not responsible for remembering the value. The initial value is set via the ``getter`` or ``initial_value``. :param model: The type of the property. This is optional, because it is better to use type hints (see notes on typing above). :param initial_value: The initial value of the property. If this is set, the property must not have a getter, and should behave like a variable. :param readonly: If True, the property cannot be set via the HTTP API. :param observable: If True, the property can be observed for changes via websockets. This causes the setter to run code in the async event loop that will notify a list of subscribers each time the property is set. Currently, only websockets can be used to observe properties. :param description: A description of the property, used in the API documentation. LabThings will attempt to take this from the docstring if not supplied. :param title: A human-readable title for the property, used in the API documentation. Defaults to the first line of the docstring, or the name of the property. :param getter: A function that gets the value of the property. :param setter: A function that sets the value of the property. :raise ValueError: if the initial value or type are missing or incorrectly specified. .. py:attribute:: model :type: type[pydantic.BaseModel] .. py:attribute:: readonly :type: bool :value: False .. py:attribute:: observable :value: False .. py:attribute:: initial_value :value: None .. py:attribute:: _description :value: None .. py:attribute:: _title :value: None .. py:attribute:: _setter .. py:attribute:: _getter .. py:method:: __set_name__(owner: type[labthings_fastapi.thing.Thing], name: str) -> None Take note of the name to which the descriptor is assigned. This is called when the descriptor is assigned to an attribute of a class. :param owner: the `.Thing` subclass to which we are being attached. :param name: the name to which we have been assigned. .. py:property:: title A human-readable title for the property. .. py:property:: description A description of the property. .. py:method:: __get__(obj: labthings_fastapi.thing.Thing | None, type: ThingProperty.__get__.type | None = None) -> Any Return the value of the property. If `obj` is none (i.e. we are getting the attribute of the class), we return the descriptor. If no getter is set, we'll return either the initial value, or the value from the object's __dict__, i.e. we behave like a variable. If a getter is set, we will use it, unless the property is observable, at which point the getter is only ever used once, to set the initial value. :param obj: the `.Thing` to which we are attached. :param type: the class on which we are defined. :return: the value of the property (when accessed on an instance), or this descriptor if accessed as a class attribute. .. py:method:: __set__(obj: labthings_fastapi.thing.Thing, value: Any) -> None Set the property's value. :param obj: the `.Thing` to which we are attached. :param value: the new value for the property. .. py:method:: _observers_set(obj: labthings_fastapi.thing.Thing) Return the observers of this property. Each observer in this set will be notified when the property is changed. See ``.ThingProperty.emit_changed_event`` :param obj: the `.Thing` to which we are attached. :return: the set of observers corresponding to ``obj``. .. py:method:: emit_changed_event(obj: labthings_fastapi.thing.Thing, value: Any) -> None Notify subscribers that the property has changed. This function is run when properties are updated. It must be run from within a thread. This could be the `Invocation` thread of a running action, or the property should be updated over via a client/http. It must be run from a thread as it is communicating with the event loop via an `asyncio` blocking portal and can cause deadlock if run in the event loop. :param obj: the `.Thing` to which we are attached. :param value: the new property value, to be sent to observers. :raise NotConnectedToServerError: if the Thing that is calling the property update is not connected to a server with a running event loop. .. py:method:: emit_changed_event_async(obj: labthings_fastapi.thing.Thing, value: Any) :async: Notify subscribers that the property has changed. This function may only be run in the `anyio` event loop. See `.ThingProperty.emit_changed_event`. :param obj: the `.Thing` to which we are attached. :param value: the new property value, to be sent to observers. .. py:property:: name The name of the property. This should be consistent between the class definition and the :ref:`wot_td` as well as appearing in the URLs for getting and setting. .. py:method:: add_to_fastapi(app: fastapi.FastAPI, thing: labthings_fastapi.thing.Thing) -> 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 `.Thing` we are adding the endpoints for. .. py:method:: property_affordance(thing: labthings_fastapi.thing.Thing, path: Optional[str] = None) -> labthings_fastapi.thing_description._model.PropertyAffordance Represent the property in a Thing Description. :param thing: the `.Thing` to which we are attached. :param path: the URL of the `.Thing`. If not present, we will retrieve the ``path`` from ``thing``. :return: A description of the property in :ref:`wot_td` format. .. py:method:: getter(func: Callable) -> typing_extensions.Self Set the function that gets the property's value. :param func: is the new getter function. :return: this property (to allow its use as a decorator). .. py:method:: setter(func: Callable) -> typing_extensions.Self Change the setter function. `.ThingProperty` descriptors return the value they hold when they are accessed. However, they can run code when they are set: this decorator sets a function as that code. :param func: is the new setter function. :return: this property (to allow its use as a decorator). .. py:class:: ThingSetting(model: type, initial_value: Any = None, readonly: bool = False, observable: bool = False, description: Optional[str] = None, title: Optional[str] = None, getter: Optional[Callable] = None, setter: Optional[Callable] = None) Bases: :py:obj:`ThingProperty` A `.ThingProperty` that persists on disk. A setting can be accessed via the HTTP API and is persistent between sessions. A `.ThingSetting` is a `.ThingProperty` 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 can be accessed via the HTTP API. `.ThingProperty` is a descriptor that functions like a variable, optionally with notifications when it is set. It may also have a getter and setter, which work in a similar way to Python properties. `.ThingProperty` can behave in several different ways: * If no ``getter`` or ``setter`` is specified, it will behave like a simple data attribute (i.e. a variable). If ``observable`` is ``True``, it is possible to register for notifications when the value is set. In this case, an ``initial_value`` is required. * If a ``getter`` is specified and ``observable`` is ``False``, the ``getter`` will be called when the property is accessed, and its return value will be the property's value, just like the builtin ``property``. The property will be read-only both locally and via HTTP. * If a ``getter`` is specified and ``observable`` is ``True``, the ``getter`` is used instead of ``initial_value`` but thereafter the property behaves like a variable. The ``getter`` is only on first access. The property may be written to locally, and whether it's writable via HTTP depends on the ``readonly`` argument. * If both a ``getter`` and ``setter`` are specified and ``observable`` is ``False``, the property behaves like a Python property, with the ``getter`` being called when the property is accessed, and the ``setter`` being called when the property is set. The property is read-only via HTTP if ``readonly`` is ``True``. It may always be written to locally. * If ``observable`` is ``True`` and a ``setter`` is specified, the property will behave like a variable, but will call the ``setter`` when the property is set. The ``setter`` may perform tasks like sending the updated value to the hardware, but it is not responsible for remembering the value. The initial value is set via the ``getter`` or ``initial_value``. :param model: The type of the property. This is optional, because it is better to use type hints (see notes on typing above). :param initial_value: The initial value of the property. If this is set, the property must not have a getter, and should behave like a variable. :param readonly: If True, the property cannot be set via the HTTP API. :param observable: If True, the property can be observed for changes via websockets. This causes the setter to run code in the async event loop that will notify a list of subscribers each time the property is set. Currently, only websockets can be used to observe properties. :param description: A description of the property, used in the API documentation. LabThings will attempt to take this from the docstring if not supplied. :param title: A human-readable title for the property, used in the API documentation. Defaults to the first line of the docstring, or the name of the property. :param getter: A function that gets the value of the property. :param setter: A function that sets the value of the property. :raise ValueError: if the initial value or type are missing or incorrectly specified. .. py:method:: __set__(obj: labthings_fastapi.thing.Thing, value: Any) Set the setting's value. This will cause the settings to be saved to disk. :param obj: the `.Thing` to which we are attached. :param value: the new value of the setting. .. py:method:: set_without_emit(obj: labthings_fastapi.thing.Thing, value: Any) Set the property's value, but do not emit event to notify the server. This function is not expected to be used externally. It is called during initial setup so that the setting can be set from disk before the server is fully started. :param obj: the `.Thing` to which we are attached. :param value: the new value of the setting. .. py:function:: thing_property(func: Callable) -> labthings_fastapi.descriptors.ThingProperty Mark a method of a Thing as a LabThings Property. This should be used as a decorator with a getter and a setter just like a standard python `property` decorator. If extra functionality is not required in the decorator, then using the `.ThingProperty` class directly may allow for clearer code 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 th 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 func: A method to use as the getter for the new property. :return: A `.ThingProperty` descriptor that works like `property` but allows the value to be read over HTTP. .. py:function:: thing_setting(func: Callable) -> labthings_fastapi.descriptors.ThingSetting Mark a method of a Thing as a LabThings Setting. A setting is a property that is saved to disk, so it persists even when the LabThings server is restarted. This should be used as a decorator with a getter and a setter just like a standard python property decorator. If extra functionality is not required in the decorator, then using the `ThingSetting` class directly may allow for clearer code where the property works like a variable. When creating a setting using this decorator, you must always add a setter as it is used to load the value from disk. This follows the same syntax as for `property`, i.e. a second function with the same name, decorated with ``@my_property_name.setter``. A type annotation is required, and should follow the same constraints as for :deco:`thing_property`. 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 func: A method to use as the getter for the new property. :return: A `.ThingSetting` descriptor that works like `property` but allows the value to be read over HTTP and saves it to disk. .. py:function:: thing_action(func: Callable, **kwargs) -> labthings_fastapi.descriptors.ActionDescriptor thing_action(**kwargs) -> Callable[[Callable], labthings_fastapi.descriptors.ActionDescriptor] Mark a method of a `.Thing` as a LabThings Action. Methods decorated with :deco:`thing_action` will be available to call over HTTP as actions. See :ref:`actions` for an introduction to the concept of actions. This decorator may be used with or without arguments. :param func: The method to be decorated as an action. :param \**kwargs: Keyword arguments are passed to the constructor of `.ActionDescriptor`. :return: Whether used with or without arguments, the result is that the method is wrapped in an `.ActionDescriptor`, so it can be called as usual, but will also be exposed over HTTP. .. py:function:: fastapi_endpoint(method: labthings_fastapi.descriptors.HTTPMethod, path: Optional[str] = None, **kwargs) -> Callable[[Callable], labthings_fastapi.descriptors.EndpointDescriptor] Mark a function as a FastAPI endpoint without making it an action. This decorator will cause a method of a `.Thing` to be directly added to the HTTP API, bypassing the machinery underlying Action and Property affordances. Such endpoints will not be documented in the :ref:`wot_td` but may be used as the target of links. For example, this could allow a file to be downloaded from the `.Thing` at a known URL, or serve a video stream that wouldn't be supported as a `.Blob`\ . The majority of `.Thing` implementations won't need this decorator, but it is here to enable flexibility when it's needed. This decorator always takes arguments; in particular, ``method`` is required. It should be used as: .. code-block:: python class DownloadThing(Thing): @fastapi_endpoint("get") def plain_text_response(self) -> str: return "example string" This decorator is intended to work very similarly to the `fastapi` decorators ``@app.get``, ``@app.post``, etc., with two changes: 1. The path is relative to the host `.Thing` and will default to the name of the method. 2. The method will be called with the host `.Thing` as its first argument, i.e. it will be bound to the class as usua. :param method: The HTTP verb this endpoint responds to. :param path: The path, relative to the host `.Thing` base URL. :param \**kwargs: Additional keyword arguments are passed to the `fastapi.FastAPI.get` decorator if ``method`` is ``get``, or to the equivalent decorator for other HTTP verbs. :return: When used as intended, the result is an `.EndpointDescriptor`. .. py:class:: ThingServer(settings_folder: Optional[str] = None) Use FastAPI to serve `.Thing` instances. The `.ThingServer` sets up a `fastapi.FastAPI` application and uses it to expose the capabilities of `.Thing` instances over HTTP. There are several functions of a `.ThingServer`: * Manage where settings are stored, to allow `.Thing` instances to load and save their settings from disk. * Configure the server to allow cross-origin requests (required if we use a web app that is not served from the `.ThingServer`). * Manage the threads used to run :ref:`actions`. * Manage :ref:`blobs` to allow binary data to be returned. * Allow threaded code to call functions in the event loop, by providing an `anyio.from_thread.BlockingPortal`. Initialise a LabThings server. Setting up the `.ThingServer` involves creating the underlying `fastapi.FastAPI` app, setting its lifespan function (used to set up and shut down the `.Thing` instances), and configuring it to allow cross-origin requests. We also create the `.ActionManager` to manage :ref:`actions` and the `.BlobManager` to manage the downloading of :ref:`blobs`. :param settings_folder: the location on disk where `.Thing` settings will be saved. .. py:attribute:: app .. py:attribute:: settings_folder :value: './settings' .. py:attribute:: action_manager .. py:attribute:: blob_data_manager .. py:attribute:: _things :type: dict[str, labthings_fastapi.thing.Thing] .. py:attribute:: blocking_portal :type: Optional[anyio.from_thread.BlockingPortal] :value: None .. py:attribute:: startup_status :type: dict[str, str | dict] .. py:method:: set_cors_middleware() -> None Configure the server to allow requests from other origins. This is required to allow web applications access to the HTTP API, if they are not served from the same origin (i.e. if they are not served as part of the `.ThingServer`.). This is usually needed during development, and may be needed at other times depending on how you are using LabThings. .. py:property:: things :type: collections.abc.Mapping[str, labthings_fastapi.thing.Thing] Return a dictionary of all the things. :return: a dictionary mapping thing paths to `.Thing` instances. .. py:attribute:: ThingInstance .. py:method:: things_by_class(cls: type[ThingInstance]) -> Sequence[ThingInstance] Return all Things attached to this server matching a class. Return all instances of ``cls`` attached to this server. :param cls: A `.Thing` subclass. :return: all instances of ``cls`` that have been added to this server. .. py:method:: thing_by_class(cls: type[ThingInstance]) -> ThingInstance Return the instance of ``cls`` attached to this server. This function calls `.ThingServer.things_by_class`, but asserts that there is exactly one match. :param cls: a `.Thing` subclass. :return: the instance of ``cls`` attached to this server. :raise RuntimeError: if there is not exactly one matching Thing. .. py:method:: add_thing(thing: labthings_fastapi.thing.Thing, path: str) -> None Add a thing to the server. :param thing: The `.Thing` instance to add to the server. :param path: the relative path to access the thing on the server. Must only contain alphanumeric characters, hyphens, or underscores. :raise ValueError: if ``path`` contains invalid characters. :raise KeyError: if a `.Thing` has already been added at ``path``. .. py:method:: lifespan(app: fastapi.FastAPI) -> AsyncGenerator[None] :async: Manage set up and tear down of the server and Things. This method is used as a lifespan function for the FastAPI app. See the lifespan_ page in FastAPI's documentation. .. _lifespan: https://fastapi.tiangolo.com/advanced/events/#lifespan-function This does two important things: * It sets up the blocking portal so background threads can run async code (this is required for events, streams, etc.). * It runs setup/teardown code for Things by calling them as context managers. :param app: The FastAPI application wrapped by the server. :yield: no value. The FastAPI application will serve requests while this function yields. .. py:method:: add_things_view_to_app() Add an endpoint that shows the list of attached things. .. py:class:: ThingClient(base_url: str, client: Optional[httpx.Client] = None) A client for a LabThings-FastAPI Thing. .. note:: ThingClient must be subclassed to add actions/properties, so this class will be minimally useful on its own. The best way to get a client for a particular Thing is currently `.ThingClient.from_url`, which dynamically creates a subclass with the right attributes. Create a ThingClient connected to a remote Thing. :param base_url: the base URL of the Thing. This should be the URL of the Thing Description document. :param client: an optional `httpx.Client` object to use for all HTTP requests. This may be a `fastapi.TestClient` object for testing purposes. .. py:attribute:: server .. py:attribute:: path .. py:attribute:: client .. py:method:: get_property(path: str) -> Any Make a GET request to retrieve the value of a property. :param path: the URI of the ``getproperty`` endpoint, relative to the ``base_url``. :return: the property's value, as deserialised from JSON. .. py:method:: set_property(path: str, value: Any) Make a PUT request to set the value of a property. :param path: the URI of the ``getproperty`` endpoint, relative to the ``base_url``. :param value: the property's value. Currently this must be serialisable to JSON. .. py:method:: invoke_action(path: str, **kwargs) Invoke an action on the Thing. This method will make the initial POST request to invoke an action, then poll the resulting invocation until it completes. If successful, the action's output will be returned directly. While the action is running, log messages will be re-logged locally. If you have enabled logging to the console, these should be visible. :param path: the URI of the ``invokeaction`` endpoint, relative to the ``base_url`` :param \**kwargs: Additional arguments will be combined into the JSON body of the ``POST`` request and sent as input to the action. These will be validated on the server. :return: the output value of the action. :raise RuntimeError: is raised if the action does not complete successfully. .. py:method:: follow_link(response: dict, rel: str) -> httpx.Response Follow a link in a response object, by its `rel` attribute. :param response: is the dictionary returned by e.g. `.poll_invocation`. :param rel: picks the link to follow by matching its ``rel`` item. :return: the response to making a ``GET`` request to the link. .. py:method:: from_url(thing_url: str, client: Optional[httpx.Client] = None) -> typing_extensions.Self :classmethod: Create a ThingClient from a URL. This will dynamically create a subclass with properties and actions, and return an instance of that subclass pointing at the Thing URL. :param thing_url: The base URL of the Thing, which should also be the URL of its Thing Description. :param client: is an optional `httpx.Client` object. If not present, one will be created. This is particularly useful if you need to set HTTP options, or if you want to work with a local server object for testing purposes (see `fastapi.TestClient`). :return: a `.ThingClient` subclass with properties and methods that match the retrieved Thing Description (see :ref:`wot_thing`). .. py:method:: subclass_from_td(thing_description: dict) -> type[typing_extensions.Self] :classmethod: Create a ThingClient subclass from a Thing Description. Dynamically subclass `.ThingClient` to add properties and methods for each property and action in the Thing Description. :param thing_description: A :ref:`wot_td` as a dictionary, which will be used to construct the class. :return: a `.ThingClient` subclass with the right properties and methods. .. py:function:: get_blocking_portal(obj: labthings_fastapi.thing.Thing) -> Optional[anyio.from_thread.BlockingPortal] Retrieve a blocking portal from a Thing. See :ref:`concurrency` for more details. When a `.Thing` is attached to a `.ThingServer` and the `.ThingServer` is started, it sets an attribute on each `.Thing` to allow it to access an `anyio.from_thread.BlockingPortal`. This allows threaded code to call async code. This function retrieves the blocking portal from a `.Thing`. :param obj: the `.Thing` on which we are looking for the portal. :return: the blocking portal.