diff --git a/CHANGES.md b/CHANGES.md index 4092e15..d33aa7e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,8 @@ +## Unreleased + +* Added `tinify.Format` constants for the media types `convert` accepts. +* Added the missing `image/jpg` media type to the `convert` type hints. + ## 1.7.2 * Add JXL to supported image types diff --git a/test/unit/tinify_format_test.py b/test/unit/tinify_format_test.py new file mode 100644 index 0000000..7bc693e --- /dev/null +++ b/test/unit/tinify_format_test.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +import json +import pytest + +import tinify +from tinify import Format, Source + + +class TestTinifyFormat: + def test_should_expose_supported_media_types(self): + assert Format.WEBP == "image/webp" + assert Format.PNG == "image/png" + assert Format.JPEG == "image/jpeg" + assert Format.JPG == "image/jpg" + assert Format.AVIF == "image/avif" + assert Format.JXL == "image/jxl" + assert Format.ANY == "*/*" + + def test_should_not_be_instantiable(self): + with pytest.raises(TypeError): + Format() + + +class TestTinifyFormatConvert: + @pytest.fixture(autouse=True) + def setup(self, mock_requests): + tinify.key = "valid" + mock_requests.post( + "https://api.tinify.com/shrink", + status_code=201, + headers={"Location": "https://api.tinify.com/some/location"}, + ) + self.mock_requests = mock_requests + yield + + def test_convert_with_format_should_serialize_media_type(self): + self.mock_requests.post( + "https://api.tinify.com/some/location", + status_code=200, + content=b"converted file", + ) + + Source.from_buffer("png file").convert(type=Format.JXL).to_buffer() + + body = json.loads(self.mock_requests.last_request.text) + assert body["convert"] == {"type": "image/jxl"} + + def test_convert_with_multiple_formats_should_serialize_media_types(self): + self.mock_requests.post( + "https://api.tinify.com/some/location", + status_code=200, + content=b"converted file", + ) + + Source.from_buffer("png file").convert( + type=[Format.JXL, Format.WEBP] + ).to_buffer() + + body = json.loads(self.mock_requests.last_request.text) + assert body["convert"] == {"type": ["image/jxl", "image/webp"]} diff --git a/tinify/__init__.py b/tinify/__init__.py index 9fcbd45..686a3cd 100644 --- a/tinify/__init__.py +++ b/tinify/__init__.py @@ -138,6 +138,7 @@ def from_url(url): # type: (str) -> Source from .version import __version__ +from ._format import Format from .client import Client from .result_meta import ResultMeta from .result import Result @@ -146,6 +147,7 @@ def from_url(url): # type: (str) -> Source __all__ = [ 'Client', + 'Format', 'Result', 'ResultMeta', 'Source', diff --git a/tinify/_format.py b/tinify/_format.py new file mode 100644 index 0000000..fd9e6ce --- /dev/null +++ b/tinify/_format.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function, unicode_literals + + +class Format(object): + """Media types that images can be converted to. + + Use these constants with :meth:`Source.convert`:: + + tinify.from_file("input.png") \\ + .convert(type=tinify.Format.JXL) \\ + .to_file("output.jxl") + + Multiple types may be supplied, in which case the API returns the + smallest result:: + + .convert(type=[tinify.Format.JXL, tinify.Format.WEBP]) + """ + + #: WebP. + WEBP = "image/webp" + + #: PNG. + PNG = "image/png" + + #: JPEG. + JPEG = "image/jpeg" + + #: JPEG, an alias of :attr:`JPEG`. + JPG = "image/jpg" + + #: AVIF. + AVIF = "image/avif" + + #: JPEG XL. + JXL = "image/jxl" + + #: Wildcard, returns the smallest of the supported types. + ANY = "*/*" + + def __init__(self): + raise TypeError("Format is a namespace of constants and cannot be instantiated") diff --git a/tinify/_typed.py b/tinify/_typed.py index fc1a6da..06789e4 100644 --- a/tinify/_typed.py +++ b/tinify/_typed.py @@ -5,7 +5,7 @@ class ResizeOptions(TypedDict,total=False): width: Optional[int] height: Optional[int] -ConvertTypes = Literal['image/webp', 'image/jpeg', 'image/png', "image/avif", "image/jxl", "*/*"] +ConvertTypes = Literal['image/webp', 'image/jpeg', 'image/jpg', 'image/png', "image/avif", "image/jxl", "*/*"] class ConvertOptions(TypedDict, total=False): type: Union[ConvertTypes, List[ConvertTypes]]