From 98514850577df4a5c2ee3ea59c3a3eea26718226 Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Tue, 31 Mar 2026 22:21:27 +0100 Subject: [PATCH 1/6] Update config and lifecycle test to use API prefix This adds an API prefix to the three config files, and updates the hard-coded URLs in the lifecycle test to use the new API root. The change doesn't affect the unit or integration test suites, as neither of these set an API prefix. Most of the required updates are likely to be in the web app, which I've not yet touched. --- ofm_config_full.json | 1 + ofm_config_manual.json | 1 + ofm_config_simulation.json | 1 + tests/lifecycle_test/testfile.py | 4 ++-- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ofm_config_full.json b/ofm_config_full.json index 16a211fb..53ca2587 100644 --- a/ofm_config_full.json +++ b/ofm_config_full.json @@ -26,6 +26,7 @@ "bg_channel_deviations_luv": "openflexure_microscope_server.things.background_detect:ChannelDeviationLUV" }, "settings_folder": "/var/openflexure/settings/", + "api_prefix": "/api/v3", "application_config": { "log_folder": "/var/openflexure/logs/", "data_folder": "/var/openflexure/data/" diff --git a/ofm_config_manual.json b/ofm_config_manual.json index 4498cf81..e9b0c092 100644 --- a/ofm_config_manual.json +++ b/ofm_config_manual.json @@ -4,6 +4,7 @@ "system": "openflexure_microscope_server.things.system:OpenFlexureSystem" }, "settings_folder": "./openflexure/settings/", + "api_prefix": "/api/v3", "application_config": { "log_folder": "./openflexure/logs/", "data_folder": "./openflexure/data/" diff --git a/ofm_config_simulation.json b/ofm_config_simulation.json index b52ce4d7..076d7243 100644 --- a/ofm_config_simulation.json +++ b/ofm_config_simulation.json @@ -20,6 +20,7 @@ "bg_channel_deviations_luv": "openflexure_microscope_server.things.background_detect:ChannelDeviationLUV" }, "settings_folder": "./openflexure/settings/", + "api_prefix": "/api/v3", "application_config": { "log_folder": "./openflexure/logs/", "data_folder": "./openflexure/data/" diff --git a/tests/lifecycle_test/testfile.py b/tests/lifecycle_test/testfile.py index 2278c6e2..48b41f69 100755 --- a/tests/lifecycle_test/testfile.py +++ b/tests/lifecycle_test/testfile.py @@ -74,7 +74,7 @@ def main() -> None: def test_client_connection() -> None: """Check a ThingClient can interact with the simulation microscope camera.""" print("Connecting Python client to microscope, and capturing image") - cam_client = lt.ThingClient.from_url("http://localhost:5000/camera/") + cam_client = lt.ThingClient.from_url("http://localhost:5000/api/v3/camera/") img = Image.open(cam_client.grab_jpeg().open()) print(f"Successfully grabbed image of size {img.size}") assert img.size == (820, 616) @@ -95,7 +95,7 @@ def subscribe_to_mjpeg_stream() -> subprocess.Popen: "nohup", "curl", "-s", - "http://localhost:5000/camera/mjpeg_stream", + "http://localhost:5000/api/v3/camera/mjpeg_stream", ">", "/dev/null", "2>&1", From 93a2e87dac9d17e7fea421ad8489aa2206b3d8df Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Tue, 31 Mar 2026 22:59:10 +0100 Subject: [PATCH 2/6] Adjust default origin URL to use /api/v3 prefix This won't be the only URL that needs fixing, but it should make most of the web app work. --- webapp/src/stores/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/stores/settings.js b/webapp/src/stores/settings.js index 4870f258..9b89c937 100644 --- a/webapp/src/stores/settings.js +++ b/webapp/src/stores/settings.js @@ -10,7 +10,7 @@ function getOriginFromLocation() { if (origin) { return origin; } else { - return url.origin; + return `${url.origin}/api/v3`; } } From c37ed236c3fe6cf96e38980a675c04fbd3fe7fcb Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Tue, 31 Mar 2026 23:05:20 +0100 Subject: [PATCH 3/6] Depend on the relevant labthings branch This will need to be fixed before merge --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 17d55fe1..d83a4e5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ "Operating System :: OS Independent", ] dependencies = [ - "labthings-fastapi==0.1.0", + "labthings-fastapi @ https://github.com/labthings/labthings-fastapi/archive/refs/heads/api-prefix.zip", "sangaboard~=0.4.0", "camera-stage-mapping ~= 0.1.10", "opencv-python-headless ~= 4.13.0", From f91373e2152f183a26afac5855d50b87b0bfd4eb Mon Sep 17 00:00:00 2001 From: Richard Bowman Date: Wed, 1 Apr 2026 09:54:25 +0100 Subject: [PATCH 4/6] Update default URL for overriding the origin. --- webapp/src/stores/settings.js | 2 +- webapp/vite.config.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/src/stores/settings.js b/webapp/src/stores/settings.js index 9b89c937..2477dbc3 100644 --- a/webapp/src/stores/settings.js +++ b/webapp/src/stores/settings.js @@ -32,7 +32,7 @@ export const useSettingsStore = defineStore( const appTheme = ref("system"); const disableStream = ref(false); // The origin to use if overriding with dev tools - const overrideOrigin = ref("http://microscope.local:5000"); + const overrideOrigin = ref("http://microscope.local:5000/api/v3"); // The step sizes for navigation via control pane/keys presses const navigationStepSize = ref({ x: 200, diff --git a/webapp/vite.config.mjs b/webapp/vite.config.mjs index 1be459b9..61e1d6e4 100644 --- a/webapp/vite.config.mjs +++ b/webapp/vite.config.mjs @@ -42,7 +42,7 @@ export default defineConfig({ host: true, // Set the development server port to 8080. // OFM uses port 5000, so we avoid conflicts. run, and override by using the address: - // http://microscope.local:8080/?overrideOrigin=http://microscope.local:5000# + // http://microscope.local:8080/?overrideOrigin=http://microscope.local:5000/api/v3# port: 8080, strictPort: true, }, From fa5aaaf254d69529afdecd101da476af58ff9932 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 10 May 2026 12:55:19 +0100 Subject: [PATCH 5/6] Update dependencies to use new labthings rc --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d83a4e5d..81e84e83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ "Operating System :: OS Independent", ] dependencies = [ - "labthings-fastapi @ https://github.com/labthings/labthings-fastapi/archive/refs/heads/api-prefix.zip", + "labthings-fastapi==0.2.0rc1", "sangaboard~=0.4.0", "camera-stage-mapping ~= 0.1.10", "opencv-python-headless ~= 4.13.0", From b6efb73cba3adf2b396e18bfbe5a743b37d4b1d5 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Sun, 10 May 2026 13:57:12 +0100 Subject: [PATCH 6/6] Fix data directory save location and mount path --- src/openflexure_microscope_server/server/__init__.py | 2 +- .../server/serve_static_files.py | 9 ++++++--- src/openflexure_microscope_server/things/__init__.py | 2 +- tests/unit_tests/test_serve_static_files.py | 7 +++++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/openflexure_microscope_server/server/__init__.py b/src/openflexure_microscope_server/server/__init__.py index 754a7799..52ee182f 100644 --- a/src/openflexure_microscope_server/server/__init__.py +++ b/src/openflexure_microscope_server/server/__init__.py @@ -86,7 +86,7 @@ def customise_server( ) add_v2_endpoints(server) - add_static_files(server.app, application_config.data_folder) + add_static_files(server, application_config.data_folder) # Configure logging to DEBUG if requested in CLI args. if debug: diff --git a/src/openflexure_microscope_server/server/serve_static_files.py b/src/openflexure_microscope_server/server/serve_static_files.py index 81338cc8..0d360a73 100644 --- a/src/openflexure_microscope_server/server/serve_static_files.py +++ b/src/openflexure_microscope_server/server/serve_static_files.py @@ -6,6 +6,8 @@ from fastapi import FastAPI from fastapi.responses import FileResponse, RedirectResponse from fastapi.staticfiles import StaticFiles +import labthings_fastapi as lt + THIS_DIR = os.path.dirname(os.path.abspath(__file__)) STATIC_PATH = os.path.normpath(os.path.join(THIS_DIR, "..", "static")) @@ -35,7 +37,7 @@ def add_static_file(app: FastAPI, fname: str, folder: str) -> None: ) -def add_static_files(app: FastAPI, data_folder: str) -> None: +def add_static_files(server: lt.ThingServer, data_folder: str) -> None: """Add the static files responsible for the webapp app to the FastAPI app. Note that any file in the root of the static dir will not be cached. However, the @@ -43,9 +45,10 @@ def add_static_files(app: FastAPI, data_folder: str) -> None: The Vue CSS and JS are hashed, so if updated their filename will update. The most important file not to cache is "index.html". - :param app: The FastAPI app to add to, in this case the OpenFlexure server + :param server: The LabThings server. :param data_folder: The directory for any data. """ + app = server.app check_static_dir() @app.get("/", response_class=RedirectResponse) @@ -71,7 +74,7 @@ def add_static_files(app: FastAPI, data_folder: str) -> None: if not os.path.isdir(data_folder): os.makedirs(data_folder) app.mount( - "/data/", + server._api_prefix.rstrip("/") + "/data/", StaticFiles(directory=data_folder), name="data", ) diff --git a/src/openflexure_microscope_server/things/__init__.py b/src/openflexure_microscope_server/things/__init__.py index b35f1fdf..c497209f 100644 --- a/src/openflexure_microscope_server/things/__init__.py +++ b/src/openflexure_microscope_server/things/__init__.py @@ -31,7 +31,7 @@ class OFMThing(lt.Thing): raise ValueError("No application configuration was supplied.") app_data_dir = application_config["data_folder"] self._data_dir = os.path.join( - os.path.normpath(str(app_data_dir)), os.path.normpath(self.path.strip("/")) + os.path.normpath(str(app_data_dir)), os.path.normpath(self.name) ) return self diff --git a/tests/unit_tests/test_serve_static_files.py b/tests/unit_tests/test_serve_static_files.py index ba5fb693..62a741d0 100644 --- a/tests/unit_tests/test_serve_static_files.py +++ b/tests/unit_tests/test_serve_static_files.py @@ -124,10 +124,13 @@ def test_add_static_files(mock_static_dir, mocker): "openflexure_microscope_server.server.serve_static_files.STATIC_PATH", mock_static_dir, ) + mock_server = mocker.Mock() + mock_server.app = mock_app + mock_server._api_prefix = "/api/v3" # Get the wrapper function from the mocked decorator wrapper = mock_app.get.return_value with tempfile.TemporaryDirectory() as datadir: - serve_static_files.add_static_files(mock_app, data_folder=datadir) + serve_static_files.add_static_files(mock_server, data_folder=datadir) # Get should have been called twice to create a route for index assert mock_app.get.call_count == 2 @@ -158,5 +161,5 @@ def test_add_static_files(mock_static_dir, mocker): assert "/assets/" in mounted_path assert os.path.join(mock_static_dir, "assets") in mounted_dir - assert mock_app.mount.call_args_list[1].args[0] == "/data/" + assert mock_app.mount.call_args_list[1].args[0] == "/api/v3/data/" assert mock_app.mount.call_args_list[1].args[1].directory == datadir