Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions pyrit/converter/audio_volume_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import io
import logging
import math
from typing import Any, Literal

import numpy as np
Expand Down Expand Up @@ -46,13 +47,13 @@ def __init__(
output_format (str): The format of the audio file, defaults to "wav".
volume_factor (float): The factor by which to scale the volume.
Values > 1.0 increase volume, values < 1.0 decrease volume.
Must be greater than 0. Defaults to 1.5.
Must be finite and greater than 0. Defaults to 1.5.

Raises:
ValueError: If volume_factor is not positive.
ValueError: If volume_factor is non-finite or not positive.
"""
if volume_factor <= 0:
raise ValueError("volume_factor must be greater than 0.")
if not math.isfinite(volume_factor) or volume_factor <= 0:
raise ValueError("volume_factor must be finite and greater than 0.")
self._output_format = output_format
self._volume_factor = volume_factor

Expand Down
11 changes: 6 additions & 5 deletions pyrit/converter/audio_white_noise_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import io
import logging
import math
from typing import Any, Literal

import numpy as np
Expand Down Expand Up @@ -44,14 +45,14 @@ def __init__(
output_format (str): The format of the audio file, defaults to "wav".
noise_scale (float): Controls the amplitude of the added noise, expressed
as a fraction of the signal's maximum possible value. For int16 audio
the noise amplitude will be noise_scale * 32767. Must be greater than 0
and at most 1.0. Defaults to 0.02.
the noise amplitude will be noise_scale * 32767. Must be finite, greater
than 0, and at most 1.0. Defaults to 0.02.

Raises:
ValueError: If noise_scale is not in (0, 1].
ValueError: If noise_scale is non-finite or not in (0, 1].
"""
if noise_scale <= 0 or noise_scale > 1.0:
raise ValueError("noise_scale must be between 0 (exclusive) and 1.0 (inclusive).")
if not math.isfinite(noise_scale) or noise_scale <= 0 or noise_scale > 1.0:
raise ValueError("noise_scale must be finite, greater than 0, and at most 1.0.")
self._output_format = output_format
self._noise_scale = noise_scale

Expand Down
10 changes: 6 additions & 4 deletions pyrit/converter/image_color_saturation_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Licensed under the MIT license.

import logging
import math
from typing import Literal

from PIL import Image, ImageEnhance
Expand Down Expand Up @@ -41,13 +42,14 @@ def __init__(
0.0 produces a grayscale image (black and white).
1.0 preserves the original colors.
Values greater than 1.0 oversaturate the colors.
Defaults to 0.0 (grayscale image).
Must be finite. Defaults to 0.0 (grayscale image).

Raises:
ValueError: If unsupported output format is specified, or if level is negative.
ValueError: If unsupported output format is specified, or if level is non-finite
or negative.
"""
if level < 0:
raise ValueError(f"Level must be non-negative, got {level}")
if not math.isfinite(level) or level < 0:
raise ValueError(f"Level must be finite and non-negative, got {level}")
self._level = level
super().__init__(output_format=output_format)

Expand Down
8 changes: 6 additions & 2 deletions pyrit/converter/image_rotation_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Licensed under the MIT license.

import logging
import math
from typing import Literal

from PIL import Image
Expand Down Expand Up @@ -40,13 +41,16 @@ def __init__(
Must be one of 'JPEG', 'PNG', or 'WEBP'.
If None, keeps original format (if supported).
angle (float): The rotation angle in degrees (counter-clockwise).
Defaults to 90.0.
Must be finite. Defaults to 90.0.
fill_color (tuple[int, int, int]): The RGB color to fill exposed background areas
after rotation. Defaults to (255, 255, 255) (white).

Raises:
ValueError: If unsupported output format is specified, or if the fill color is out of range.
ValueError: If unsupported output format is specified, if the angle is non-finite,
or if the fill color is out of range.
"""
if not math.isfinite(angle):
raise ValueError(f"Angle must be finite, got {angle}")
if (
not isinstance(fill_color, tuple)
or len(fill_color) != 3
Expand Down
11 changes: 9 additions & 2 deletions tests/unit/converter/test_audio_volume_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,13 @@ async def test_convert_async_file_not_found():

def test_invalid_volume_factor_zero():
"""volume_factor of 0 should raise ValueError."""
with pytest.raises(ValueError, match="volume_factor must be greater than 0"):
with pytest.raises(ValueError, match="volume_factor must be finite and greater than 0"):
AudioVolumeConverter(volume_factor=0)


def test_invalid_volume_factor_negative():
"""Negative volume_factor should raise ValueError."""
with pytest.raises(ValueError, match="volume_factor must be greater than 0"):
with pytest.raises(ValueError, match="volume_factor must be finite and greater than 0"):
AudioVolumeConverter(volume_factor=-1.0)


Expand All @@ -160,3 +160,10 @@ async def test_unsupported_input_type(sqlite_instance):
converter = AudioVolumeConverter(volume_factor=1.5)
with pytest.raises(ValueError, match="Input type not supported"):
await converter.convert_async(prompt="some_file.wav", input_type="text")


@pytest.mark.parametrize("volume_factor", [float("nan"), float("inf"), float("-inf")])
def test_invalid_volume_factor_non_finite(volume_factor: float) -> None:
"""Non-finite volume factors should fail during converter construction."""
with pytest.raises(ValueError, match="volume_factor must be finite"):
AudioVolumeConverter(volume_factor=volume_factor)
13 changes: 10 additions & 3 deletions tests/unit/converter/test_audio_white_noise_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,19 +115,19 @@ async def test_white_noise_file_not_found():

def test_white_noise_invalid_scale_zero():
"""noise_scale of 0 should raise ValueError."""
with pytest.raises(ValueError, match="noise_scale must be between 0"):
with pytest.raises(ValueError, match="noise_scale must be finite"):
AudioWhiteNoiseConverter(noise_scale=0)


def test_white_noise_invalid_scale_negative():
"""Negative noise_scale should raise ValueError."""
with pytest.raises(ValueError, match="noise_scale must be between 0"):
with pytest.raises(ValueError, match="noise_scale must be finite"):
AudioWhiteNoiseConverter(noise_scale=-0.1)


def test_white_noise_invalid_scale_above_one():
"""noise_scale > 1 should raise ValueError."""
with pytest.raises(ValueError, match="noise_scale must be between 0"):
with pytest.raises(ValueError, match="noise_scale must be finite"):
AudioWhiteNoiseConverter(noise_scale=1.5)


Expand Down Expand Up @@ -170,3 +170,10 @@ async def test_white_noise_initialized_seed_is_repeatable_and_does_not_disturb_n
for output_path in output_paths:
if os.path.exists(output_path):
os.remove(output_path)


@pytest.mark.parametrize("noise_scale", [float("nan"), float("inf"), float("-inf")])
def test_invalid_noise_scale_non_finite(noise_scale: float) -> None:
"""Non-finite noise scales should fail during converter construction."""
with pytest.raises(ValueError, match="noise_scale must be finite"):
AudioWhiteNoiseConverter(noise_scale=noise_scale)
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def test_image_color_saturation_converter_initialization_output_format_validatio
def test_image_color_saturation_converter_initialization_level_validation():
"""Test validation of level parameter."""
for invalid_level in [-0.1, -1.0, -100.0]:
with pytest.raises(ValueError, match="Level must be non-negative"):
with pytest.raises(ValueError, match="Level must be finite and non-negative"):
ImageColorSaturationConverter(level=invalid_level)

for valid_level in [0.0, 0.5, 1.0, 2.0, 10.0]:
Expand Down Expand Up @@ -213,3 +213,10 @@ async def test_image_color_saturation_converter_output_format_fallback():
mock_serializer.read_data_async.return_value = img_bytes
await converter.convert_async(prompt="test.tiff", input_type="image_path")
assert mock_serializer.file_extension == "jpeg"


@pytest.mark.parametrize("level", [float("nan"), float("inf"), float("-inf")])
def test_invalid_level_non_finite(level: float) -> None:
"""Non-finite saturation levels should fail during converter construction."""
with pytest.raises(ValueError, match="Level must be finite"):
ImageColorSaturationConverter(level=level)
7 changes: 7 additions & 0 deletions tests/unit/converter/test_image_rotation_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,10 @@ def test_image_rotation_converter_custom_fill_color(sample_image_bytes):
# Check that a corner pixel (exposed area) has the fill color
corner_pixel = rotated_image.getpixel((0, 0))
assert corner_pixel[:3] == fill_color


@pytest.mark.parametrize("angle", [float("nan"), float("inf"), float("-inf")])
def test_invalid_angle_non_finite(angle: float) -> None:
"""Non-finite rotation angles should fail during converter construction."""
with pytest.raises(ValueError, match="Angle must be finite"):
ImageRotationConverter(angle=angle)