labthings_fastapi.actions ========================= .. py:module:: labthings_fastapi.actions .. autoapi-nested-parse:: Actions module. :ref:`actions` are represented by methods, decorated with the `.action` decorator. See the :ref:`actions` documentation for a top-level overview of actions in LabThings-FastAPI. Developer notes --------------- Currently much of the code related to Actions is in `.action` and the underlying `.ActionDescriptor`. This is likely to be refactored in the near future. Attributes ---------- .. autoapisummary:: labthings_fastapi.actions.ACTION_INVOCATIONS_PATH Classes ------- .. autoapisummary:: labthings_fastapi.actions.Invocation labthings_fastapi.actions.ActionManager Module Contents --------------- .. py:data:: ACTION_INVOCATIONS_PATH :value: '/action_invocations' The API route used to list `.Invocation` objects. .. py:class:: Invocation(action: ActionDescriptor, thing: labthings_fastapi.thing.Thing, id: uuid.UUID, input: Optional[pydantic.BaseModel] = None, dependencies: Optional[dict[str, Any]] = None, log_len: int = 1000) Bases: :py:obj:`threading.Thread` A Thread subclass that retains output values and tracks progress. `.Invocation` threads add several bits of functionality compared to the base `threading.Thread`. * They are instantiated with an `.ActionDescriptor` and a `.Thing` rather than a target function (see ``__init__``). * Each invocation is assigned a unique ``ID`` to allow it to be polled over HTTP. * A `.CancelHook` is provided to allow the invocation to stop gracefully if it is cancelled by the user. Create a thread to run an action and track its outputs. :param action: provides the function that we run, as well as metadata and type information. The descriptor is not bound to an object, so we supply the `.Thing` it's bound to when the function is run. :param thing: is the object on which we are running the ``action``, i.e. it is supplied to the function wrapped by ``action`` as the ``self`` argument. :param id: is a `uuid.UUID` used to identify the invocation, for example when polling its status via HTTP. :param input: is a `pydantic.BaseModel` representing the body of the HTTP request that invoked the action. It is supplied to the function as keyword arguments. :param dependencies: is a dictionary of keyword arguments, supplied by FastAPI by its dependency injection mechanism. :param log_len: sets the number of log entries that will be held in memory by the invocation's logger. .. py:attribute:: action_ref .. py:attribute:: thing_ref .. py:attribute:: input :value: None .. py:attribute:: dependencies :value: None .. py:attribute:: _ID .. py:attribute:: retention_time .. py:attribute:: expiry_time :type: Optional[datetime.datetime] :value: None .. py:attribute:: _status_lock .. py:attribute:: _status :type: labthings_fastapi.invocations.InvocationStatus .. py:attribute:: _return_value :type: Optional[Any] :value: None .. py:attribute:: _request_time :type: datetime.datetime .. py:attribute:: _start_time :type: Optional[datetime.datetime] :value: None .. py:attribute:: _end_time :type: Optional[datetime.datetime] :value: None .. py:attribute:: _exception :type: Optional[Exception] :value: None .. py:attribute:: _log :type: collections.deque .. py:property:: id :type: uuid.UUID UUID for the thread. Note this not the same as the native thread ident. .. py:property:: output :type: Any Return value of the Action. If the Action is still running, returns None. :raise NoBlobManagerError: If this is called in a context where the blob manager context variables are not available. This stops errors being raised later once the blob is returned and tries to serialise. If the errors happen during serialisation the stack-trace will not clearly identify the route with the missing dependency. .. py:property:: log :type: list[labthings_fastapi.invocations.LogRecordModel] A list of log items generated by the Action. .. py:property:: status :type: labthings_fastapi.invocations.InvocationStatus Current running status of the thread. See `.InvocationStatus` for the values and their meanings. .. py:property:: thing :type: labthings_fastapi.thing.Thing The `.Thing` to which the action is bound, i.e. this is ``self``. :raises RuntimeError: if the Thing no longer exists. .. py:property:: cancel_hook :type: labthings_fastapi.invocation_contexts.CancelEvent The cancel event associated with this Invocation. .. py:method:: cancel() -> None Cancel the task by requesting the code to stop. This is an opt-in feature: the action must use a `.CancelHook` dependency and periodically check it. .. py:method:: response(request: Optional[fastapi.Request] = None) -> labthings_fastapi.invocations.InvocationModel Generate a representation of the invocation suitable for HTTP. When an invocation is polled, we return a JSON object that includes its status, any log entries, a return value (if completed), and a link to poll for updates. :param request: is used to generate the ``href`` in the response, which should retrieve an updated version of this response. :return: an `.InvocationModel` representing this `.Invocation`. .. py:method:: run() -> None Run the action and track progress. `.Invocation` overrides the default `threading.Thread.run` method to add ways to track its progress and capture the return value. The code to be run is the function wrapped in the `.ActionDescriptor` that is passed in as ``action``. Its arguments are the associated `.Thing` (the first argument, i.e. ``self``), the ``input`` model (split into keyword arguments for each field), and any ``dependencies`` (also as keyword arguments). We update the status of the action by setting ``self._status`` and emitting a changed event. This runs async code in the event loop that informs any clients listening over websockets that the event's status has changed. Logs are retained by a custom log handler, and are included when the `.Invocation` is serialised over HTTP. If exceptions are raised by the action code, these are caught and stored. The status is then set to ERROR and the thread terminates. See `.Invocation.status` for status values. :raises RuntimeError: if there is no Thing associated with the invocation. .. py:class:: ActionManager A class to manage a collection of actions. Set up an `.ActionManager`. .. py:attribute:: _invocations :type: dict[uuid.UUID, Invocation] .. py:attribute:: _invocations_lock .. py:property:: invocations :type: list[Invocation] A list of all the `.Invocation` objects running or recently completed. .. py:method:: append_invocation(invocation: Invocation) -> None Add an `.Invocation` to the `.ActionManager`. :param invocation: The `.Invocation` to add. .. py:method:: invoke_action(action: ActionDescriptor, thing: labthings_fastapi.thing.Thing, id: uuid.UUID, input: Any, dependencies: dict[str, Any]) -> Invocation Invoke an action, returning the thread where it's running. See `.Invocation` for more details. :param action: provides the function that we run, as well as metadata and type information. The descriptor is not bound to an object, so we supply the `.Thing` it's bound to when the function is run. :param thing: is the object on which we are running the ``action``, i.e. it is supplied to the function wrapped by ``action`` as the ``self`` argument. :param id: is a `uuid.UUID` used to identify the invocation, for example when polling its status via HTTP. :param input: is a `pydantic.BaseModel` representing the body of the HTTP request that invoked the action. It is supplied to the function as keyword arguments. :param dependencies: is a dictionary of keyword arguments, supplied by FastAPI by its dependency injection mechanism. :return: an `.Invocation` object that has been started. .. py:method:: get_invocation(id: uuid.UUID) -> Invocation Retrieve an invocation by ID. :param id: the unique ID of the action to retrieve. :return: the `.Invocation` object. .. py:method:: list_invocations(action: Optional[ActionDescriptor] = None, thing: Optional[labthings_fastapi.thing.Thing] = None, request: Optional[fastapi.Request] = None) -> list[labthings_fastapi.invocations.InvocationModel] All of the invocations currently managed. Returns a list of `.InvocationModel` instances representing all the invocations that are currently running, or have recently completed and not yet expired. :param action: filters out only the invocations of a particular `.ActionDescriptor`. Note that if there are two Things of the same subclass, filtering by action will return invocations on either `.Thing`. :param thing: returns only invocations of actions on a particular `.Thing`. This will often be combined with filtering by ``action`` to give the list of invocations returned by a GET request on an action endpoint. :param request: is used to pass a `fastapi.Request` object to the `.Invocation.response` method. Doing so ensures the URL returned as ``href`` in the response matches the address used to communicate with the server (i.e. it uses `fastapi.Request.url_for` instead of a path generated from a string). :return: A list of invocations, optionally filtered by Thing and/or Action. .. py:method:: expire_invocations() -> None Delete invocations that have passed their expiry time. .. py:method:: attach_to_app(app: fastapi.FastAPI) -> None Add /action_invocations and /action_invocation/{id} endpoints to FastAPI. :param app: The `fastapi.FastAPI` application to which we add the endpoints.