Skip to content

feat: add Format constants for convert media types, including JXL - #28

Open
Sreini wants to merge 2 commits into
masterfrom
feat/jxl-format
Open

Sreini wants to merge 2 commits into
masterfrom
feat/jxl-format

Conversation

@Sreini

@Sreini Sreini commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Adds a Format namespace of media-type constants so the types accepted by
convert are discoverable via autocomplete — including JPEG XL
(image/jxl), which the API already accepts but no client surfaced.

image/jxl is already in the API allowlist (apps/api/src/schemas/optimizations.ts
and apps/opt-api/src/constants/content-types.ts), so this is a client-side
discoverability change only. There is no API-side work outstanding.

Members

WEBP, PNG, JPEG, JPG, AVIF, JXL, ANY (*/*) — mirroring the
API allowlist exactly.

Why string constants and not an enum

The JSON body has to carry the media type verbatim. A language-level enum
serializes as its name or ordinal, not image/jxl. String constants also keep
this fully backward compatible: callers passing raw strings are unaffected.

Also fixed here

ConvertTypes was missing image/jpg, which the API does accept — passing it
failed type checking. Added.

Verification

Full suite green locally: pytest test/unit → 90 passed, 1 pre-existing skip, including 4 new tinify_format_test.py cases.

Part of a coordinated change across all six Tinify client libraries, so the
same Format API is available in each.

Adds a Format namespace of media-type string constants so the types
accepted by convert() are discoverable via autocomplete, matching the
other Tinify client libraries.

Also adds the missing image/jpg to ConvertTypes. The API accepts it,
but the type hints did not, so passing it failed type checking.
Comment thread tinify/_typed.py
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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 175af41 — and it was worse than a missing hint: the constants did not typecheck at all.

JXL = "image/jxl" was inferred as str, so convert(type=Format.JXL) failed against the ConvertTypes literal union:

Argument "type" to "convert" of "Source" has incompatible type "str";
expected "Literal['image/webp', ..., 'image/jxl', '*/*']"

The constants were exactly what you could not pass. Each is now annotated Final[Literal[...]], which narrows it to its own literal type and makes it a member of ConvertTypes, so no change to _typed.py is needed.

Used # type: comments with a TYPE_CHECKING-guarded import rather than inline annotations, since setup.py still classifies 2.7 and this module is imported at runtime by __init__.py. Verified with mypy: all three call sites (single, list, bare literal) now pass; python -c "import tinify" still works.

Worth noting the repo's own mypy --check tinify would not have caught this — it checks the package, not call sites that consume these hints.

The constants were inferred as plain str, so passing Format.JXL to
convert() failed type checking against the ConvertTypes literal union --
the constants were exactly what you could not pass.

Annotating each as Final[Literal[...]] via type comments narrows them to
their own literal type. Type comments plus a TYPE_CHECKING-guarded import
keep the module importable on the Python 2.7 the package still claims.

Also clarifies that JPG is not an alias of JPEG, and annotates __init__.
@Sreini

Sreini commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Heads-up: the three Mypy jobs now fail on master too, independent of this PR.

mypy: error: ambiguous option: --check could match --check-untyped-defs, --check-unreachable

This is argument parsing — it happens before mypy opens a single source file, so no code change can influence it.

ci-cd.yml runs mypy --check tinify. --check was never a real flag; it used to resolve as an unambiguous abbreviation. A newer mypy added --check-unreachable, so the abbreviation is now ambiguous and mypy exits 2. setup.py lists mypy unpinned in tests_require, so CI installs whatever is current.

Verified with a throwaway branch at master's exact tip, zero changes (since deleted): all three Mypy jobs fail identically.

I left the workflow alone on purpose. Changing it to mypy tinify makes the jobs run but then red for a different pre-existing reason:

tinify/client.py:34: error: Incompatible types in assignment (expression has type
"dict[str, str]", variable has type "CaseInsensitiveDict[str]")

That reproduces identically on master, so it is also not from this PR. Both want fixing, but as their own change rather than bundled into this one — say the word and I will send it.

Unit tests pass everywhere: 90 passed, 1 skipped locally and across the CI matrix.

One thing worth noting about the defect this PR fixes: mypy --check tinify would not have caught it even when the command worked. It checks the package, not the call sites that consume ConvertTypes — which is exactly where Format.JXL was being rejected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants