diff --git a/.github/workflows/analysis-coverage.yml b/.github/workflows/analysis-coverage.yml
index e96cb912..975e7a33 100644
--- a/.github/workflows/analysis-coverage.yml
+++ b/.github/workflows/analysis-coverage.yml
@@ -288,7 +288,7 @@ jobs:
coverage-pi-heif:
runs-on: ubuntu-22.04
- name: Pi-Heif Coverage(Linux) • 🐍3.8
+ name: Pi-Heif Coverage(Linux) • 🐍3.13
steps:
- uses: actions/checkout@v4
@@ -297,7 +297,8 @@ jobs:
- uses: actions/setup-python@v5
with:
- python-version: '3.8'
+ python-version: '3.13'
+ allow-prereleases: true
- name: Prepare system
run: |
@@ -333,7 +334,7 @@ jobs:
test-pi-heif:
runs-on: ubuntu-22.04
- name: Pi-Heif Test(Linux) • 🐍3.8
+ name: Pi-Heif Test(Linux) • 🐍3.13
steps:
- uses: actions/checkout@v4
@@ -344,7 +345,8 @@ jobs:
- uses: actions/setup-python@v5
with:
- python-version: '3.8'
+ python-version: '3.13'
+ allow-prereleases: true
- name: Prepare system
run: |
diff --git a/.github/workflows/test-wheels-pi_heif.yml b/.github/workflows/test-wheels-pi_heif.yml
index 8dcd5d6a..99c14304 100644
--- a/.github/workflows/test-wheels-pi_heif.yml
+++ b/.github/workflows/test-wheels-pi_heif.yml
@@ -72,7 +72,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: ["pypy-3.9", "pypy-3.10", "3.8", "3.9", "3.10", "3.11", "3.12"]
+ python-version: ["pypy-3.9", "pypy-3.10", "3.9", "3.10", "3.11", "3.12", "3.13"]
env:
PH_LIGHT_ACTION: 1
@@ -105,7 +105,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: ["pypy-3.9", "pypy-3.10", "3.8", "3.9", "3.10", "3.11", "3.12"]
+ python-version: ["pypy-3.9", "pypy-3.10", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- name: Delay, waiting Pypi to update.
@@ -138,7 +138,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: ["pypy-3.9", "pypy-3.10", "3.8", "3.9", "3.10", "3.11", "3.12"]
+ python-version: ["pypy-3.9", "pypy-3.10", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- name: Delay, waiting Pypi to update.
diff --git a/.github/workflows/test-wheels.yml b/.github/workflows/test-wheels.yml
index ea4453d6..d93bfe0b 100644
--- a/.github/workflows/test-wheels.yml
+++ b/.github/workflows/test-wheels.yml
@@ -73,7 +73,7 @@ jobs:
runs-on: windows-2019
strategy:
matrix:
- python-version: ["pypy-3.9", "pypy-3.10", "3.8", "3.9", "3.10", "3.11", "3.12"]
+ python-version: ["pypy-3.9", "pypy-3.10", "3.9", "3.10", "3.11", "3.12", "3.13"]
env:
PH_FULL_ACTION: 1
@@ -103,7 +103,7 @@ jobs:
runs-on: macos-12
strategy:
matrix:
- python-version: ["pypy-3.9", "pypy-3.10", "3.8", "3.9", "3.10", "3.11", "3.12"]
+ python-version: ["pypy-3.9", "pypy-3.10", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- name: Delay, waiting Pypi to update.
@@ -134,7 +134,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
- python-version: ["pypy-3.9", "pypy-3.10", "3.8", "3.9", "3.10", "3.11", "3.12"]
+ python-version: ["pypy-3.9", "pypy-3.10", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- name: Delay, waiting Pypi to update.
diff --git a/README.md b/README.md
index 4409b40d..4d72e13c 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
[![docs](https://readthedocs.org/projects/pillow-heif/badge/?version=latest)](https://pillow-heif.readthedocs.io/en/latest/?badge=latest)
[![codecov](https://codecov.io/gh/bigcat88/pillow_heif/branch/master/graph/badge.svg?token=JY64F2OL6V)](https://codecov.io/gh/bigcat88/pillow_heif)
-![PythonVersion](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)
+![PythonVersion](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue)
![impl](https://img.shields.io/pypi/implementation/pillow_heif)
![pypi](https://img.shields.io/pypi/v/pillow_heif.svg)
[![Downloads](https://static.pepy.tech/personalized-badge/pillow-heif?period=total&units=international_system&left_color=grey&right_color=orange&left_text=Downloads)](https://pepy.tech/project/pillow-heif)
@@ -145,7 +145,6 @@ if im.info["depth_images"]:
| **_Wheels table_** | macOS
Intel | macOS
Silicon | Windows
| musllinux* | manylinux* |
|--------------------|:---------------:|:-----------------:|:------------:|:----------:|:----------:|
-| CPython 3.8 | ✅ | N/A | ✅ | ✅ | ✅ |
| CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅ |
| CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅ |
| CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅ |
diff --git a/pi-heif/README.md b/pi-heif/README.md
index 3c6163c9..ac14d7b2 100644
--- a/pi-heif/README.md
+++ b/pi-heif/README.md
@@ -3,7 +3,7 @@
[![Analysis & Coverage](https://github.com/bigcat88/pillow_heif/actions/workflows/analysis-coverage.yml/badge.svg)](https://github.com/bigcat88/pillow_heif/actions/workflows/analysis-coverage.yml)
[![Wheels test(Pi-Heif)](https://github.com/bigcat88/pillow_heif/actions/workflows/test-wheels-pi_heif.yml/badge.svg)](https://github.com/bigcat88/pillow_heif/actions/workflows/test-wheels-pi_heif.yml)
-![PythonVersion](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue)
+![PythonVersion](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue)
![impl](https://img.shields.io/pypi/implementation/pi_heif)
![pypi](https://img.shields.io/pypi/v/pi_heif.svg)
[![Downloads](https://static.pepy.tech/personalized-badge/pi-heif?period=total&units=international_system&left_color=grey&right_color=orange&left_text=Downloads)](https://pepy.tech/project/pi-heif)
@@ -86,7 +86,6 @@ if im.info["depth_images"]:
| **_Wheels table_** | macOS
Intel | macOS
Silicon | Windows
| musllinux* | manylinux* |
|--------------------|:---------------:|:-----------------:|:------------:|:----------:|:----------:|
-| CPython 3.8 | ✅ | N/A | ✅ | ✅ | ✅ |
| CPython 3.9 | ✅ | ✅ | ✅ | ✅ | ✅ |
| CPython 3.10 | ✅ | ✅ | ✅ | ✅ | ✅ |
| CPython 3.11 | ✅ | ✅ | ✅ | ✅ | ✅ |
diff --git a/pi-heif/setup.cfg b/pi-heif/setup.cfg
index 5830b201..d75cefbf 100644
--- a/pi-heif/setup.cfg
+++ b/pi-heif/setup.cfg
@@ -15,11 +15,11 @@ classifiers =
Topic :: Multimedia :: Graphics
Topic :: Multimedia :: Graphics :: Graphics Conversion
Programming Language :: Python :: 3
- Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
+ Programming Language :: Python :: 3.13
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
@@ -33,7 +33,7 @@ project_urls =
Changelog=https://github.com/bigcat88/pillow_heif/blob/master/CHANGELOG.md
[options]
-python_requires = >=3.8
+python_requires = >=3.9
zip_safe = False
packages = find:
install_requires =
diff --git a/pillow_heif/_version.py b/pillow_heif/_version.py
index dd47b1cc..902281b2 100644
--- a/pillow_heif/_version.py
+++ b/pillow_heif/_version.py
@@ -1,3 +1,3 @@
"""Version of pillow_heif/pi_heif."""
-__version__ = "0.19.0.dev0"
+__version__ = "0.20.0.dev0"
diff --git a/pillow_heif/as_plugin.py b/pillow_heif/as_plugin.py
index d39db826..eb487d8b 100644
--- a/pillow_heif/as_plugin.py
+++ b/pillow_heif/as_plugin.py
@@ -1,7 +1,9 @@
"""Plugins for the Pillow library."""
+from __future__ import annotations
+
from itertools import chain
-from typing import Union
+from typing import IO
from warnings import warn
from PIL import Image, ImageFile, ImageSequence
@@ -32,7 +34,7 @@
class _LibHeifImageFile(ImageFile.ImageFile):
"""Base class with all functionality for ``HeifImageFile`` and ``AvifImageFile`` classes."""
- _heif_file: Union[HeifFile, None] = None
+ _heif_file: HeifFile | None = None
_close_exclusive_fp_after_loading = True
_mode: str # only for Pillow 10.1+
@@ -85,7 +87,7 @@ def getxmp(self) -> dict:
return self._getxmp(xmp_data[0]) # pylint: disable=no-member
return {}
- def seek(self, frame):
+ def seek(self, frame: int):
if not self._seek_check(frame):
return
self.__frame = frame
@@ -94,7 +96,7 @@ def seek(self, frame):
if _exif is not None and getattr(_exif, "_loaded", None):
_exif._loaded = False # pylint: disable=protected-access
- def tell(self):
+ def tell(self) -> int:
return self.__frame
def verify(self) -> None:
@@ -113,7 +115,7 @@ def is_animated(self) -> bool:
"""Returns ``True`` if this image contains more than one frame, or ``False`` otherwise."""
return self.n_frames > 1
- def _seek_check(self, frame):
+ def _seek_check(self, frame: int):
if frame < 0 or frame >= self.n_frames:
raise EOFError("attempt to seek outside sequence")
return self.tell() != frame
@@ -140,11 +142,11 @@ def _is_supported_heif(fp) -> bool:
return magic[8:12] in (b"heic", b"heix", b"heim", b"heis", b"hevc", b"hevx", b"hevm", b"hevs", b"mif1", b"msf1")
-def _save_heif(im, fp, _filename):
+def _save_heif(im: Image.Image, fp: IO[bytes], _filename: str | bytes):
__save_one(im, fp, HeifCompressionFormat.HEVC)
-def _save_all_heif(im, fp, _filename):
+def _save_all_heif(im: Image.Image, fp: IO[bytes], _filename: str | bytes):
__save_all(im, fp, HeifCompressionFormat.HEVC)
@@ -183,11 +185,11 @@ def _is_supported_avif(fp) -> bool:
# return False
-def _save_avif(im, fp, _filename):
+def _save_avif(im: Image.Image, fp: IO[bytes], _filename: str | bytes):
__save_one(im, fp, HeifCompressionFormat.AV1)
-def _save_all_avif(im, fp, _filename):
+def _save_all_avif(im: Image.Image, fp: IO[bytes], _filename: str | bytes):
__save_all(im, fp, HeifCompressionFormat.AV1)
@@ -234,13 +236,13 @@ def __options_update(**kwargs):
warn(f"Unknown option: {k}", stacklevel=1)
-def __save_one(im, fp, compression_format: HeifCompressionFormat):
+def __save_one(im: Image.Image, fp: IO[bytes], compression_format: HeifCompressionFormat):
ctx_write = CtxEncode(compression_format, **im.encoderinfo)
_pil_encode_image(ctx_write, im, True, **im.encoderinfo)
ctx_write.save(fp)
-def __save_all(im, fp, compression_format: HeifCompressionFormat):
+def __save_all(im: Image.Image, fp: IO[bytes], compression_format: HeifCompressionFormat):
ctx_write = CtxEncode(compression_format, **im.encoderinfo)
current_frame = im.tell() if hasattr(im, "tell") else None
append_images = im.encoderinfo.get("append_images", [])
diff --git a/pillow_heif/heif.py b/pillow_heif/heif.py
index 930b846d..b9df9adc 100644
--- a/pillow_heif/heif.py
+++ b/pillow_heif/heif.py
@@ -1,8 +1,10 @@
"""Functions and classes for heif images to read and write."""
+from __future__ import annotations
+
from copy import copy, deepcopy
from io import SEEK_SET
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any
from PIL import Image
@@ -38,7 +40,7 @@
class BaseImage:
"""Base class for :py:class:`HeifImage` and :py:class:`HeifDepthImage`."""
- size: tuple
+ size: tuple[int, int]
"""Width and height of the image."""
mode: str
@@ -82,7 +84,7 @@ def __array_interface__(self):
else:
width = int(width / 2)
typestr = " 1:
shape += (MODE_INFO[self.mode][0],)
return {"shape": shape, "typestr": typestr, "version": 3, "data": self.data}
@@ -143,13 +145,11 @@ class HeifImage(BaseImage):
def __init__(self, c_image):
super().__init__(c_image)
- _metadata: List[dict] = c_image.metadata
+ _metadata: list[dict] = c_image.metadata
_exif = _retrieve_exif(_metadata)
_xmp = _retrieve_xmp(_metadata)
- _thumbnails: List[Optional[int]] = (
- [i for i in c_image.thumbnails if i is not None] if options.THUMBNAILS else []
- )
- _depth_images: List[Optional[HeifDepthImage]] = (
+ _thumbnails: list[int | None] = [i for i in c_image.thumbnails if i is not None] if options.THUMBNAILS else []
+ _depth_images: list[HeifDepthImage | None] = (
[HeifDepthImage(i) for i in c_image.depth_image_list if i is not None] if options.DEPTH_IMAGES else []
)
_heif_meta = _get_heif_meta(c_image)
@@ -166,7 +166,7 @@ def __init__(self, c_image):
if _heif_meta:
self.info["heif"] = _heif_meta
save_colorspace_chroma(c_image, self.info)
- _color_profile: Dict[str, Any] = c_image.color_profile
+ _color_profile: dict[str, Any] = c_image.color_profile
if _color_profile:
if _color_profile["type"] in ("rICC", "prof"):
self.info["icc_profile"] = _color_profile["data"]
@@ -248,7 +248,7 @@ def __init__(self, fp=None, convert_hdr_to_8bit=True, bgr_mode=False, **kwargs):
preferred_decoder,
)
self.mimetype = mimetype
- self._images: List[HeifImage] = [HeifImage(i) for i in images if i is not None]
+ self._images: list[HeifImage] = [HeifImage(i) for i in images if i is not None]
self.primary_index = 0
for index, _ in enumerate(self._images):
if _.info.get("primary", False):
@@ -385,7 +385,7 @@ def __delitem__(self, key):
raise IndexError(f"invalid image index: {key}")
del self._images[key]
- def add_frombytes(self, mode: str, size: tuple, data, **kwargs):
+ def add_frombytes(self, mode: str, size: tuple[int, int], data, **kwargs):
"""Adds image from bytes to container.
.. note:: Supports ``stride`` value if needed.
@@ -549,7 +549,7 @@ def read_heif(fp, convert_hdr_to_8bit=True, bgr_mode=False, **kwargs) -> HeifFil
return ret
-def encode(mode: str, size: tuple, data, fp, **kwargs) -> None:
+def encode(mode: str, size: tuple[int, int], data, fp, **kwargs) -> None:
"""Encodes data in a ``fp``.
:param mode: `BGR(A);16`, `RGB(A);16`, LA;16`, `L;16`, `I;16L`, `BGR(A)`, `RGB(A)`, `LA`, `L`
@@ -560,12 +560,12 @@ def encode(mode: str, size: tuple, data, fp, **kwargs) -> None:
_encode_images([HeifImage(MimCImage(mode, size, data, **kwargs))], fp, **kwargs)
-def _encode_images(images: List[HeifImage], fp, **kwargs) -> None:
+def _encode_images(images: list[HeifImage], fp, **kwargs) -> None:
compression = kwargs.get("format", "HEIF")
compression_format = HeifCompressionFormat.AV1 if compression == "AVIF" else HeifCompressionFormat.HEVC
if not _pillow_heif.get_lib_info()[compression]:
raise RuntimeError(f"No {compression} encoder found.")
- images_to_save: List[HeifImage] = images + kwargs.get("append_images", [])
+ images_to_save: list[HeifImage] = images + kwargs.get("append_images", [])
if not kwargs.get("save_all", True):
images_to_save = images_to_save[:1]
if not images_to_save:
@@ -603,7 +603,7 @@ def from_pillow(pil_image: Image.Image) -> HeifFile:
return _
-def from_bytes(mode: str, size: tuple, data, **kwargs) -> HeifFile:
+def from_bytes(mode: str, size: tuple[int, int], data, **kwargs) -> HeifFile:
"""Creates :py:class:`~pillow_heif.HeifFile` from bytes.
.. note:: Supports ``stride`` value if needed.
diff --git a/pillow_heif/misc.py b/pillow_heif/misc.py
index d747030e..375ced85 100644
--- a/pillow_heif/misc.py
+++ b/pillow_heif/misc.py
@@ -3,6 +3,8 @@
Mostly for internal use, so prototypes can change between versions.
"""
+from __future__ import annotations
+
import builtins
import re
from dataclasses import dataclass
@@ -10,7 +12,6 @@
from math import ceil
from pathlib import Path
from struct import pack, unpack
-from typing import List, Optional, Union
from PIL import Image
@@ -93,7 +94,7 @@ def save_colorspace_chroma(c_image, info: dict) -> None:
info["chroma"] = chroma
-def set_orientation(info: dict) -> Optional[int]:
+def set_orientation(info: dict) -> int | None:
"""Reset orientation in ``EXIF`` to ``1`` if any orientation present.
Removes ``XMP`` orientation tag if it is present.
@@ -116,7 +117,7 @@ def _get_orientation_for_encoder(info: dict) -> int:
return 1 if image_orientation is None else image_orientation
-def _get_orientation_xmp(info: dict, exif_orientation: Optional[int], reset: bool = False) -> Optional[int]:
+def _get_orientation_xmp(info: dict, exif_orientation: int | None, reset: bool = False) -> int | None:
xmp_orientation = 1
if info.get("xmp"):
xmp_data = info["xmp"].rsplit(b"\x00", 1)
@@ -141,7 +142,7 @@ def _get_orientation_xmp(info: dict, exif_orientation: Optional[int], reset: boo
return xmp_orientation if exif_orientation is None and xmp_orientation != 1 else None
-def _get_orientation(info: dict, reset: bool = False) -> Optional[int]:
+def _get_orientation(info: dict, reset: bool = False) -> int | None:
original_orientation = None
if info.get("exif"):
try:
@@ -215,7 +216,7 @@ def _get_bytes(fp, length=None) -> bytes:
return bytes(fp)[:length]
-def _retrieve_exif(metadata: List[dict]) -> Optional[bytes]:
+def _retrieve_exif(metadata: list[dict]) -> bytes | None:
_result = None
_purge = []
for i, md_block in enumerate(metadata):
@@ -235,7 +236,7 @@ def _retrieve_exif(metadata: List[dict]) -> Optional[bytes]:
return _result
-def _retrieve_xmp(metadata: List[dict]) -> Optional[bytes]:
+def _retrieve_xmp(metadata: list[dict]) -> bytes | None:
_result = None
_purge = []
for i, md_block in enumerate(metadata):
@@ -248,7 +249,7 @@ def _retrieve_xmp(metadata: List[dict]) -> Optional[bytes]:
return _result
-def _exif_from_pillow(img: Image.Image) -> Optional[bytes]:
+def _exif_from_pillow(img: Image.Image) -> bytes | None:
if "exif" in img.info:
return img.info["exif"]
if hasattr(img, "getexif"): # noqa
@@ -258,7 +259,7 @@ def _exif_from_pillow(img: Image.Image) -> Optional[bytes]:
return None
-def _xmp_from_pillow(img: Image.Image) -> Optional[bytes]:
+def _xmp_from_pillow(img: Image.Image) -> bytes | None:
_xmp = None
if "xmp" in img.info:
_xmp = img.info["xmp"]
@@ -324,7 +325,7 @@ def _rotate_pil(img: Image.Image, orientation: int) -> Image.Image:
return img
-def _get_primary_index(some_iterator, primary_index: Optional[int]) -> int:
+def _get_primary_index(some_iterator, primary_index: int | None) -> int:
primary_attrs = [_.info.get("primary", False) for _ in some_iterator]
if primary_index is None:
primary_index = 0
@@ -336,7 +337,7 @@ def _get_primary_index(some_iterator, primary_index: Optional[int]) -> int:
return primary_index
-def __get_camera_intrinsic_matrix(values: Optional[tuple]):
+def __get_camera_intrinsic_matrix(values: tuple | None):
return (
{
"focal_length_x": values[0],
@@ -383,7 +384,7 @@ def __init__(self, compression_format: HeifCompressionFormat, **kwargs):
_value = value if isinstance(value, str) else str(value)
self.ctx_write.set_parameter(key, _value)
- def add_image(self, size: tuple, mode: str, data, **kwargs) -> None:
+ def add_image(self, size: tuple[int, int], mode: str, data, **kwargs) -> None:
"""Adds image to the encoder."""
if size[0] <= 0 or size[1] <= 0:
raise ValueError("Empty images are not supported.")
@@ -412,7 +413,7 @@ def add_image_ycbcr(self, img: Image.Image, **kwargs) -> None:
im_out.add_plane_l(img.size, 8, 8, bytes(img.getdata(i)), kwargs.get("stride", 0), i)
self._finish_add_image(im_out, img.size, **kwargs)
- def _finish_add_image(self, im_out, size: tuple, **kwargs):
+ def _finish_add_image(self, im_out, size: tuple[int, int], **kwargs):
# set ICC color profile
__icc_profile = kwargs.get("icc_profile")
if __icc_profile is not None:
@@ -468,15 +469,15 @@ def save(self, fp) -> None:
class MimCImage:
"""Mimicry of the HeifImage class."""
- def __init__(self, mode: str, size: tuple, data: bytes, **kwargs):
+ def __init__(self, mode: str, size: tuple[int, int], data: bytes, **kwargs):
self.mode = mode
self.size = size
self.stride: int = kwargs.get("stride", size[0] * MODE_INFO[mode][0] * ceil(MODE_INFO[mode][1] / 8))
self.data = data
- self.metadata: List[dict] = []
+ self.metadata: list[dict] = []
self.color_profile = None
- self.thumbnails: List[int] = []
- self.depth_image_list: List = []
+ self.thumbnails: list[int] = []
+ self.depth_image_list: list = []
self.primary = False
self.chroma = HeifChroma.UNDEFINED.value
self.colorspace = HeifColorspace.UNDEFINED.value
@@ -494,6 +495,6 @@ def bit_depth(self) -> int:
return MODE_INFO[self.mode][1]
-def load_libheif_plugin(plugin_path: Union[str, Path]) -> None:
+def load_libheif_plugin(plugin_path: str | Path) -> None:
"""Load specified LibHeif plugin."""
_pillow_heif.load_plugin(plugin_path)
diff --git a/pyproject.toml b/pyproject.toml
index 87f8c383..96b3e5c7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,10 +7,10 @@ requires = [
[tool.cibuildwheel]
build-verbosity = "2"
build = [
- "cp38-* cp39-* cp310-* cp311-* cp312-* cp313-* pp39-* pp310-*",
+ "cp39-* cp310-* cp311-* cp312-* cp313-* pp39-* pp310-*",
]
skip = [
- "cp36-* cp37-* pp37-* pp38-* cp38-macosx_arm64",
+ "cp36-* cp37-* cp38-* pp37-* pp38-*",
]
test-extras = "tests-min"
test-command = "pytest {project}"
@@ -40,11 +40,11 @@ before-build = [
[tool.black]
line-length = 120
-target-version = [ "py38" ]
+target-version = [ "py39" ]
preview = true
[tool.ruff]
-target-version = "py38"
+target-version = "py39"
line-length = 120
preview = true
lint.select = [
diff --git a/setup.cfg b/setup.cfg
index aba7d0f9..10860169 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -15,11 +15,11 @@ classifiers =
Topic :: Multimedia :: Graphics
Topic :: Multimedia :: Graphics :: Graphics Conversion
Programming Language :: Python :: 3
- Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
+ Programming Language :: Python :: 3.13
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
License :: OSI Approved :: GNU General Public License v2 (GPLv2)
@@ -33,7 +33,7 @@ project_urls =
Changelog=https://github.com/bigcat88/pillow_heif/blob/master/CHANGELOG.md
[options]
-python_requires = >=3.8
+python_requires = >=3.9
zip_safe = False
packages = find:
install_requires =
diff --git a/setup.py b/setup.py
index b40af67a..0cfe105c 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,7 @@
#!/usr/bin/env python
"""Script to build wheel."""
+from __future__ import annotations
+
import os
import re
import subprocess
@@ -7,7 +9,6 @@
from pathlib import Path
from re import finditer
from shutil import copy
-from typing import List
from warnings import warn
from setuptools import Extension, setup
@@ -22,7 +23,7 @@ class RequiredDependencyException(Exception):
"""Raised when no ``libheif`` is found."""
-def get_version():
+def get_version() -> str:
"""Returns version of the project."""
match = re.search(r'__version__\s*=\s*"(.*?)"', Path("pillow_heif/_version.py").read_text(encoding="utf-8"))
return match.group(1)
@@ -35,7 +36,7 @@ def _cmd_exists(cmd: str) -> bool:
return any(os.access(os.path.join(path, cmd), os.X_OK) for path in os.environ["PATH"].split(os.pathsep))
-def _pkg_config(name):
+def _pkg_config(name: str) -> tuple[list[str], list[str]] | None:
command = os.environ.get("PKG_CONFIG", "pkg-config")
for keep_system in (True, False):
try:
@@ -67,7 +68,7 @@ def _pkg_config(name):
class PillowHeifBuildExt(build_ext):
"""Class based on the Pillow setup method."""
- def build_extensions(self): # noqa
+ def build_extensions(self) -> None: # noqa
"""Builds all required python binary extensions of the project."""
if os.getenv("PRE_COMMIT"):
return
@@ -216,7 +217,7 @@ def build_extensions(self): # noqa
build_ext.build_extensions(self)
- def _update_extension(self, name, libraries, extra_compile_args=None, extra_link_args=None):
+ def _update_extension(self, name: str, libraries, extra_compile_args=None, extra_link_args=None) -> None:
for extension in self.extensions:
if extension.name == name:
extension.libraries += libraries
@@ -225,7 +226,7 @@ def _update_extension(self, name, libraries, extra_compile_args=None, extra_link
if extra_link_args is not None:
extension.extra_link_args += extra_link_args
- def _find_include_dir(self, dirname, include):
+ def _find_include_dir(self, dirname: str, include: str):
for directory in self.compiler.include_dirs:
print(f"Checking for include file '{include}' in '{directory}'")
result_path = os.path.join(directory, include)
@@ -241,7 +242,7 @@ def _find_include_dir(self, dirname, include):
return ""
@staticmethod
- def _add_directory(paths: List, subdir):
+ def _add_directory(paths: list[str], subdir: str | None):
if subdir:
subdir = os.path.realpath(subdir)
if os.path.isdir(subdir) and subdir not in paths: