Merge branch 'v3-labthings-api-prefix' into 'v3'

Add an API prefix

See merge request openflexure/openflexure-microscope-server!562
This commit is contained in:
Julian Stirling 2026-05-11 11:51:29 +00:00
commit a2965e588d
11 changed files with 22 additions and 13 deletions

View file

@ -26,6 +26,7 @@
"bg_channel_deviations_luv": "openflexure_microscope_server.things.background_detect:ChannelDeviationLUV" "bg_channel_deviations_luv": "openflexure_microscope_server.things.background_detect:ChannelDeviationLUV"
}, },
"settings_folder": "/var/openflexure/settings/", "settings_folder": "/var/openflexure/settings/",
"api_prefix": "/api/v3",
"application_config": { "application_config": {
"log_folder": "/var/openflexure/logs/", "log_folder": "/var/openflexure/logs/",
"data_folder": "/var/openflexure/data/" "data_folder": "/var/openflexure/data/"

View file

@ -4,6 +4,7 @@
"system": "openflexure_microscope_server.things.system:OpenFlexureSystem" "system": "openflexure_microscope_server.things.system:OpenFlexureSystem"
}, },
"settings_folder": "./openflexure/settings/", "settings_folder": "./openflexure/settings/",
"api_prefix": "/api/v3",
"application_config": { "application_config": {
"log_folder": "./openflexure/logs/", "log_folder": "./openflexure/logs/",
"data_folder": "./openflexure/data/" "data_folder": "./openflexure/data/"

View file

@ -20,6 +20,7 @@
"bg_channel_deviations_luv": "openflexure_microscope_server.things.background_detect:ChannelDeviationLUV" "bg_channel_deviations_luv": "openflexure_microscope_server.things.background_detect:ChannelDeviationLUV"
}, },
"settings_folder": "./openflexure/settings/", "settings_folder": "./openflexure/settings/",
"api_prefix": "/api/v3",
"application_config": { "application_config": {
"log_folder": "./openflexure/logs/", "log_folder": "./openflexure/logs/",
"data_folder": "./openflexure/data/" "data_folder": "./openflexure/data/"

View file

@ -18,7 +18,7 @@ classifiers = [
"Operating System :: OS Independent", "Operating System :: OS Independent",
] ]
dependencies = [ dependencies = [
"labthings-fastapi==0.1.0", "labthings-fastapi==0.2.0rc1",
"sangaboard~=0.4.0", "sangaboard~=0.4.0",
"camera-stage-mapping ~= 0.1.10", "camera-stage-mapping ~= 0.1.10",
"opencv-python-headless ~= 4.13.0", "opencv-python-headless ~= 4.13.0",

View file

@ -86,7 +86,7 @@ def customise_server(
) )
add_v2_endpoints(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. # Configure logging to DEBUG if requested in CLI args.
if debug: if debug:

View file

@ -6,6 +6,8 @@ from fastapi import FastAPI
from fastapi.responses import FileResponse, RedirectResponse from fastapi.responses import FileResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
import labthings_fastapi as lt
THIS_DIR = os.path.dirname(os.path.abspath(__file__)) THIS_DIR = os.path.dirname(os.path.abspath(__file__))
STATIC_PATH = os.path.normpath(os.path.join(THIS_DIR, "..", "static")) 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. """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 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 The Vue CSS and JS are hashed, so if updated their filename will update. The most
important file not to cache is "index.html". 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. :param data_folder: The directory for any data.
""" """
app = server.app
check_static_dir() check_static_dir()
@app.get("/", response_class=RedirectResponse) @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): if not os.path.isdir(data_folder):
os.makedirs(data_folder) os.makedirs(data_folder)
app.mount( app.mount(
"/data/", server._api_prefix.rstrip("/") + "/data/",
StaticFiles(directory=data_folder), StaticFiles(directory=data_folder),
name="data", name="data",
) )

View file

@ -31,7 +31,7 @@ class OFMThing(lt.Thing):
raise ValueError("No application configuration was supplied.") raise ValueError("No application configuration was supplied.")
app_data_dir = application_config["data_folder"] app_data_dir = application_config["data_folder"]
self._data_dir = os.path.join( 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 return self

View file

@ -74,7 +74,7 @@ def main() -> None:
def test_client_connection() -> None: def test_client_connection() -> None:
"""Check a ThingClient can interact with the simulation microscope camera.""" """Check a ThingClient can interact with the simulation microscope camera."""
print("Connecting Python client to microscope, and capturing image") 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()) img = Image.open(cam_client.grab_jpeg().open())
print(f"Successfully grabbed image of size {img.size}") print(f"Successfully grabbed image of size {img.size}")
assert img.size == (820, 616) assert img.size == (820, 616)
@ -95,7 +95,7 @@ def subscribe_to_mjpeg_stream() -> subprocess.Popen:
"nohup", "nohup",
"curl", "curl",
"-s", "-s",
"http://localhost:5000/camera/mjpeg_stream", "http://localhost:5000/api/v3/camera/mjpeg_stream",
">", ">",
"/dev/null", "/dev/null",
"2>&1", "2>&1",

View file

@ -124,10 +124,13 @@ def test_add_static_files(mock_static_dir, mocker):
"openflexure_microscope_server.server.serve_static_files.STATIC_PATH", "openflexure_microscope_server.server.serve_static_files.STATIC_PATH",
mock_static_dir, 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 # Get the wrapper function from the mocked decorator
wrapper = mock_app.get.return_value wrapper = mock_app.get.return_value
with tempfile.TemporaryDirectory() as datadir: 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 # Get should have been called twice to create a route for index
assert mock_app.get.call_count == 2 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 "/assets/" in mounted_path
assert os.path.join(mock_static_dir, "assets") in mounted_dir 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 assert mock_app.mount.call_args_list[1].args[1].directory == datadir

View file

@ -10,7 +10,7 @@ function getOriginFromLocation() {
if (origin) { if (origin) {
return origin; return origin;
} else { } else {
return url.origin; return `${url.origin}/api/v3`;
} }
} }
@ -32,7 +32,7 @@ export const useSettingsStore = defineStore(
const appTheme = ref("system"); const appTheme = ref("system");
const disableStream = ref(false); const disableStream = ref(false);
// The origin to use if overriding with dev tools // 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 // The step sizes for navigation via control pane/keys presses
const navigationStepSize = ref({ const navigationStepSize = ref({
x: 200, x: 200,

View file

@ -42,7 +42,7 @@ export default defineConfig({
host: true, host: true,
// Set the development server port to 8080. // Set the development server port to 8080.
// OFM uses port 5000, so we avoid conflicts. run, and override by using the address: // 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, port: 8080,
strictPort: true, strictPort: true,
}, },