Skip to content

Commit 7957450

Browse files
authored
Merge pull request #130 from labthings/retire-file-manager
Remove the file_manager module
2 parents 667ad88 + 59c8fe0 commit 7957450

File tree

8 files changed

+46
-225
lines changed

8 files changed

+46
-225
lines changed

.coveragerc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,8 @@ concurrency = multiprocessing, thread
33
parallel = true
44
sigterm = true
55
omit = tests/**/*.py, docs/**/*.py
6+
7+
[report]
8+
exclude_lines =
9+
pragma: no cover
10+
if TYPE_CHECKING:

src/labthings_fastapi/actions/__init__.py

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@
88
from typing import TYPE_CHECKING
99
import weakref
1010
from fastapi import FastAPI, HTTPException, Request
11-
from fastapi.responses import FileResponse
1211
from pydantic import BaseModel
1312

1413
from ..utilities import model_to_dict
1514
from ..utilities.introspection import EmptyInput
1615
from ..thing_description.model import LinkElement
17-
from ..file_manager import FileManager
1816
from .invocation_model import InvocationModel, InvocationStatus
1917
from ..dependencies.invocation import (
2018
CancelHook,
@@ -69,10 +67,6 @@ def __init__(
6967
self.retention_time = action.retention_time
7068
self.expiry_time: Optional[datetime.datetime] = None
7169

72-
# This is added post-hoc by the FastAPI endpoint, in
73-
# `ActionDescriptor.add_to_fastapi`
74-
self._file_manager: Optional[FileManager] = None
75-
7670
# Private state properties
7771
self._status_lock = Lock() # This Lock protects properties below
7872
self._status: InvocationStatus = InvocationStatus.PENDING # Task status
@@ -148,8 +142,6 @@ def response(self, request: Optional[Request] = None):
148142
LinkElement(rel="self", href=href),
149143
LinkElement(rel="output", href=href + "/output"),
150144
]
151-
if self._file_manager:
152-
links += self._file_manager.links(href)
153145
return self.action.invocation_model(
154146
status=self.status,
155147
id=self.id,
@@ -413,50 +405,3 @@ def delete_invocation(id: uuid.UUID) -> None:
413405
),
414406
)
415407
invocation.cancel()
416-
417-
@app.get(
418-
ACTION_INVOCATIONS_PATH + "/{id}/files",
419-
responses={
420-
404: {"description": "Invocation ID not found"},
421-
503: {"description": "No files are available for this invocation"},
422-
},
423-
)
424-
def action_invocation_files(id: uuid.UUID) -> list[str]:
425-
with self._invocations_lock:
426-
try:
427-
invocation: Any = self._invocations[id]
428-
except KeyError:
429-
raise HTTPException(
430-
status_code=404,
431-
detail="No action invocation found with ID {id}",
432-
)
433-
if not invocation._file_manager:
434-
raise HTTPException(
435-
status_code=503,
436-
detail="No files are available for this invocation",
437-
)
438-
return invocation._file_manager.filenames
439-
440-
@app.get(
441-
ACTION_INVOCATIONS_PATH + "/{id}/files/{filename}",
442-
response_class=FileResponse,
443-
responses={
444-
404: {"description": "Invocation ID not found, or file not found"},
445-
503: {"description": "No files are available for this invocation"},
446-
},
447-
)
448-
def action_invocation_file(id: uuid.UUID, filename: str):
449-
with self._invocations_lock:
450-
try:
451-
invocation: Any = self._invocations[id]
452-
except KeyError:
453-
raise HTTPException(
454-
status_code=404,
455-
detail="No action invocation found with ID {id}",
456-
)
457-
if not invocation._file_manager:
458-
raise HTTPException(
459-
status_code=503,
460-
detail="No files are available for this invocation",
461-
)
462-
return FileResponse(invocation._file_manager.path(filename))

src/labthings_fastapi/descriptors/action.py

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -186,22 +186,16 @@ def start_action(
186186
background_tasks: BackgroundTasks,
187187
**dependencies,
188188
):
189-
try:
190-
action = action_manager.invoke_action(
191-
action=self,
192-
thing=thing,
193-
input=body,
194-
dependencies=dependencies,
195-
id=id,
196-
cancel_hook=cancel_hook,
197-
)
198-
background_tasks.add_task(action_manager.expire_invocations)
199-
return action.response(request=request)
200-
finally:
201-
try:
202-
action._file_manager = request.state.file_manager
203-
except AttributeError:
204-
pass # This probably means there was no FileManager created.
189+
action = action_manager.invoke_action(
190+
action=self,
191+
thing=thing,
192+
input=body,
193+
dependencies=dependencies,
194+
id=id,
195+
cancel_hook=cancel_hook,
196+
)
197+
background_tasks.add_task(action_manager.expire_invocations)
198+
return action.response(request=request)
205199

