labthings_fastapi.outputs.blob

Module Contents

Classes

BlobData

The interface for the data store of a Blob.

ServerSideBlobData

A BlobData protocol for server-side use, i.e. including response()

BlobBytes

A BlobOutput that holds its data in memory as a :class:bytes object

BlobFile

A BlobOutput that holds its data in a file

Blob

A container for binary data that may be retrieved over HTTP

BlobDataManager

A class to manage BlobData objects

Functions

blob_type

Create a BlobOutput subclass for a given media type

blob_serialisation_context_manager

Set context variables to allow blobs to be [de]serialised

Data

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.

url_to_blobdata_ctx

This context variable gives access to a function that makes BlobData objects from a URL, by retrieving them from the BlobDataManager.

BlobIOContextDep

A dependency that enables Blobs to be serialised and deserialised.

API

class labthings_fastapi.outputs.blob.BlobData

Bases: typing_extensions.Protocol

The interface for the data store of a Blob.

Blob objects can represent their data in various ways. Each of those options must provide three ways to access the data, which are the content property, the save() method, and the open() method.

This protocol defines the interface needed by any data store used by a Blob.

Objects that are used on the server will additionally need to implement the ServerSideBlobData protocol, which adds a response() method and id property.

property media_type: str

The MIME type of the data, e.g. ‘image/png’ or ‘application/json’

property content: bytes

The data as a bytes object

save(filename: str) None

Save the data to a file

open() io.IOBase

Return a file-like object that may be read from.

class labthings_fastapi.outputs.blob.ServerSideBlobData

Bases: labthings_fastapi.outputs.blob.BlobData, typing_extensions.Protocol

A BlobData protocol for server-side use, i.e. including response()

Blob objects returned by actions must use BlobData objects that can be downloaded. This protocol extends that protocol to include a response() method that returns a FastAPI response object.

See BlobBytes or BlobFile for concrete implementations.

id: Optional[uuid.UUID]

None

A unique identifier for this BlobData object.

The ID is set when the BlobData object is added to the BlobDataManager. It is used to retrieve the BlobData object from the manager.

response() fastapi.responses.Response

A :class:fastapi.Response object that sends binary data.

class labthings_fastapi.outputs.blob.BlobBytes(data: bytes, media_type: str)

A BlobOutput that holds its data in memory as a :class:bytes object

id: Optional[uuid.UUID]

None

__init__(data: bytes, media_type: str)
property content: bytes
save(filename: str) None
open() io.IOBase
response() fastapi.responses.Response
class labthings_fastapi.outputs.blob.BlobFile(file_path: str, media_type: str, **kwargs)

A BlobOutput that holds its data in a file

Only the filepath is retained by default. If you are using e.g. a temporary directory, you should add the temporary directory as a property, to stop it being garbage collected.

id: Optional[uuid.UUID]

None

__init__(file_path: str, media_type: str, **kwargs)
property content: bytes
save(filename: str) None
open() io.IOBase
response() fastapi.responses.Response
class labthings_fastapi.outputs.blob.Blob

Bases: pydantic.BaseModel

A container for binary data that may be retrieved over HTTP

See the documentation on blobs for more information on how to use this class.

A Blob may be created to hold data using the class methods from_bytes or from_temporary_directory. The constructor will attempt to deserialise a Blob from a URL (see __init__ method).

You are strongly advised to subclass this class and specify the media_type attribute, as this will propagate to the auto-generated documentation.

href: str

None

The URL where the data may be retrieved. This will be blob://local if the data is stored locally.

media_type: str

/

The MIME type of the data. This should be overridden in subclasses.

rel: Literal[output]

‘output’

description: str

‘The output from this action is not serialised to JSON, so it must be retrieved as a file. This link …’

_data: Optional[labthings_fastapi.outputs.blob.ServerSideBlobData]

None

This object holds the data, either in memory or as a file.

If _data is None, then the Blob has not been deserialised yet, and the href should point to a valid address where the data may be downloaded.

retrieve_data()

Retrieve the data from the URL

When a Blob is created using its constructor, pydantic will attempt to deserialise it by retrieving the data from the URL specified in href. Currently, this must be a URL pointing to a Blob that already exists on this server.

This validator will only work if the function to resolve URLs to BlobData objects has been set in the context variable url_to_blobdata_ctx. This is done when actions are being invoked over HTTP by the BlobIOContextDep dependency.

to_dict() Mapping[str, str]

Serialise the Blob to a dictionary and make it downloadable

When pydantic serialises this object, it will call this method to convert it to a dictionary. There is a significant side-effect, which is that we will add the blob to the BlobDataManager so it can be downloaded.

This serialiser will only work if the function to assign URLs to BlobData objects has been set in the context variable blobdata_to_url_ctx. This is done when actions are being returned over HTTP by the BlobIOContextDep dependency.

classmethod default_media_type() str

The default media type.

