Simplify setting model validation

This commit is contained in:
Julian Stirling 2026-02-25 19:05:24 +00:00
parent 63338f8ea0
commit 2ae4f832bf
3 changed files with 20 additions and 55 deletions

View file

@ -16,7 +16,7 @@ from types import TracebackType
from typing import Literal, Mapping, Optional, Self, Sequence
import numpy as np
from pydantic import BaseModel, computed_field, field_validator, model_validator
from pydantic import BaseModel, Field, computed_field, field_validator, model_validator
import labthings_fastapi as lt
from labthings_fastapi.types.numpy import NDArray
@ -59,12 +59,12 @@ class StackParams(BaseModel):
"""A class for holding stack parameters, and returning computed ones."""
stack_dz: int
images_to_save: int
images_to_save: int = Field(gt=0)
# Using docstrings under variables as this is how pdoc would expect
# attributed to be documented
settling_time: float = 0.3
settling_time: float = Field(default=0.3, ge=0)
"""Time (in seconds) between moving and capturing an image"""
backlash_correction: int = 250
@ -75,22 +75,6 @@ class StackParams(BaseModel):
origin: StackOrigin = StackOrigin.START
"""Where the stack is positioned relative to the current z position."""
@field_validator("images_to_save")
@classmethod
def validate_images_to_save(cls, value: int) -> int:
"""Validate that the number of images to save is greater than zero."""
if value <= 0:
raise ValueError(f"Invalid number of images to save: {value}. Must be > 0.")
return value
@field_validator("settling_time")
@classmethod
def validate_settling_time(cls, value: float) -> float:
"""Validate that settling time is zero or positive."""
if value < 0:
raise ValueError(f"Invalid settling time: {value}. Must be positive or 0.")
return value
class SmartStackParams(StackParams):
"""A class for holding smart stack parameters, and returning computed ones."""

View file

@ -15,12 +15,12 @@ import tempfile
import time
from datetime import datetime
from types import TracebackType
from typing import Any, Literal, Mapping, Optional, Self, Tuple
from typing import Annotated, Any, Literal, Mapping, Optional, Self, Tuple
import numpy as np
import piexif
from PIL import Image
from pydantic import BaseModel, field_validator
from pydantic import BaseModel, Field
import labthings_fastapi as lt
from labthings_fastapi.types.numpy import NDArray
@ -48,34 +48,15 @@ class CaptureError(RuntimeError):
"""An error trying to capture from a CameraThing."""
PositiveInt = Annotated[int, Field(ge=1)]
NonEmptyString = Annotated[str, Field(min_length=1)]
class CaptureParams(BaseModel):
"""A class for capturing at least a single image."""
images_dir: str
save_resolution: tuple[int, int]
@field_validator("save_resolution")
@classmethod
def validate_save_resolution(cls, value: Tuple[int, int]) -> Tuple[int, int]:
"""Validate that save_resolution is a tuple with exactly two positive integers."""
if not isinstance(value, tuple):
raise TypeError("Save resolution should be a tuple")
if len(value) != 2 or any(not isinstance(x, int) or x <= 0 for x in value):
raise ValueError(
f"Invalid save_resolution: {value}. "
"Must be a tuple of two positive integers."
)
return value
@field_validator("images_dir")
@classmethod
def validate_images_dir(cls, value: str) -> str:
"""Validate that images_dir is a non-empty string."""
if not isinstance(value, str) or not value:
raise ValueError(
f"Invalid images_dir: {value}. Must be a non-empty string."
)
return value
images_dir: NonEmptyString
save_resolution: tuple[PositiveInt, PositiveInt]
class NoImageInMemoryError(RuntimeError):

View file

@ -109,8 +109,8 @@ def test_stack_params_negative_images_to_save(save_ims, extra_ims):
# Depending on the values multiple messages are possible
match = (
"(Can't test for focus with fewer than 3 images|"
"Images to save must be positive and odd)|"
"Invalid number of images to save"
"Images to save must be positive and odd|"
"Input should be greater than 0)"
)
with pytest.raises(ValueError, match=match):
SmartStackParams(
@ -154,8 +154,8 @@ def test_even_images_to_save(save_ims, extra_ims):
"""
match = (
"(Can't test for focus with fewer than 3 images|"
"Images to save must be positive and odd)|"
"Invalid number of images to save"
"Images to save must be positive and odd|"
"Input should be greater than 0)"
)
with pytest.raises(ValueError, match=match):
SmartStackParams(
@ -948,7 +948,7 @@ def test_run_basic_stack_end_origin(
def test_invalid_stack_images_raises():
"""Test basic stack raises expected error for negative or zero image count."""
for capture_count in [-3, 0]:
with pytest.raises(ValueError, match="Invalid number of images to save"):
with pytest.raises(ValueError, match="Input should be greater than 0"):
StackParams(
stack_dz=10,
images_to_save=capture_count,
@ -960,7 +960,7 @@ def test_invalid_stack_images_raises():
def test_invalid_stack_settling_raises():
"""Test basic stack raises expected error for negative settling time."""
with pytest.raises(ValueError, match="Invalid settling time"):
with pytest.raises(ValueError, match="Input should be greater than or equal to 0"):
StackParams(
stack_dz=10,
images_to_save=1,
@ -973,7 +973,7 @@ def test_invalid_stack_settling_raises():
@pytest.mark.parametrize(
("bad_path", "match_err"),
[
("", "Must be a non-empty string"),
("", "String should have at least 1 character"),
(None, "Input should be a valid string"),
(67, "Input should be a valid string"),
],
@ -987,8 +987,8 @@ def test_invalid_capture_dir_raises(bad_path, match_err):
@pytest.mark.parametrize(
("bad_res", "match_err"),
[
((-100, 50), "Invalid save_resolution:"),
((20, 0), "Invalid save_resolution:"),
((-100, 50), "Input should be greater than or equal to 1"),
((20, 0), "Input should be greater than or equal to 1"),
("", "Input should be a valid tuple"),
(None, "Input should be a valid tuple"),
(67, "Input should be a valid tuple"),