Skip to content

Commit 4dfe02e

Browse files
authored
Added endpoints to check status of multigrid controllers (#636)
* Added backend server endpoint to check whether a multigrid controller exists * Added instrument server endpoint to check whether a multigrid controller exists * Updated route manifest * Added tests for new endpoints * Optimised existing tests for instrument server endpoints
1 parent 7ea973e commit 4dfe02e

File tree

5 files changed

+267
-57
lines changed

5 files changed

+267
-57
lines changed

src/murfey/instrument_server/api.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,15 @@ def stop_multigrid_watcher(session_id: MurfeySessionID, label: str):
215215
return {"success": True}
216216

217217

218+
@router.get("/sessions/{session_id}/multigrid_controller/status")
219+
def check_multigrid_controller_exists(
220+
session_id: MurfeySessionID,
221+
):
222+
if controllers.get(session_id, None) is not None:
223+
return {"exists": True}
224+
return {"exists": False}
225+
226+
218227
@router.post("/sessions/{session_id}/multigrid_controller/visit_end_time")
219228
def update_multigrid_controller_visit_end_time(
220229
session_id: MurfeySessionID, end_time: datetime

src/murfey/server/api/instrument.py

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import datetime
55
import logging
66
from pathlib import Path
7-
from typing import Annotated, List, Optional
7+
from typing import Annotated, Any, List, Optional
88
from urllib.parse import quote
99

1010
import aiohttp
@@ -101,6 +101,31 @@ async def check_if_session_is_active(
101101
return {"active": response.status == 200}
102102

103103

104+
@router.get("/sessions/{session_id}/multigrid_controller/status")
105+
async def check_multigrid_controller_exists(session_id: MurfeySessionID, db=murfey_db):
106+
session = db.exec(select(Session).where(Session.id == session_id)).one()
107+
instrument_name = session.instrument_name
108+
machine_config = get_machine_config(instrument_name=instrument_name)[
109+
instrument_name
110+
]
111+
if machine_config.instrument_server_url:
112+
log.debug(
113+
f"Submitting request to inspect multigrid controller for session {session_id}"
114+
)
115+
async with aiohttp.ClientSession() as clientsession:
116+
async with clientsession.get(
117+
f"{machine_config.instrument_server_url}{url_path_for('api.router', 'check_multigrid_controller_exists', session_id=session_id)}",
118+
headers={
119+
"Authorization": f"Bearer {instrument_server_tokens[session_id]['access_token']}"
120+
},
121+
) as resp:
122+
data: dict[str, Any] = await resp.json()
123+
else:
124+
data = {"detail": "No instrument server URL found"}
125+
log.debug(f"Received response: {data}")
126+
return data
127+
128+
104129
@router.post("/sessions/{session_id}/multigrid_watcher")
105130
async def setup_multigrid_watcher(
106131
session_id: MurfeySessionID, watcher_spec: MultigridWatcherSetup, db=murfey_db
@@ -165,6 +190,36 @@ async def start_multigrid_watcher(session_id: MurfeySessionID, db=murfey_db):
165190
return data
166191

167192

193+
@router.post("/sessions/{session_id}/multigrid_controller/visit_end_time")
194+
async def update_visit_end_time(
195+
session_id: MurfeySessionID, end_time: datetime.datetime, db=murfey_db
196+
):
197+
# Load data for session
198+
session_entry = db.exec(select(Session).where(Session.id == session_id)).one()
199+
instrument_name = session_entry.instrument_name
200+
201+
# Update visit end time in database
202+
session_entry.visit_end_time = end_time
203+
db.add(session_entry)
204+
db.commit()
205+
206+
# Update the multigrid controller
207+
data = {}
208+
machine_config = get_machine_config(instrument_name=instrument_name)[
209+
instrument_name
210+
]
211+
if machine_config.instrument_server_url:
212+
async with aiohttp.ClientSession() as clientsession:
213+
async with clientsession.post(
214+
f"{machine_config.instrument_server_url}{url_path_for('api.router', 'update_multigrid_controller_visit_end_time', session_id=session_id)}?end_time={quote(end_time.isoformat())}",
215+
headers={
216+
"Authorization": f"Bearer {instrument_server_tokens[session_id]['access_token']}"
217+
},
218+
) as resp:
219+
data = await resp.json()
220+
return data
221+
222+
168223
class ProvidedProcessingParameters(BaseModel):
169224
dose_per_frame: float
170225
extract_downscale: bool = True
@@ -397,36 +452,6 @@ async def finalise_session(session_id: MurfeySessionID, db=murfey_db):
397452
return data
398453

399454

400-
@router.post("/sessions/{session_id}/multigrid_controller/visit_end_time")
401-
async def update_visit_end_time(
402-
session_id: MurfeySessionID, end_time: datetime.datetime, db=murfey_db
403-
):
404-
# Load data for session
405-
session_entry = db.exec(select(Session).where(Session.id == session_id)).one()
406-
instrument_name = session_entry.instrument_name
407-
408-
# Update visit end time in database
409-
session_entry.visit_end_time = end_time
410-
db.add(session_entry)
411-
db.commit()
412-
413-
# Update the multigrid controller
414-
data = {}
415-
machine_config = get_machine_config(instrument_name=instrument_name)[
416-
instrument_name
417-
]
418-
if machine_config.instrument_server_url:
419-
async with aiohttp.ClientSession() as clientsession:
420-
async with clientsession.post(
421-
f"{machine_config.instrument_server_url}{url_path_for('api.router', 'update_multigrid_controller_visit_end_time', session_id=session_id)}?end_time={quote(end_time.isoformat())}",
422-
headers={
423-
"Authorization": f"Bearer {instrument_server_tokens[session_id]['access_token']}"
424-
},
425-
) as resp:
426-
data = await resp.json()
427-
return data
428-
429-
430455
@router.post("/sessions/{session_id}/abandon_session")
431456
async def abandon_session(session_id: MurfeySessionID, db=murfey_db):
432457
data = {}

src/murfey/util/route_manifest.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ murfey.instrument_server.api.router:
4343
path_params: []
4444
methods:
4545
- POST
46+
- path: /sessions/{session_id}/multigrid_controller/status
47+
function: check_multigrid_controller_exists
48+
path_params: []
49+
methods:
50+
- GET
4651
- path: /sessions/{session_id}/stop_rsyncer
4752
function: stop_rsyncer
4853
path_params: []
@@ -503,6 +508,11 @@ murfey.server.api.instrument.router:
503508
path_params: []
504509
methods:
505510
- POST
511+
- path: /instrument_server/sessions/{session_id}/multigrid_controller/status
512+
function: check_multigrid_controller_exists
513+
path_params: []
514+
methods:
515+
- GET
506516
- path: /instrument_server/sessions/{session_id}/provided_processing_parameters
507517
function: pass_proc_params_to_instrument_server
508518
path_params: []

tests/instrument_server/test_api.py

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
11
from pathlib import Path
22
from typing import Optional
3-
from unittest.mock import ANY, Mock, patch
3+
from unittest.mock import ANY, MagicMock, patch
44
from urllib.parse import urlparse
55

6-
from pytest import mark
6+
import pytest
7+
from fastapi import FastAPI
8+
from fastapi.testclient import TestClient
9+
from pytest_mock import MockerFixture
710

8-
from murfey.instrument_server.api import (
9-
GainReference,
10-
_get_murfey_url,
11-
upload_gain_reference,
12-
)
11+
from murfey.instrument_server.api import _get_murfey_url
12+
from murfey.instrument_server.api import router as client_router
13+
from murfey.instrument_server.api import validate_session_token
1314
from murfey.util import posix_path
1415
from murfey.util.api import url_path_for
1516

17+
18+
def set_up_test_client(session_id: Optional[int] = None):
19+
"""
20+
Helper function to set up a test client for the instrument server with validation
21+
checks disabled.
22+
"""
23+
# Set up the instrument server
24+
client_app = FastAPI()
25+
if session_id:
26+
client_app.dependency_overrides[validate_session_token] = lambda: session_id
27+
client_app.include_router(client_router)
28+
return TestClient(client_app)
29+
30+
1631
test_get_murfey_url_params_matrix = (
1732
# Server URL to use
1833
("default",),
@@ -23,7 +38,7 @@
2338
)
2439

2540

26-
@mark.parametrize("test_params", test_get_murfey_url_params_matrix)
41+
@pytest.mark.parametrize("test_params", test_get_murfey_url_params_matrix)
2742
def test_get_murfey_url(
2843
test_params: tuple[str],
2944
mock_client_configuration, # From conftest.py
@@ -57,6 +72,24 @@ def test_get_murfey_url(
5772
assert parsed_server.path == parsed_original.path
5873

5974

75+
def test_check_multigrid_controller_exists(mocker: MockerFixture):
76+
session_id = 1
77+
78+
# Patch out the multigrid controllers that have been stored in memory
79+
mocker.patch("murfey.instrument_server.api.controllers", {session_id: MagicMock()})
80+
81+
# Set up the test client
82+
client_server = set_up_test_client(session_id=session_id)
83+
url_path = url_path_for(
84+
"api.router", "check_multigrid_controller_exists", session_id=session_id
85+
)
86+
response = client_server.get(url_path)
87+
88+
# Check that the result is as expected
89+
assert response.status_code == 200
90+
assert response.json() == {"exists": True}
91+
92+
6093
test_upload_gain_reference_params_matrix = (
6194
# Rsync URL settings
6295
("http://1.1.1.1",), # When rsync_url is provided
@@ -65,25 +98,23 @@ def test_get_murfey_url(
6598
)
6699

67100

68-
@mark.parametrize("test_params", test_upload_gain_reference_params_matrix)
69-
@patch("murfey.instrument_server.api.subprocess")
70-
@patch("murfey.instrument_server.api.tokens")
71-
@patch("murfey.instrument_server.api._get_murfey_url")
72-
@patch("murfey.instrument_server.api.requests")
101+
@pytest.mark.parametrize("test_params", test_upload_gain_reference_params_matrix)
73102
def test_upload_gain_reference(
74-
mock_request,
75-
mock_get_server_url,
76-
mock_tokens,
77-
mock_subprocess,
103+
mocker: MockerFixture,
78104
test_params: tuple[Optional[str]],
79105
):
80-
81106
# Unpack test parameters and define other ones
82107
(rsync_url_setting,) = test_params
83-
server_url = "http://0.0.0.0:8000"
108+
server_url = "https://murfey.server.test"
84109
instrument_name = "murfey"
85110
session_id = 1
86111

112+
# Mock out objects
113+
mock_request = mocker.patch("murfey.instrument_server.api.requests")
114+
mock_get_server_url = mocker.patch("murfey.instrument_server.api._get_murfey_url")
115+
mock_subprocess = mocker.patch("murfey.instrument_server.api.subprocess")
116+
mocker.patch("murfey.instrument_server.api.tokens", {session_id: ANY})
117+
87118
# Create a mock machine config base on the test params
88119
rsync_module = "data"
89120
gain_ref_dir = "C:/ProgramData/Gatan/Gain Reference"
@@ -95,12 +126,12 @@ def test_upload_gain_reference(
95126
mock_machine_config["rsync_url"] = rsync_url_setting
96127

97128
# Assign expected values to the mock objects
98-
mock_response = Mock()
129+
mock_response = MagicMock()
99130
mock_response.status_code = 200
100131
mock_response.json.return_value = mock_machine_config
101132
mock_request.get.return_value = mock_response
102133
mock_get_server_url.return_value = server_url
103-
mock_subprocess.run.return_value = Mock(returncode=0)
134+
mock_subprocess.run.return_value = MagicMock(returncode=0)
104135

105136
# Construct payload and pass request to function
106137
gain_ref_file = f"{gain_ref_dir}/gain.mrc"
@@ -111,13 +142,18 @@ def test_upload_gain_reference(
111142
"visit_path": visit_path,
112143
"gain_destination_dir": gain_dest_dir,
113144
}
114-
result = upload_gain_reference(
145+
146+
# Set up instrument server test client
147+
client_server = set_up_test_client(session_id=session_id)
148+
149+
# Poke the endpoint with the expected data
150+
url_path = url_path_for(
151+
"api.router",
152+
"upload_gain_reference",
115153
instrument_name=instrument_name,
116154
session_id=session_id,
117-
gain_reference=GainReference(
118-
**payload,
119-
),
120155
)
156+
response = client_server.post(url_path, json=payload)
121157

122158
# Check that the machine config request was called
123159
machine_config_url = f"{server_url}{url_path_for('session_control.router', 'machine_info_by_instrument', instrument_name=instrument_name)}"
@@ -145,4 +181,4 @@ def test_upload_gain_reference(
145181
)
146182

147183
# Check that the function ran through to completion successfully
148-
assert result == {"success": True}
184+
assert response.json() == {"success": True}

0 commit comments

Comments
 (0)