Blob should generally be subclassed to define the default media type, as this forms part of the auto-generated documentation. Using the Blob class directly will result in a media type of */*, which makes it unclear what format the output is in.

property data: labthings_fastapi.outputs.blob.ServerSideBlobData

The data store for this Blob

Blob objects may hold their data in various ways, defined by the ServerSideBlobData protocol. This property returns the data store for this Blob.

If the Blob has not yet been downloaded, there may be no data held locally, in which case this function will raise a ValueError.

It is recommended to use the content property or save() or open() methods rather than accessing this property directly. Those methods will download data if required, rather than raising an error.

property content: bytes

Return the the output as a bytes object

This property may return the bytes object, or if we have a file it will read the file and return the contents. Client objects may use this property to download the output.

This property is read-only. You should also only read it once, as no guarantees are given about cacheing - reading it many times risks reading the file from disk many times, or re-downloading an artifact.

save(filepath: str) None

Save the output to a file.

This may remove the need to hold the output in memory.

open() io.IOBase

Open the output as a binary file-like object.

classmethod from_bytes(data: bytes) typing_extensions.Self

Create a BlobOutput from a bytes object

classmethod from_temporary_directory(folder: tempfile.TemporaryDirectory, file: str) typing_extensions.Self

Create a BlobOutput from a file in a temporary directory

The TemporaryDirectory object will persist as long as this BlobOutput does, which will prevent it from being cleaned up until the object is garbage collected.

classmethod from_file(file: str) typing_extensions.Self

Create a BlobOutput from a regular file

The file should exist for at least as long as the BlobOutput does; this is assumed to be the case and nothing is done to ensure it’s not temporary. If you are using temporary files, consider creating your Blob with from_temporary_directory instead.

response()

“Return a suitable response for serving the output

labthings_fastapi.outputs.blob.blob_type(media_type: str) type[labthings_fastapi.outputs.blob.Blob]

Create a BlobOutput subclass for a given media type

This convenience function may confuse static type checkers, so it is usually clearer to make a subclass instead, e.g.:

class MyImageBlob(Blob):
    media_type = "image/png"
class labthings_fastapi.outputs.blob.BlobDataManager

A class to manage BlobData objects

The BlobManager is responsible for serving Blob objects to clients. It holds weak references: it will not retain Blobs that are no longer in use. Most Blobs will be retained by the output of an action: this holds a strong reference, and will be expired by the ActionManager.

_blobs: weakref.WeakValueDictionary[uuid.UUID, labthings_fastapi.outputs.blob.ServerSideBlobData]

None

__init__()
add_blob(blob: labthings_fastapi.outputs.blob.ServerSideBlobData) uuid.UUID

Add a BlobOutput to the manager, generating a unique ID

get_blob(blob_id: uuid.UUID) labthings_fastapi.outputs.blob.ServerSideBlobData

Retrieve a BlobOutput from the manager

download_blob(blob_id: uuid.UUID)

Download a BlobOutput

attach_to_app(app: fastapi.FastAPI)

Attach the BlobDataManager to a FastAPI app

labthings_fastapi.outputs.blob.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.

It is only available within a blob_serialisation_context_manager because it requires access to the BlobDataManager and the url_for function from the FastAPI app.

labthings_fastapi.outputs.blob.url_to_blobdata_ctx

‘(…)’

This context variable gives access to a function that makes BlobData objects from a URL, by retrieving them from the BlobDataManager.

It is only available within a blob_serialisation_context_manager because it requires access to the BlobDataManager.

async labthings_fastapi.outputs.blob.blob_serialisation_context_manager(request: fastapi.Request)

Set context variables to allow blobs to be [de]serialised

labthings_fastapi.outputs.blob.BlobIOContextDep: typing_extensions.TypeAlias

None

A dependency that enables Blobs to be serialised and deserialised.

BLOB Output Module

The BlobOutput class is used when you need to return something file-like that can’t easily (or efficiently) be converted to JSON. This is useful for returning large objects like images, especially where an existing file-type is the obvious way to handle it.

There is a dedicated documentation page on blobs that explains how to use this mechanism.

To return a file from an action, you should declare its return type as a BlobOutput subclass, defining the media_type attribute.

class MyImageBlob(Blob):
    media_type = "image/png"

class MyThing(Thing):
    @thing_action
    def get_image(self) -> MyImageBlob:
        # Do something to get the image data
        data = self._get_image_data()
        return MyImageBlob.from_bytes(data)

The action should then return an instance of that subclass, with data supplied either as a bytes object or a file on disk. If files are used, it’s your responsibility to ensure the file is deleted after the Blob object is garbage-collected. Constructing it using the class methods from_bytes or from_temporary_directory will ensure this is done for you.

Bear in mind a tempfile object only holds a file descriptor and is not safe for concurrent use, which does not work well with the HTTP API: action outputs may be retrieved multiple times after the action has completed, possibly concurrently. Creating a temp folder and making a file inside it with from_temporary_directory is the safest way to deal with this.