diff --git a/picamera_coverage.zip b/picamera_coverage.zip index 7c9090de..121e8c33 100644 Binary files a/picamera_coverage.zip and b/picamera_coverage.zip differ diff --git a/src/openflexure_microscope_server/things/autofocus.py b/src/openflexure_microscope_server/things/autofocus.py index eebff335..695c349c 100644 --- a/src/openflexure_microscope_server/things/autofocus.py +++ b/src/openflexure_microscope_server/things/autofocus.py @@ -405,6 +405,7 @@ class AutofocusThing(lt.Thing): field of view to assess focus (autofocus and testing the success of a z-stack) """ + _class_settings = {"validate_properties_on_set": True} _cam: BaseCamera = lt.thing_slot() _stage: BaseStage = lt.thing_slot() diff --git a/src/openflexure_microscope_server/things/background_detect.py b/src/openflexure_microscope_server/things/background_detect.py index c2753a75..fcf03b9d 100644 --- a/src/openflexure_microscope_server/things/background_detect.py +++ b/src/openflexure_microscope_server/things/background_detect.py @@ -38,6 +38,8 @@ class ChannelBlankError(lt.exceptions.InvocationError): class BackgroundDetectAlgorithm(lt.Thing): """The base class for defining background detect algorithms.""" + _class_settings = {"validate_properties_on_set": True} + display_name: str = lt.property(default="Base Detector", readonly=True) def __init__(self, thing_server_interface: lt.ThingServerInterface) -> None: diff --git a/src/openflexure_microscope_server/things/camera/__init__.py b/src/openflexure_microscope_server/things/camera/__init__.py index 96840889..b9f0f2a7 100644 --- a/src/openflexure_microscope_server/things/camera/__init__.py +++ b/src/openflexure_microscope_server/things/camera/__init__.py @@ -172,6 +172,8 @@ class BaseCamera(OFMThing): ``__init__`` method of the subclass. """ + _class_settings = {"validate_properties_on_set": True} + _all_background_detectors: Mapping[str, BackgroundDetectAlgorithm] = lt.thing_slot() mjpeg_stream = lt.outputs.MJPEGStreamDescriptor() diff --git a/src/openflexure_microscope_server/things/camera_stage_mapping.py b/src/openflexure_microscope_server/things/camera_stage_mapping.py index 813c762f..341683a9 100644 --- a/src/openflexure_microscope_server/things/camera_stage_mapping.py +++ b/src/openflexure_microscope_server/things/camera_stage_mapping.py @@ -119,6 +119,8 @@ class CameraStageMapper(lt.Thing): override the ``get_xyz_position()`` and ``move_to_xyz_position()`` methods. """ + _class_settings = {"validate_properties_on_set": True} + _cam: BaseCamera = lt.thing_slot() _stage: BaseStage = lt.thing_slot() diff --git a/src/openflexure_microscope_server/things/illumination.py b/src/openflexure_microscope_server/things/illumination.py index 2e444138..ce8647b0 100644 --- a/src/openflexure_microscope_server/things/illumination.py +++ b/src/openflexure_microscope_server/things/illumination.py @@ -12,6 +12,8 @@ from .stage.sangaboard import SangaboardThing class Illumination(lt.Thing): """Base class for an illumination controller.""" + _class_settings = {"validate_properties_on_set": True} + @lt.action def set_led(self, led_on: bool = True) -> None: """Set the LED to on or off.""" diff --git a/src/openflexure_microscope_server/things/scan_workflows.py b/src/openflexure_microscope_server/things/scan_workflows.py index c92900c2..fe4ee898 100644 --- a/src/openflexure_microscope_server/things/scan_workflows.py +++ b/src/openflexure_microscope_server/things/scan_workflows.py @@ -62,6 +62,8 @@ class ScanWorkflow(Generic[SettingModelType], lt.Thing): scan planning, acquisition routine. """ + _class_settings = {"validate_properties_on_set": True} + display_name: str = lt.property(default="Base Workflow", readonly=True) ui_blurb: str = lt.property( default="If you see this message, something is wrong.", readonly=True diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index 5b2e54af..25b0a60b 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -119,6 +119,8 @@ class SmartScanThing(OFMThing): past scans. """ + _class_settings = {"validate_properties_on_set": True} + _cam: BaseCamera = lt.thing_slot() _stage: BaseStage = lt.thing_slot() _all_workflows: Mapping[str, ScanWorkflow] = lt.thing_slot() diff --git a/src/openflexure_microscope_server/things/stage/__init__.py b/src/openflexure_microscope_server/things/stage/__init__.py index d0bdeab6..e5b0b72b 100644 --- a/src/openflexure_microscope_server/things/stage/__init__.py +++ b/src/openflexure_microscope_server/things/stage/__init__.py @@ -106,6 +106,7 @@ class BaseStage(lt.Thing): """ _axis_names = ("x", "y", "z") + _class_settings = {"validate_properties_on_set": True} def __init__(self, thing_server_interface: lt.ThingServerInterface) -> None: """Initialise the stage. diff --git a/src/openflexure_microscope_server/things/stage_measure.py b/src/openflexure_microscope_server/things/stage_measure.py index 411e57ed..4d426a51 100644 --- a/src/openflexure_microscope_server/things/stage_measure.py +++ b/src/openflexure_microscope_server/things/stage_measure.py @@ -117,6 +117,8 @@ class ParasiticMotionError(lt.exceptions.InvocationError): class RangeofMotionThing(lt.Thing): """A class used to measure the range of motion of the stage in X and Y.""" + _class_settings = {"validate_properties_on_set": True} + _autofocus: AutofocusThing = lt.thing_slot() _cam: BaseCamera = lt.thing_slot() _csm: CameraStageMapper = lt.thing_slot() diff --git a/src/openflexure_microscope_server/things/system.py b/src/openflexure_microscope_server/things/system.py index ff6a2fed..d1d02517 100644 --- a/src/openflexure_microscope_server/things/system.py +++ b/src/openflexure_microscope_server/things/system.py @@ -46,6 +46,8 @@ class OpenFlexureSystem(lt.Thing): of the system. """ + _class_settings = {"validate_properties_on_set": True} + _microscope_id: Optional[str] = None @lt.setting diff --git a/tests/unit_tests/test_simulated_camera.py b/tests/unit_tests/test_simulated_camera.py index 4935d9d6..dd499fc9 100644 --- a/tests/unit_tests/test_simulated_camera.py +++ b/tests/unit_tests/test_simulated_camera.py @@ -7,6 +7,7 @@ import numpy as np import pytest from hypothesis import given from hypothesis import strategies as st +from pydantic import ValidationError import labthings_fastapi as lt @@ -210,7 +211,7 @@ def test_objective_getter_setter(camera): camera.objective = 15 with pytest.raises(ValueError, match=err_msg): camera.objective = 0 - with pytest.raises(ValueError, match=err_msg): + with pytest.raises(ValidationError): camera.objective = "twenty" diff --git a/tests/unit_tests/test_smart_scan.py b/tests/unit_tests/test_smart_scan.py index e8abf2d5..566faea9 100644 --- a/tests/unit_tests/test_smart_scan.py +++ b/tests/unit_tests/test_smart_scan.py @@ -25,7 +25,7 @@ from unittest import mock import pytest from fastapi import HTTPException -from pydantic import BaseModel +from pydantic import BaseModel, ValidationError from labthings_fastapi.exceptions import InvocationCancelledError from labthings_fastapi.testing import create_thing_without_server @@ -206,8 +206,11 @@ def test_setting_workflows(caplog, mocker): with caplog.at_level(logging.WARNING), smart_scan_thing: assert smart_scan_thing._workflow_name == "foo" assert smart_scan_thing._workflow is workflows["foo"] - # Can't set None, warns doesn't change - smart_scan_thing.workflow_name = None + # Can't set None, raises a ValidationError + with pytest.raises(ValidationError): + smart_scan_thing.workflow_name = None + # Empty string still won't change, but just warns + smart_scan_thing.workflow_name = "" assert len(caplog.records) == 1 assert smart_scan_thing._workflow_name == "foo" assert smart_scan_thing._workflow is workflows["foo"]