diff --git a/src/hflow/transform.py b/src/hflow/transform.py index aa1791cb..61e9d568 100644 --- a/src/hflow/transform.py +++ b/src/hflow/transform.py @@ -56,7 +56,6 @@ import hashlib import json import logging -import math from collections.abc import Callable, Mapping, Sequence from copy import copy from dataclasses import dataclass, field @@ -64,6 +63,11 @@ from typing import Any, Literal from hflow import video as video_module +from hflow._field_guards import ( + require_int_in_range, + require_positive_float, + require_positive_int, +) from hflow._grouped_mcap_writer import ( NO_SCHEMA_ID, ChannelId, @@ -156,28 +160,11 @@ class TransformConfig: def __post_init__(self) -> None: """Reject invalid settings before they become a pipeline identity.""" - if isinstance(self.crf, bool) or not isinstance(self.crf, int): - raise ValueError(f"crf must be an int, got {type(self.crf).__name__}") - if not 0 <= self.crf <= 51: - raise ValueError(f"crf must be between 0 and 51, got {self.crf}") - + require_int_in_range(self.crf, "crf", minimum=0, maximum=51) if self.gop_seconds is not None: - if isinstance(self.gop_seconds, bool) or not isinstance(self.gop_seconds, int | float): - raise ValueError( - f"gop_seconds must be an int or float, got {type(self.gop_seconds).__name__}" - ) - if not math.isfinite(self.gop_seconds) or self.gop_seconds <= 0: - raise ValueError(f"gop_seconds must be positive and finite, got {self.gop_seconds}") - + require_positive_float(self.gop_seconds, "gop_seconds") if self.chunk_size_bytes is not None: - if isinstance(self.chunk_size_bytes, bool) or not isinstance( - self.chunk_size_bytes, int - ): - raise ValueError( - f"chunk_size_bytes must be an int, got {type(self.chunk_size_bytes).__name__}" - ) - if self.chunk_size_bytes <= 0: - raise ValueError(f"chunk_size_bytes must be positive, got {self.chunk_size_bytes}") + require_positive_int(self.chunk_size_bytes, "chunk_size_bytes") @dataclass(frozen=True) diff --git a/tests/test_transform.py b/tests/test_transform.py index edd5dd00..dcbc662c 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -49,24 +49,27 @@ def test_pipeline_version_is_a_content_hash() -> None: @pytest.mark.parametrize( - ("construct", "field"), + ("construct", "message"), [ - (lambda: TransformConfig(crf=True), "crf"), - (lambda: TransformConfig(crf=-1), "crf"), - (lambda: TransformConfig(crf=52), "crf"), - (lambda: TransformConfig(gop_seconds=True), "gop_seconds"), - (lambda: TransformConfig(gop_seconds=0), "gop_seconds"), - (lambda: TransformConfig(gop_seconds=float("nan")), "gop_seconds"), - (lambda: TransformConfig(gop_seconds=float("inf")), "gop_seconds"), - (lambda: TransformConfig(chunk_size_bytes=True), "chunk_size_bytes"), - (lambda: TransformConfig(chunk_size_bytes=0), "chunk_size_bytes"), - (lambda: TransformConfig(chunk_size_bytes=-1), "chunk_size_bytes"), + (lambda: TransformConfig(crf=True), "crf must be an int, got bool"), + (lambda: TransformConfig(crf=-1), "crf must be in [0, 51], got -1"), + (lambda: TransformConfig(crf=52), "crf must be in [0, 51], got 52"), + (lambda: TransformConfig(gop_seconds=True), "gop_seconds must be an int or float, got bool"), + (lambda: TransformConfig(gop_seconds=0), "gop_seconds must be > 0, got 0"), + (lambda: TransformConfig(gop_seconds=float("nan")), "gop_seconds must be finite, got nan"), + (lambda: TransformConfig(gop_seconds=float("inf")), "gop_seconds must be finite, got inf"), + ( + lambda: TransformConfig(chunk_size_bytes=True), + "chunk_size_bytes must be an int, got bool", + ), + (lambda: TransformConfig(chunk_size_bytes=0), "chunk_size_bytes must be > 0, got 0"), + (lambda: TransformConfig(chunk_size_bytes=-1), "chunk_size_bytes must be > 0, got -1"), ], ) def test_transform_config_rejects_invalid_numeric_settings( - construct: Callable[[], TransformConfig], field: str + construct: Callable[[], TransformConfig], message: str ) -> None: - with pytest.raises(ValueError, match=field): + with pytest.raises(ValueError, match=f"^{message}$"): construct()