labthings_fastapi.websockets ============================ .. py:module:: labthings_fastapi.websockets .. autoapi-nested-parse:: Handle notification of events, property, and action status changes. There are several kinds of "event" in the WoT vocabulary, not all of which are called Event, which is why this module is called `notifications`. In all cases, these are events that happen on an exposed Thing, and may need to be relayed to one or more listeners (currently via a WebSocket connection, though long polling may also be an option in the future). The aim at this stage (July 2023) is for a minimal working example that enables property changes to be fed via a websocket. Events proper should not be a big step thereafter. The W3C standard does not define a way for one websocket to handle multiple Things, so for now the websocket endpoint will be associated with a single Thing instance. This may change in the future. (c) Richard Bowman July 2023, released under GNU-LGPL-3.0 Attributes ---------- .. autoapisummary:: labthings_fastapi.websockets.LOGGER labthings_fastapi.websockets.WEBTHING_ERROR_URL Functions --------- .. autoapisummary:: labthings_fastapi.websockets.observation_error_response labthings_fastapi.websockets.relay_notifications_to_websocket labthings_fastapi.websockets.assert_property_is_observable labthings_fastapi.websockets.process_messages_from_websocket labthings_fastapi.websockets.websocket_endpoint Module Contents --------------- .. py:data:: LOGGER .. py:data:: WEBTHING_ERROR_URL :value: 'https://w3c.github.io/web-thing-protocol/errors' .. py:function:: observation_error_response(name: str, affordance_type: Literal['action', 'property'], exception: Exception) -> dict[str, str | dict] Generate a websocket error response for observing an action or property. When a websocket client asks to observe a property or action that either doesn't exist or isn't observable, this function makes a dictionary that can be returned to the client indicating an error. :param name: The name of the affordance being observed. :param affordance_type: The type of the affordance. :param exception: The error that was raised. :returns: A dictionary that may be returned to the websocket. :raises TypeError: if the exception is not a `KeyError` or `.PropertyNotObservableError`\ . .. py:function:: relay_notifications_to_websocket(websocket: fastapi.WebSocket, receive_stream: anyio.streams.memory.MemoryObjectReceiveStream[labthings_fastapi.message_broker.Message]) -> None :async: Relay objects from a stream to a websocket as JSON. :ref:`wot_affordances` (events, actions) that we've registered with will post messages to the queue: this function takes those messages from the queue and passes them to the websocket. :param websocket: the WebSocket we are communicating over. :param receive_stream: a stream that will yield objects that we send over the websocket. .. py:function:: assert_property_is_observable(thing: labthings_fastapi.thing.Thing, property: str) -> bool Check that a Thing has a particular property and it is observable. :param thing: the `~lt.Thing` instance being observed. :param property: the name of the property. :raises KeyError: if the property does not exist. :raises PropertyNotObservableError: if the property isn't observable. :returns: `True` if an exception wasn't raised. .. py:function:: process_messages_from_websocket(websocket: fastapi.WebSocket, send_stream: anyio.streams.memory.MemoryObjectSendStream[labthings_fastapi.message_broker.Message], broker: labthings_fastapi.message_broker.MessageBroker, thing: labthings_fastapi.thing.Thing) -> None :async: Process messages received from a websocket. Currently, this will allow us to observe properties, by registering (or de-registering) for those properties. :param websocket: the WebSocket we are communicating over. :param send_stream: an `anyio.abc.ObjectSendStream` that we use to register for events, i.e. data sent to that stream will be sent through this websocket, by `.relay_notifications_to_websocket`\ . :param broker: the message broker to use for subscriptions. :param thing: the `~lt.Thing` we are attached to. The websocket is specific to one `~lt.Thing`, and this is it. .. py:function:: websocket_endpoint(thing: labthings_fastapi.thing.Thing, websocket: fastapi.WebSocket, broker: labthings_fastapi.message_broker.MessageBroker) -> None :async: Handle communication to a client via websocket. This function handles a websocket connection to a `~lt.Thing`\ 's websocket endpoint. It can add observers to properties and actions, and will forward notifications from the property or action back to the websocket. :param thing: the `~lt.Thing` the websocket is attached to. :param websocket: the web socket that has been created. :param broker: the message broker to use for subscriptions.