Update how the application configuration is read for LabThings 0.0.17

This commit is contained in:
Julian Stirling 2026-03-04 14:03:14 +00:00
parent 45903f92a6
commit 7e4ec53d78
7 changed files with 37 additions and 62 deletions

View file

@ -75,8 +75,6 @@ def customise_server(
server: lt.ThingServer, application_config: OFMApplicationData
) -> None:
"""Customise the server with additional endpoints, etc."""
configure_logging(application_config.log_folder)
if DEVELOPER_MODE:
# Allow CORS in developer mode for easier testing with the webapp
server.app.add_middleware(
@ -108,7 +106,12 @@ def serve_from_cli(argv: Optional[list[str]] = None) -> None:
lt_config = None
server = None
try:
lt_config, application_config = _full_config_from_args(args)
lt_config = _full_config_from_args(args)
# Validate our application data
if lt_config.application_config is None:
raise ValueError("No application configuration was supplied.")
application_config = OFMApplicationData(**lt_config.application_config)
configure_logging(application_config.log_folder)
server = lt.ThingServer.from_config(lt_config)
customise_server(server, application_config)
@ -166,25 +169,18 @@ def serve_from_cli(argv: Optional[list[str]] = None) -> None:
raise e
def _full_config_from_args(
args: Namespace,
) -> tuple[ThingServerConfig, OFMApplicationData]:
def _full_config_from_args(args: Namespace) -> ThingServerConfig:
"""Load configuration from LabThings args allowing patching.
This returns the labthings ThingServerConfig model and a dictionary of the config
for the microscope.
This returns the labthings ThingServerConfig model.
This provides similar functionarlity to lt.cli.config_from_args except allows the
configuration file to specify a base config, and optionally patches.
"""
# Don't allow configuration to be set as an argument as then we cannot handle
# application_config
if not args.config:
raise RuntimeError(
"OpenFlexure Microscope Server must have a configuration file specified."
)
patched_config = load_patched_config(args.config)
application_config = OFMApplicationData(**patched_config.pop("application_config"))
return ThingServerConfig(**patched_config), application_config
return ThingServerConfig(**patched_config)

View file

@ -7,9 +7,6 @@ with other Things and including them in the LabThings-FastAPI config file.
import posixpath
from typing import Optional, Self
from starlette.routing import Mount
from starlette.staticfiles import StaticFiles
import labthings_fastapi as lt
@ -20,7 +17,13 @@ class OFMThing(lt.Thing):
def __enter__(self) -> Self:
"""Set the data directory when the Thing is entered."""
self._data_dir = get_data_directory_from_server(self)
# Note that the `application_config` was already validated when the
# server initialised.
application_config = self._thing_server_interface.application_config
if application_config is None:
raise ValueError("No application configuration was supplied.")
app_data_dir = application_config["data_folder"]
self._data_dir = posixpath.join(str(app_data_dir), self.path.strip("/"))
return self
@property
@ -31,31 +34,3 @@ class OFMThing(lt.Thing):
"No data directory set. Has the LabThings server been started?"
)
return self._data_dir
def get_data_directory_from_server(thing: lt.Thing) -> str:
"""Get the data directory from the server.
:param thing: The Thing to get the data directory for.
:return: The data directory as a string:
:raise RuntimeError: If not able to get the data directory for any reason.
"""
server = thing._thing_server_interface._server()
if server is None:
raise RuntimeError("No server found to communicate with.")
routes = server.app.routes
try:
route_paths = [route.path if hasattr(route, "path") else "" for route in routes]
data_index = route_paths.index("/data")
except ValueError as e:
raise RuntimeError("Could not find data directory") from e
mount = routes[data_index]
if not isinstance(mount, Mount):
raise RuntimeError("Data directory isn't a starlette.routing.Mount.")
if not isinstance(mount.app, StaticFiles):
raise RuntimeError("Data is not mounted as static files.")
app_data_dir = mount.app.directory
if app_data_dir is None:
raise RuntimeError("Data directory is not set.")
return posixpath.join(str(app_data_dir), thing.path.strip("/"))

View file

@ -604,7 +604,7 @@ class BaseCamera(lt.Thing):
return self._background_detector_name
@background_detector_name.setter
def _set_background_detector_name(self, name: str) -> None:
def _set_background_detector_name(self, name: Optional[str]) -> None:
"""Validate and set background_detector_name."""
if name not in self._all_background_detectors:
self.logger.warning(f"{name} is not a valid background detector name.")