Skip to content

Commit 635d9a7

Browse files
Add unit test that fails without the fix in previous commit
1 parent bc7a404 commit 635d9a7

2 files changed

Lines changed: 42 additions & 1 deletion

File tree

src/labthings_fastapi/actions/__init__.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
InvocationCancelledError,
2020
invocation_logger,
2121
)
22-
from ..outputs.blob import BlobIOContextDep
22+
from ..outputs.blob import BlobIOContextDep, blobdata_to_url_ctx
2323

2424
if TYPE_CHECKING:
2525
# We only need these imports for type hints, so this avoids circular imports.
@@ -29,6 +29,14 @@
2929
ACTION_INVOCATIONS_PATH = "/action_invocations"
3030

3131

32+
class NoBlobManagerError(RuntimeError):
33+
"""Raised if an API route accesses Invocation outputs without a BlobIOContextDep.
34+
35+
Any access to an invocation output must have BlobIOContextDep as a dependency, as
36+
the output may be a blob, and the blob needs this context to resolve its URL.
37+
"""
38+
39+
3240
class Invocation(Thread):
3341
"""A Thread subclass that retains output values and tracks progress
3442
@@ -89,6 +97,15 @@ def output(self) -> Any:
8997
"""
9098
Return value of the Action. If the Action is still running, returns None.
9199
"""
100+
try:
101+
blobdata_to_url_ctx.get()
102+
except LookupError as e:
103+
raise NoBlobManagerError(
104+
"An invocation output has been requested from a api route that "
105+
"doesn't have a BlobIOContextDep dependency. This dependency is needed "
106+
" for blobs to identify their url."
107+
) from e
108+
92109
with self._status_lock:
93110
return self._return_value
94111

tests/test_actions.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,30 @@ def run(payload=None):
1818
return run
1919

2020

21+
def test_get_action_invocations():
22+
"""Test that running "get" on an action returns a list of invocations."""
23+
with TestClient(server.app) as client:
24+
# When we start the action has no invocations
25+
invocations_before = client.get("/thing/increment_counter").json()
26+
assert invocations_before == []
27+
# Start the action
28+
r = client.post("/thing/increment_counter")
29+
assert r.status_code in (200, 201)
30+
# Now it is started, there is a list of 1 dictionary containing the
31+
# invocation information.
32+
invocations_after = client.get("/thing/increment_counter").json()
33+
assert len(invocations_after) == 1
34+
assert isinstance(invocations_after, list)
35+
assert isinstance(invocations_after[0], dict)
36+
assert "status" in invocations_after[0]
37+
assert "id" in invocations_after[0]
38+
assert "action" in invocations_after[0]
39+
assert "href" in invocations_after[0]
40+
assert "timeStarted" in invocations_after[0]
41+
# Let the task finish before ending the test
42+
poll_task(client, r.json())
43+
44+
2145
def test_counter():
2246
with TestClient(server.app) as client:
2347
before_value = client.get("/thing/counter").json()

0 commit comments

Comments
 (0)