-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
It is now possible to gracefully cancel actions by DELETEing the invocation. This relies on the action in question using a CancelHook dependency, and periodically checking it. I've refactored slightly, combining three invocation-related dependencies into one submodule.
- Loading branch information
Showing
10 changed files
with
148 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
"""FastAPI dependency for an invocation ID""" | ||
from __future__ import annotations | ||
import uuid | ||
from typing import Annotated | ||
from fastapi import Depends | ||
import logging | ||
import threading | ||
|
||
|
||
def invocation_id() -> uuid.UUID: | ||
"""Return a UUID for an action invocation | ||
This is for use as a FastAPI dependency, to allow other dependencies to | ||
access the invocation ID. Useful for e.g. file management. | ||
""" | ||
return uuid.uuid4() | ||
|
||
|
||
InvocationID = Annotated[uuid.UUID, Depends(invocation_id)] | ||
|
||
|
||
def invocation_logger(id: InvocationID) -> logging.Logger: | ||
"""Retrieve a logger object for an action invocation""" | ||
return logging.getLogger(f"labthings_fastapi.actions.{id}") | ||
|
||
|
||
InvocationLogger = Annotated[logging.Logger, Depends(invocation_logger)] | ||
|
||
|
||
class InvocationCancelledError(SystemExit): | ||
pass | ||
|
||
|
||
class CancelEvent(threading.Event): | ||
def __init__(self, id: InvocationID): | ||
threading.Event.__init__(self) | ||
self.invocation_id = id | ||
|
||
def raise_if_set(self): | ||
"""Raise a CancelledError if the event is set""" | ||
if self.is_set(): | ||
raise InvocationCancelledError("The action was cancelled.") | ||
|
||
def sleep(self, timeout: float): | ||
"""Sleep for a given time in seconds, but raise an exception if cancelled""" | ||
if self.wait(timeout): | ||
raise InvocationCancelledError("The action was cancelled.") | ||
|
||
|
||
def invocation_cancel_hook(id: InvocationID) -> CancelHook: | ||
"""Get a cancel hook belonging to a particular invocation""" | ||
return CancelEvent(id) | ||
|
||
|
||
CancelHook = Annotated[CancelEvent, Depends(invocation_cancel_hook)] |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
""" | ||
This tests the log that is returned in an action invocation | ||
""" | ||
from fastapi.testclient import TestClient | ||
from labthings_fastapi.thing_server import ThingServer | ||
from temp_client import poll_task, task_href | ||
from labthings_fastapi.thing import Thing | ||
from labthings_fastapi.decorators import thing_action | ||
from labthings_fastapi.descriptors import PropertyDescriptor | ||
from labthings_fastapi.dependencies.invocation import CancelHook | ||
|
||
|
||
class ThingOne(Thing): | ||
counter = PropertyDescriptor(int, 0) | ||
|
||
@thing_action | ||
def count_slowly(self, cancel: CancelHook): | ||
for i in range(10): | ||
cancel.sleep(0.1) | ||
self.counter += 1 | ||
|
||
|
||
def test_invocation_logging(): | ||
server = ThingServer() | ||
thing_one = ThingOne() | ||
server.add_thing(thing_one, "/thing_one") | ||
with TestClient(server.app) as client: | ||
r = client.post("/thing_one/count_slowly") | ||
r.raise_for_status() | ||
dr = client.delete(task_href(r.json())) | ||
invocation = poll_task(client, r.json()) | ||
assert invocation["status"] == "cancelled" | ||
assert thing_one.counter < 9 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters