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
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
60 changes: 60 additions & 0 deletions test/unit/tinify_format_test.py
Original file line number Diff line number Diff line change
@@ -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"]}
2 changes: 2 additions & 0 deletions tinify/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -146,6 +147,7 @@ def from_url(url): # type: (str) -> Source

__all__ = [
'Client',
'Format',
'Result',
'ResultMeta',
'Source',
Expand Down
42 changes: 42 additions & 0 deletions tinify/_format.py
Original file line number Diff line number Diff line change
@@ -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")
2 changes: 1 addition & 1 deletion tinify/_typed.py
Original file line number Diff line number Diff line change
Expand Up @@ -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", "*/*"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need a type hint in the API to inform the user that they may use the enum from _format.py

class ConvertOptions(TypedDict, total=False):
type: Union[ConvertTypes, List[ConvertTypes]]

Expand Down
Loading