Skip to content

Commit 8b44a93

Browse files
committed
Improved docstrings on blob
1 parent e2c6865 commit 8b44a93

File tree

1 file changed

+72
-14
lines changed
  • src/labthings_fastapi/outputs

1 file changed

+72
-14
lines changed

src/labthings_fastapi/outputs/blob.py

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
To return a file from an action, you should declare its return type as a BlobOutput
88
subclass, 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
1111
either as a `bytes` object or a file on disk. If files are used, it's your
1212
responsibility to ensure the file is deleted after the `BlobOutput` object is
1313
garbage-collected. Constructing it using the class methods `from_bytes` or
@@ -52,31 +52,54 @@
5252

5353
@runtime_checkable
5454
class 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

7081
class 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

78101
class 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

134157
class 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

Comments
 (0)