77To return a file from an action, you should declare its return type as a BlobOutput
88subclass, defining the `media_type` attribute.
99
10- The output from the class should be an instance of that subclass, with data supplied
10+ The action should then return an instance of that subclass, with data supplied
1111either as a `bytes` object or a file on disk. If files are used, it's your
1212responsibility to ensure the file is deleted after the `BlobOutput` object is
1313garbage-collected. Constructing it using the class methods `from_bytes` or
5252
5353@runtime_checkable
5454class BlobData (Protocol ):
55- """A Protocol for a BlobOutput object"""
55+ """The interface for the data store of a Blob.
56+
57+ :class:`.Blob` objects can represent their data in various ways. Each of
58+ those options must provide three ways to access the data, which are the
59+ `content` property, the `save()` method, and the `open()` method.
60+ """
5661
5762 @property
5863 def media_type (self ) -> str :
64+ """The MIME type of the data, e.g. 'image/png' or 'application/json'"""
5965 pass
6066
6167 @property
6268 def content (self ) -> bytes :
69+ """The data as a `bytes` object"""
6370 pass
6471
65- def save (self , filename : str ) -> None : ...
72+ def save (self , filename : str ) -> None :
73+ """Save the data to a file"""
74+ ...
6675
67- def open (self ) -> io .IOBase : ...
76+ def open (self ) -> io .IOBase :
77+ """Return a file-like object that may be read from."""
78+ ...
6879
6980
7081class ServerSideBlobData (BlobData , Protocol ):
71- """A BlobOutput protocol for server-side use, i.e. including `response()`"""
82+ """A BlobData protocol for server-side use, i.e. including `response()`
83+
84+ :class:`Blob` objects returned by actions must use :class:`.BlobData` objects
85+ that can be downloaded. This protocol extends the :class:`.BlobData` protocol to
86+ include a :meth:`~.ServerSideBlobData.response()` method that returns a FastAPI response object.
87+ """
7288
7389 id : Optional [uuid .UUID ] = None
90+ """A unique identifier for this BlobData object.
91+
92+ The ID is set when the BlobData object is added to the BlobDataManager.
93+ It is used to retrieve the BlobData object from the manager.
94+ """
7495
75- def response (self ) -> Response : ...
96+ def response (self ) -> Response :
97+ """A :class:`fastapi.Response` object that sends binary data."""
98+ ...
7699
77100
78101class BlobBytes :
79- """A BlobOutput that holds its data in memory as a `bytes` object"""
102+ """A BlobOutput that holds its data in memory as a :class: `bytes` object"""
80103
81104 id : Optional [uuid .UUID ] = None
82105
@@ -132,27 +155,49 @@ def response(self) -> Response:
132155
133156
134157class Blob (BaseModel ):
135- """An output from LabThings best returned as binary data, not JSON
136-
137- This may be instantiated either using the class methods `from_bytes` or
138- `from_temporary_directory`, which will use a `bytes` object to store the
139- output, or return a file on disk in a temporary directory. In the latter
140- case, the temporary directory will be deleted when the object is garbage
141- collected.
158+ """A container for binary data that may be retrieved over HTTP
159+
160+ See :doc:`blobs` for more information on how to use this class.
161+
162+ A :class:`.Blob` may be created to hold data using the class methods
163+ `from_bytes` or `from_temporary_directory`. The constructor will
164+ attempt to deserialise a Blob from a URL, and may only be used within
165+ a `blob_serialisation_context_manager`. This is made available when
166+ actions are invoked, or when their output is returned.
167+
168+ You are strongly advised to subclass this class and specify the
169+ `media_type` attribute, as this will propagate to the auto-generated
170+ documentation.
142171 """
143172
144173 href : str
174+ """The URL where the data may be retrieved. This will be `blob://local`
175+ if the data is stored locally."""
145176 media_type : str = "*/*"
177+ """The MIME type of the data. This should be overridden in subclasses."""
146178 rel : Literal ["output" ] = "output"
147179 description : str = (
148180 "The output from this action is not serialised to JSON, so it must be "
149181 "retrieved as a file. This link will return the file."
150182 )
151183
152184 _data : Optional [ServerSideBlobData ] = None
185+ """This object holds the data, either in memory or as a file."""
153186
154187 @model_validator (mode = "after" )
155188 def retrieve_data (self ):
189+ """Retrieve the data from the URL
190+
191+ When a :class:`.Blob` is created using its constructor, :mod:`pydantic`
192+ will attempt to deserialise it by retrieving the data from the URL
193+ specified in `href`. Currently, this must be a URL pointing to a
194+ :class:`.Blob` that already exists on this server.
195+
196+ This validator will only work if the function to resolve URLs to
197+ :class:`.BlobData` objects has been set in the context variable. This
198+ is done when actions are being invoked over HTTP, or when
199+ their outputs are being returned.
200+ """
156201 if self .href == "blob://local" :
157202 if self ._data :
158203 return self
@@ -170,6 +215,19 @@ def retrieve_data(self):
170215
171216 @model_serializer (mode = "plain" , when_used = "always" )
172217 def to_dict (self ) -> Mapping [str , str ]:
218+ """Serialise the Blob to a dictionary and make it downloadable
219+
220+ When :mod:`pydantic` serialises this object, it will call this method
221+ to convert it to a dictionary. There is a significant side-effect, which
222+ is that we will add the blob to the :class:`.BlobDataManager` so it
223+ can be downloaded.
224+
225+ This serialiser will only work if the function to resolve URLs to
226+ :class:`.BlobData` objects has been set in the context variable. This
227+ is done when the outputs of actions are being returned.
228+
229+ Note that the
230+ """
173231 if self .href == "blob://local" :
174232 try :
175233 blobdata_to_url = blobdata_to_url_ctx .get ()
0 commit comments