labthings_fastapi.actions
Actions module.
Actions are represented by methods, decorated with the thing_action
decorator.
See the Actions documentation for a top-level overview of actions in LabThings-FastAPI.
Developer notes
Currently much of the code related to Actions is in thing_action and the
underlying ActionDescriptor. This is likely to be refactored in the near
future.
Submodules
Attributes
This context variable gives access to a function that makes BlobData objects |
|
The API route used to list |
Exceptions
An invocation was cancelled by the user. |
|
Raised if an API route accesses Invocation outputs without a BlobIOContextDep. |
Classes
Represent the input of an action that has no required parameters. |
|
The current status of an |
|
A model to serialise |
|
A Thread subclass that retains output values and tracks progress. |
|
A log handler that stores entries in memory. |
|
A class to manage a collection of actions. |
Functions
|
Convert a pydantic model to a dictionary, non-recursively. |
Package Contents
- labthings_fastapi.actions.model_to_dict(model: pydantic.BaseModel | None) Dict[str, Any]
Convert a pydantic model to a dictionary, non-recursively.
We convert only the top level model, i.e. we do not recurse into submodels. This is important to avoid serialising Blob objects in action inputs. This function returns
dict(model), with exceptions for the case ofNone(converted to an empty dictionary) andpydantic.RootModel(checked to see if they correspond to empty input).If
pydantic.RootModelwith non-empty input is allowed, this function will need to be updated to handle them.- Parameters:
model – A Pydantic model (usually the input of an action).
- Returns:
A dictionary with string keys, which are the fields of the model. This should be suitable for using as
**kwargsto an action.- Raises:
ValueError – if we are given a root model that isn’t empty.
- class labthings_fastapi.actions.EmptyInput(/, root=PydanticUndefined, **data)
Bases:
pydantic.RootModelRepresent the input of an action that has no required parameters.
This may be either a dictionary or
None.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.- root: EmptyObject | None = None
- class labthings_fastapi.actions.LinkElement(/, **data: Any)
Bases:
pydantic.BaseModelSee https://www.w3.org/TR/wot-thing-description11/#link.
Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.- model_config
Configuration for the model, should be a dictionary conforming to [
ConfigDict][pydantic.config.ConfigDict].
- href: AnyUri
- class labthings_fastapi.actions.InvocationStatus(*args, **kwds)
Bases:
enum.EnumThe current status of an
Invocation.- PENDING = 'pending'
The
Invocationhas not yet been started.
- RUNNING = 'running'
The
Invocationis running in its thread.
- COMPLETED = 'completed'
The
Invocationfinished successfully. A return value may be available.
- CANCELLED = 'cancelled'
The
Invocationwas cancelled and has finished.
- ERROR = 'error'
The
Invocationterminated unexpectedly due to an error.
- class labthings_fastapi.actions.LogRecordModel(/, **data: Any)
Bases:
pydantic.BaseModelA model to serialise
logging.LogRecordobjects.Create a new model by parsing and validating input data from keyword arguments.
Raises [
ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.selfis explicitly positional-only to allowselfas a field name.- model_config
Configuration for the model, should be a dictionary conforming to [
ConfigDict][pydantic.config.ConfigDict].
- created: datetime.datetime
- classmethod generate_message(data: Any) Any
Ensure LogRecord objects have constructed their message.
- Parameters:
data – The LogRecord to process.
- Returns:
The LogRecord, with a message constructed.
- exception labthings_fastapi.actions.InvocationCancelledError
Bases:
BaseExceptionAn invocation was cancelled by the user.
Note that this inherits from BaseException so won’t be caught by
except Exception, it must be handled specifically.Action code may want to handle cancellation gracefully. This exception should be propagated if the action’s status should be reported as
cancelled, or it may be handled so that the action finishes, returns a value, and is marked ascompleted.If this exception is handled, the
CancelEventshould be reset to allow anotherInvocationCancelledErrorto be raised if the invocation receives a second cancellation signal.Initialize self. See help(type(self)) for accurate signature.
- labthings_fastapi.actions.blobdata_to_url_ctx
This context variable gives access to a function that makes BlobData objects downloadable, by assigning a URL and adding them to the [
BlobDataManager](#labthings_fastapi.outputs.blob.BlobDataManager).It is only available within a [
blob_serialisation_context_manager](#labthings_fastapi.outputs.blob.blob_serialisation_context_manager) because it requires access to theBlobDataManagerand theurl_forfunction from the FastAPI app.
- labthings_fastapi.actions.ACTION_INVOCATIONS_PATH = '/action_invocations'
The API route used to list
Invocationobjects.
- exception labthings_fastapi.actions.NoBlobManagerError
Bases:
RuntimeErrorRaised if an API route accesses Invocation outputs without a BlobIOContextDep.
Any access to an invocation output must have BlobIOContextDep as a dependency, as the output may be a blob, and the blob needs this context to resolve its URL.
Initialize self. See help(type(self)) for accurate signature.
- class labthings_fastapi.actions.Invocation(action: labthings_fastapi.descriptors.ActionDescriptor, thing: labthings_fastapi.thing.Thing, id: uuid.UUID, input: pydantic.BaseModel | None = None, dependencies: dict[str, Any] | None = None, log_len: int = 1000, cancel_hook: labthings_fastapi.dependencies.invocation.CancelHook | None = None)
Bases:
threading.ThreadA Thread subclass that retains output values and tracks progress.
Invocationthreads add several bits of functionality compared to the basethreading.Thread.They are instantiated with an
ActionDescriptorand aThingrather than a target function (see__init__).Each invocation is assigned a unique
IDto allow it to be polled over HTTP.A
CancelHookis 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.
- Parameters:
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
Thingit’s bound to when the function is run.thing – is the object on which we are running the
action, i.e. it is supplied to the function wrapped byactionas theselfargument.id – is a
uuid.UUIDused to identify the invocation, for example when polling its status via HTTP.input – is a
pydantic.BaseModelrepresenting the body of the HTTP request that invoked the action. It is supplied to the function as keyword arguments.dependencies – is a dictionary of keyword arguments, supplied by FastAPI by its dependency injection mechanism.
log_len – sets the number of log entries that will be held in memory by the invocation’s logger.
cancel_hook – is a
threading.Eventsubclass that tells the invocation it’s time to stop. SeeCancelHook.
- action_ref
- thing_ref
- input = None
- dependencies = None
- cancel_hook = None
- _ID
- retention_time
- expiry_time: datetime.datetime | None = None
- _status_lock
- _status: invocation_model.InvocationStatus
- _request_time: datetime.datetime
- _start_time: datetime.datetime | None = None
- _end_time: datetime.datetime | None = None
- _log: collections.deque
- property output: Any
Return value of the Action. If the Action is still running, returns None.
- Raises:
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.
- property log: list[invocation_model.LogRecordModel]
A list of log items generated by the Action.
- property status: invocation_model.InvocationStatus
Current running status of the thread.
See
InvocationStatusfor the values and their meanings.
- property action
The
ActionDescriptorobject running in this thread.
- property thing: labthings_fastapi.thing.Thing
The
Thingto which the action is bound, i.e. this isself.
- cancel() None
Cancel the task by requesting the code to stop.
This is an opt-in feature: the action must use a
CancelHookdependency and periodically check it.
- response(request: fastapi.Request | None = None) invocation_model.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.
- Parameters:
request – is used to generate the
hrefin the response, which should retrieve an updated version of this response.- Returns:
an
InvocationModelrepresenting thisInvocation.
- run() None
Run the action and track progress.
Invocationoverrides the defaultthreading.Thread.runmethod to add ways to track its progress and capture the return value.The code to be run is the function wrapped in the
ActionDescriptorthat is passed in asaction. Its arguments are the associatedThing(the first argument, i.e.self), theinputmodel (split into keyword arguments for each field), and anydependencies(also as keyword arguments).We update the status of the action by setting
self._statusand 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
Invocationis 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.statusfor status values.- Raises:
Exception – any exception raised in the action function will propagate through this method. Usually, this will just cause the thread to terminate after setting
statustoERRORand saving the exception toself._exception.
- class labthings_fastapi.actions.DequeLogHandler(dest: MutableSequence, level: int = logging.INFO)
Bases:
logging.HandlerA log handler that stores entries in memory.
Set up a log handler that appends messages to a deque.
Warning
This log handler does not currently rotate or truncate the list - so if you use it on a thread that produces a lot of log messages, you may run into memory problems.
Using a
dequewith a finite capacity helps to mitigate this.- Parameters:
dest – should specify a deque, to which we will append each log entry as it comes in. This is assumed to be thread safe.
level – sets the level of the logger. For most invocations, a log level of
logging.INFOis appropriate.
- dest
- emit(record: logging.LogRecord) None
Save a log record to the destination deque.
- Parameters:
record – the
logging.LogRecordobject to add.
- class labthings_fastapi.actions.ActionManager
A class to manage a collection of actions.
Set up an
ActionManager.- _invocations
- _invocations_lock
- property invocations: list[Invocation]
A list of all the
Invocationobjects running or recently completed.
- append_invocation(invocation: Invocation) None
Add an
Invocationto theActionManager.- Parameters:
invocation – The
Invocationto add.
- invoke_action(action: labthings_fastapi.descriptors.ActionDescriptor, thing: labthings_fastapi.thing.Thing, id: uuid.UUID, input: Any, dependencies: dict[str, Any], cancel_hook: labthings_fastapi.dependencies.invocation.CancelHook) Invocation
Invoke an action, returning the thread where it’s running.
See
Invocationfor more details.- Parameters:
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
Thingit’s bound to when the function is run.thing – is the object on which we are running the
action, i.e. it is supplied to the function wrapped byactionas theselfargument.id – is a
uuid.UUIDused to identify the invocation, for example when polling its status via HTTP.input – is a
pydantic.BaseModelrepresenting the body of the HTTP request that invoked the action. It is supplied to the function as keyword arguments.dependencies – is a dictionary of keyword arguments, supplied by FastAPI by its dependency injection mechanism.
cancel_hook – is a
threading.Eventsubclass that tells the invocation it’s time to stop. SeeCancelHook.
- Returns:
an
Invocationobject that has been started.
- get_invocation(id: uuid.UUID) Invocation
Retrieve an invocation by ID.
- Parameters:
id – the unique ID of the action to retrieve.
- Returns:
the
Invocationobject.
- list_invocations(action: labthings_fastapi.descriptors.ActionDescriptor | None = None, thing: labthings_fastapi.thing.Thing | None = None, request: fastapi.Request | None = None) list[invocation_model.InvocationModel]
All of the invocations currently managed.
Returns a list of
InvocationModelinstances representing all the invocations that are currently running, or have recently completed and not yet expired.- Parameters:
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 eitherThing.thing – returns only invocations of actions on a particular
Thing. This will often be combined with filtering byactionto give the list of invocations returned by a GET request on an action endpoint.request – is used to pass a
fastapi.Requestobject to theInvocation.responsemethod. Doing so ensures the URL returned ashrefin the response matches the address used to communicate with the server (i.e. it usesfastapi.Request.url_forinstead of a path generated from a string).
- Returns:
A list of invocations, optionally filtered by Thing and/or Action.
- expire_invocations()
Delete invocations that have passed their expiry time.
- attach_to_app(app: fastapi.FastAPI) None
Add /action_invocations and /action_invocation/{id} endpoints to FastAPI.
- Parameters:
app – The
fastapi.FastAPIapplication to which we add the endpoints.