206200
if issubclass(self.input_model, EmptyInput):
207201
annotation = Body(default_factory=StrictEmptyInput)

src/labthings_fastapi/file_manager.py

Lines changed: 0 additions & 72 deletions
This file was deleted.

tests/test_action_manager.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from temp_client import poll_task
55
import time
66
import labthings_fastapi as lt
7+
from labthings_fastapi.actions import ACTION_INVOCATIONS_PATH
78

89

910
class TestThing(lt.Thing):
@@ -22,16 +23,20 @@ def increment_counter(self):
2223
server.add_thing(thing, "/thing")
2324

2425

25-
def action_partial(client: TestClient, url: str):
26-
def run(payload=None):
27-
r = client.post(url, json=payload)
28-
assert r.status_code in (200, 201)
29-
return poll_task(client, r.json())
26+
def test_action_expires():
27+
"""Check the action is removed from the server
3028
31-
return run
29+
We've set the retention period to be very short, so the action
30+
should not be retrievable after some time has elapsed.
3231
32+
This test checks that actions do indeed get removed.
3333
34-
def test_expiry():
34+
Note that the code that expires actions runs whenever a new
35+
action is started. That's why we need to invoke the action twice:
36+
the second invocation runs the code that deletes the first one.
37+
This behaviour might change in the future, making the second run
38+
unnecessary.
39+
"""
3540
with TestClient(server.app) as client:
3641
before_value = client.get("/thing/counter").json()
3742
r = client.post("/thing/increment_counter")
@@ -42,5 +47,24 @@ def test_expiry():
4247
after_value = client.get("/thing/counter").json()
4348
assert after_value == before_value + 2
4449
invocation["status"] = "running" # Force an extra poll
50+
# When the second action runs, the first one should expire
51+
# so polling it again should give a 404.
4552
with pytest.raises(httpx.HTTPStatusError):
4653
poll_task(client, invocation)
54+
55+
56+
def test_actions_list():
57+
"""Check that the /action_invocations/ path works.
58+
59+
The /action_invocations/ path should return a list of invocation
60+
objects (a representation of each action that's been run recently).
61+
62+
It's implemented in `ActionManager.list_all_invocations`.
63+
"""
64+
with TestClient(server.app) as client:
65+
r = client.post("/thing/increment_counter")
66+
invocation = poll_task(client, r.json())
67+
r2 = client.get(ACTION_INVOCATIONS_PATH)
68+
r2.raise_for_status()
69+
invocations = r2.json()
70+
assert invocations == [invocation]

tests/test_dependencies.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from fastapi import Depends, FastAPI, Request
88
from labthings_fastapi.deps import InvocationID
9-
from labthings_fastapi.file_manager import FileManagerDep
109
from fastapi.testclient import TestClient
1110
from module_with_deps import FancyIDDep
1211

@@ -55,17 +54,3 @@ def endpoint(id: DepClass = Depends()) -> bool:
5554
assert r.status_code == 200
5655
invocation = r.json()
5756
assert invocation is True
58-
59-
60-
def test_file_manager():
61-
app = FastAPI()
62-
63-
@app.post("/invoke_with_file")
64-
def invoke_with_file(
65-
file_manager: FileManagerDep,
66-
) -> dict[str, str]:
67-
return {"directory": str(file_manager.directory)}
68-
69-
with TestClient(app) as client:
70-
r = client.post("/invoke_with_file")
71-
assert r.status_code == 200

tests/test_dependencies_2.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from fastapi.testclient import TestClient
2323
from module_with_deps import FancyIDDep, FancyID, ClassDependsOnFancyID
2424
import labthings_fastapi as lt
25-
from labthings_fastapi.file_manager import FileManager
2625

2726

2827
def test_dep_from_module():
@@ -147,17 +146,3 @@ def endpoint(id: lt.deps.InvocationID) -> bool:
147146
with TestClient(app) as client:
148147
r = client.post("/endpoint")
149148
assert r.status_code == 200
150-
151-
152-
def test_filemanager_dep():
153-
"""Test out our FileManager class as a dependency"""
154-
app = FastAPI()
155-
156-
@app.post("/endpoint")
157-
def endpoint(fm: Annotated[FileManager, Depends()]) -> str:
158-
return f"Saving to {fm.directory}"
159-
160-
with TestClient(app) as client:
161-
r = client.post("/endpoint")
162-
assert r.status_code == 200
163-
assert r.json().startswith("Saving to ")

tests/test_temp_files.py

Lines changed: 0 additions & 45 deletions
This file was deleted.

0 commit comments

Comments
 (0)