Skip to content

Commit c4fb19a

Browse files
committed
Add initial model uploads
1 parent 63a7044 commit c4fb19a

File tree

3 files changed

+117
-0
lines changed

3 files changed

+117
-0
lines changed

src/pato/crud/sessions.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import pathlib
23
import shutil
34
from datetime import datetime
@@ -18,6 +19,7 @@
1819
from ..utils.generic import ProposalReference, check_session_active, parse_proposal
1920

2021
HDF5_FILE_SIGNATURE = b"\x89\x48\x44\x46\x0d\x0a\x1a\x0a"
22+
MRC_FILE_SIGNATURE = b"\x4d\x41\x50"
2123

2224

2325
def _validate_session_active(proposal_reference: ProposalReference):
@@ -263,3 +265,43 @@ def upload_processing_model(file: UploadFile, proposal_reference: ProposalRefere
263265
)
264266

265267
file.file.close()
268+
269+
270+
def upload_initial_model(file: UploadFile, proposal_reference: ProposalReference):
271+
file.file.seek(208)
272+
file_header = file.file.read(3)
273+
file.file.seek(0)
274+
275+
if file_header != MRC_FILE_SIGNATURE:
276+
raise HTTPException(
277+
detail="Invalid file type (must be MRC file)",
278+
status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
279+
)
280+
281+
file_path = f"{_get_folder_and_visit(proposal_reference)[0]}/processing"
282+
283+
if not os.path.isdir(file_path):
284+
app_logger.error(
285+
f"Failed to upload {file.filename}: visit directory '{file_path}' does not exist"
286+
)
287+
raise HTTPException(
288+
detail="Failed to upload file",
289+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
290+
)
291+
292+
file_path = os.path.join(file_path, "initial_model")
293+
# Create initial_model folder if it does not yet exist
294+
os.makedirs(file_path, exist_ok=True)
295+
296+
try:
297+
with open(os.path.join(file_path, file.filename or "model.mrc"), "wb") as f:
298+
shutil.copyfileobj(file.file, f)
299+
except OSError as e:
300+
file.file.close()
301+
app_logger.error(f"Failed to upload {file.filename}: {e}")
302+
raise HTTPException(
303+
detail="Failed to upload file",
304+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
305+
)
306+
307+
file.file.close()

src/pato/routes/proposals.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
prefix="/proposals",
2121
)
2222

23+
2324
@router.get(
2425
"/{proposalReference}/sessions/{visitNumber}/dataGroups",
2526
response_model=Paged[DataCollectionGroupSummaryResponse],
@@ -93,3 +94,13 @@ def upload_processing_model(
9394
return sessions_crud.upload_processing_model(
9495
file=file, proposal_reference=proposalReference
9596
)
97+
98+
99+
@router.post("/{proposalReference}/sessions/{visitNumber}/initialModel")
100+
def upload_initial_model(
101+
file: UploadFile, proposalReference=Depends(Permissions.session)
102+
):
103+
"""Upload custom initial model"""
104+
return sessions_crud.upload_initial_model(
105+
file=file, proposal_reference=proposalReference
106+
)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from datetime import datetime
2+
from unittest.mock import mock_open, patch
3+
4+
from lims_utils.tables import BLSession
5+
6+
VALID_FILE = b"\x00" * 208 + b"\x4d\x41\x50"
7+
8+
9+
def active_mock(_):
10+
return BLSession(
11+
startDate=datetime(year=2022, month=1, day=1),
12+
sessionId=27464088,
13+
beamLineName="m12",
14+
)
15+
16+
17+
@patch("pato.crud.sessions._validate_session_active", new=active_mock)
18+
@patch("builtins.open", new_callable=mock_open())
19+
@patch("pato.crud.sessions.os.path.isdir", new=lambda _: True)
20+
def test_post(_, mock_permissions, client):
21+
"""Should write file successfully if file matches expected signature"""
22+
resp = client.post(
23+
"/proposals/cm31111/sessions/5/initialModel",
24+
files={"file": ("mrc.mrc", VALID_FILE, "application/octet-stream")},
25+
)
26+
27+
assert resp.status_code == 200
28+
29+
30+
@patch("pato.crud.sessions._validate_session_active", new=active_mock)
31+
@patch("builtins.open", new_callable=mock_open())
32+
def test_invalid_file_signature(_, mock_permissions, client):
33+
"""Should raise exception if file signature doesn't match MRC file signature"""
34+
resp = client.post(
35+
"/proposals/cm31111/sessions/5/initialModel",
36+
files={"file": ("not-mrc.mrc", b"\x01\x02", "application/octet-stream")},
37+
)
38+
39+
assert resp.status_code == 415
40+
41+
42+
@patch("pato.crud.sessions._validate_session_active", new=active_mock)
43+
@patch("builtins.open", side_effect=OSError("Write Error"))
44+
def test_write_error(_, mock_permissions, client):
45+
"""Should return 500 if there was an error writing the file"""
46+
resp = client.post(
47+
"/proposals/cm31111/sessions/5/initialModel",
48+
files={"file": ("mrc.mrc", VALID_FILE, "application/octet-stream")},
49+
)
50+
51+
assert resp.status_code == 500
52+
53+
54+
@patch("pato.crud.sessions._validate_session_active", new=active_mock)
55+
@patch("builtins.open", side_effect=OSError("Write Error"))
56+
@patch("pato.crud.sessions.os.path.isdir", new=lambda _: False)
57+
def test_dir_does_not_exist(_, mock_permissions, client):
58+
"""Should return 500 if directory does not exist"""
59+
resp = client.post(
60+
"/proposals/cm31111/sessions/5/initialModel",
61+
files={"file": ("mrc.mrc", VALID_FILE, "application/octet-stream")},
62+
)
63+
64+
assert resp.status_code == 500

0 commit comments

Comments
 (0)