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: