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
25 changes: 22 additions & 3 deletions imgproxykit/options/quality.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import ClassVar
from typing import ClassVar, override

from imgproxykit.options.base import ScalarOption

Expand All @@ -12,6 +12,9 @@ class Quality(ScalarOption[int]):
Attributes:
value: Quality percentage. Zero delegates to server quality settings.

Raises:
ValueError: If ``value`` is not an integer between 0 and 100.

Usage:

>>> option = Quality(85)
Expand All @@ -23,8 +26,24 @@ class Quality(ScalarOption[int]):

"""

# Public API:

# Public API:
name: ClassVar[str] = 'quality'
short_name: ClassVar[str | None] = 'q'

@override
def validate(self) -> None:
"""
Validate that the quality percentage is between 0 and 100.

Raises:
ValueError: If quality is outside 0..100 or is a boolean.

"""
if isinstance(self.value, bool) or not (
isinstance(self.value, int) and 0 <= self.value <= 100
):
msg = (
f'Quality must be an integer between 0 and 100, '
f'got {self.value}.'
)
raise ValueError(msg)
20 changes: 19 additions & 1 deletion imgproxykit/options/rotate.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import ClassVar
from typing import ClassVar, override

from imgproxykit.options.base import ScalarOption

Expand All @@ -14,6 +14,9 @@ class Rotate(ScalarOption[int]):
Attributes:
value: Clockwise angle in degrees; must be a multiple of 90.

Raises:
ValueError: If ``value`` is not an integer multiple of 90.

Usage:

>>> option = Rotate(90)
Expand All @@ -27,3 +30,18 @@ class Rotate(ScalarOption[int]):

name: ClassVar[str] = 'rotate'
short_name: ClassVar[str | None] = 'rot'

@override
def validate(self) -> None:
"""
Validate that the angle is an integer multiple of 90 degrees.

Raises:
ValueError: If angle is not a multiple of 90 or is a boolean.

"""
if isinstance(self.value, bool) or not (
isinstance(self.value, int) and self.value % 90 == 0
):
msg = f'Angle must be an integer multiple of 90, got {self.value}.'
raise ValueError(msg)
2 changes: 0 additions & 2 deletions tests/test_unit/test_options/test_integer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
(options.Height, 'height'),
(options.MinWidth, 'min-width'),
(options.MinHeight, 'min-height'),
(options.Quality, 'quality'),
(options.Pixelate, 'pixelate'),
(options.Rotate, 'rotate'),
(options.Expires, 'expires'),
(options.MaxBytes, 'max_bytes'),
(options.MaxResultDimension, 'max_result_dimension'),
Expand Down
48 changes: 48 additions & 0 deletions tests/test_unit/test_options/test_quality.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from typing import Any

import pytest

from imgproxykit import Transform
from imgproxykit.options import Quality
from imgproxykit.serialization import PathSerializer


@pytest.mark.parametrize('value', [0, 1, 50, 99, 100])
def test_quality_valid(path_serializer: PathSerializer, *, value: int) -> None:
"""Ensure that valid quality values serialize correctly."""
option = Quality(value)
expected = f'quality:{value}'
assert (
path_serializer._serialize_segment(option.name, option.arguments)
== expected
)


@pytest.mark.parametrize('value', [0, 75, 100])
def test_quality_transform_valid(value: int) -> None:
"""Ensure that valid quality works with Transform kwargs."""
transform = Transform(quality=value)
assert transform.options == (Quality(value),)


@pytest.mark.parametrize(
'invalid_value',
[-10, -1, 101, 200, True, False, 85.5, '85'],
)
def test_quality_invalid(invalid_value: Any) -> None:
"""Ensure that invalid quality values raise ValueError."""
with pytest.raises(
ValueError,
match='Quality must be an integer between 0 and 100',
):
Quality(invalid_value)


@pytest.mark.parametrize('invalid_value', [-1, 101, True])
def test_quality_transform_invalid(invalid_value: Any) -> None:
"""Ensure that invalid quality in Transform kwargs raises ValueError."""
with pytest.raises(
ValueError,
match='Quality must be an integer between 0 and 100',
):
Transform(quality=invalid_value) # type: ignore[arg-type]
48 changes: 48 additions & 0 deletions tests/test_unit/test_options/test_rotate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from typing import Any

import pytest

from imgproxykit import Transform
from imgproxykit.options import Rotate
from imgproxykit.serialization import PathSerializer


@pytest.mark.parametrize('angle', [0, 90, 180, 270, 360, -90, -180])
def test_rotate_valid(path_serializer: PathSerializer, *, angle: int) -> None:
"""Ensure that valid rotation angles serialize correctly."""
option = Rotate(angle)
expected = f'rotate:{angle}'
assert (
path_serializer._serialize_segment(option.name, option.arguments)
== expected
)


@pytest.mark.parametrize('angle', [0, 90, 180, 270])
def test_rotate_transform_valid(angle: int) -> None:
"""Ensure that valid rotation angle works with Transform kwargs."""
transform = Transform(rotate=angle)
assert transform.options == (Rotate(angle),)


@pytest.mark.parametrize(
'invalid_angle',
[1, 45, 89, 91, 100, -45, True, False, 90.0, '90'],
)
def test_rotate_invalid(invalid_angle: Any) -> None:
"""Ensure that invalid rotation angles raise ValueError."""
with pytest.raises(
ValueError,
match='Angle must be an integer multiple of 90',
):
Rotate(invalid_angle)


@pytest.mark.parametrize('invalid_angle', [45, 100, True])
def test_rotate_transform_invalid(invalid_angle: Any) -> None:
"""Ensure that invalid rotation in Transform kwargs raises ValueError."""
with pytest.raises(
ValueError,
match='Angle must be an integer multiple of 90',
):
Transform(rotate=invalid_angle) # type: ignore[arg-type]
Loading