mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-08-03 20:10:08 +03:00
Drop support for Python 3.9 (#9119)
Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com> Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
This commit is contained in:
parent
eb59176b09
commit
2ab301dcc9
|
@ -13,24 +13,21 @@ aptget_update()
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
if [[ $(uname) != CYGWIN* ]]; then
|
aptget_update || aptget_update retry || aptget_update retry
|
||||||
aptget_update || aptget_update retry || aptget_update retry
|
|
||||||
fi
|
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [[ $(uname) != CYGWIN* ]]; then
|
sudo apt-get -qq install libfreetype6-dev liblcms2-dev libtiff-dev python3-tk\
|
||||||
sudo apt-get -qq install libfreetype6-dev liblcms2-dev libtiff-dev python3-tk\
|
ghostscript libjpeg-turbo8-dev libopenjp2-7-dev\
|
||||||
ghostscript libjpeg-turbo8-dev libopenjp2-7-dev\
|
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
|
||||||
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
|
sway wl-clipboard libopenblas-dev nasm
|
||||||
sway wl-clipboard libopenblas-dev nasm
|
|
||||||
fi
|
|
||||||
|
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python3 -m pip install --upgrade wheel
|
python3 -m pip install --upgrade wheel
|
||||||
python3 -m pip install coverage
|
python3 -m pip install coverage
|
||||||
python3 -m pip install defusedxml
|
python3 -m pip install defusedxml
|
||||||
python3 -m pip install ipython
|
python3 -m pip install ipython
|
||||||
|
python3 -m pip install numpy
|
||||||
python3 -m pip install olefile
|
python3 -m pip install olefile
|
||||||
python3 -m pip install -U pytest
|
python3 -m pip install -U pytest
|
||||||
python3 -m pip install -U pytest-cov
|
python3 -m pip install -U pytest-cov
|
||||||
|
@ -40,36 +37,24 @@ python3 -m pip install pyroma
|
||||||
# fails on beta 3.14 and PyPy
|
# fails on beta 3.14 and PyPy
|
||||||
python3 -m pip install --only-binary=:all: pyarrow || true
|
python3 -m pip install --only-binary=:all: pyarrow || true
|
||||||
|
|
||||||
if [[ $(uname) != CYGWIN* ]]; then
|
# PyQt6 doesn't support PyPy3
|
||||||
python3 -m pip install numpy
|
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||||
|
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
|
||||||
# PyQt6 doesn't support PyPy3
|
# TODO Update condition when pyqt6 supports free-threading
|
||||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
if ! [[ "$PYTHON_GIL" == "0" ]]; then python3 -m pip install pyqt6 ; fi
|
||||||
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
|
|
||||||
# TODO Update condition when pyqt6 supports free-threading
|
|
||||||
if ! [[ "$PYTHON_GIL" == "0" ]]; then python3 -m pip install pyqt6 ; fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Pyroma uses non-isolated build and fails with old setuptools
|
|
||||||
if [[ $GHA_PYTHON_VERSION == 3.9 ]]; then
|
|
||||||
# To match pyproject.toml
|
|
||||||
python3 -m pip install "setuptools>=77"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# webp
|
|
||||||
pushd depends && ./install_webp.sh && popd
|
|
||||||
|
|
||||||
# libimagequant
|
|
||||||
pushd depends && ./install_imagequant.sh && popd
|
|
||||||
|
|
||||||
# raqm
|
|
||||||
pushd depends && ./install_raqm.sh && popd
|
|
||||||
|
|
||||||
# libavif
|
|
||||||
pushd depends && ./install_libavif.sh && popd
|
|
||||||
|
|
||||||
# extra test images
|
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
|
||||||
else
|
|
||||||
cd depends && ./install_extra_test_images.sh && cd ..
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# webp
|
||||||
|
pushd depends && ./install_webp.sh && popd
|
||||||
|
|
||||||
|
# libimagequant
|
||||||
|
pushd depends && ./install_imagequant.sh && popd
|
||||||
|
|
||||||
|
# raqm
|
||||||
|
pushd depends && ./install_raqm.sh && popd
|
||||||
|
|
||||||
|
# libavif
|
||||||
|
pushd depends && ./install_libavif.sh && popd
|
||||||
|
|
||||||
|
# extra test images
|
||||||
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
1
.github/mergify.yml
vendored
1
.github/mergify.yml
vendored
|
@ -8,7 +8,6 @@ pull_request_rules:
|
||||||
- status-success=Docker Test Successful
|
- status-success=Docker Test Successful
|
||||||
- status-success=Windows Test Successful
|
- status-success=Windows Test Successful
|
||||||
- status-success=MinGW
|
- status-success=MinGW
|
||||||
- status-success=Cygwin Test Successful
|
|
||||||
actions:
|
actions:
|
||||||
merge:
|
merge:
|
||||||
method: merge
|
method: merge
|
||||||
|
|
150
.github/workflows/test-cygwin.yml
vendored
150
.github/workflows/test-cygwin.yml
vendored
|
@ -1,150 +0,0 @@
|
||||||
name: Test Cygwin
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "**"
|
|
||||||
paths-ignore:
|
|
||||||
- ".github/workflows/docs.yml"
|
|
||||||
- ".github/workflows/wheels*"
|
|
||||||
- ".gitmodules"
|
|
||||||
- "docs/**"
|
|
||||||
- "wheels/**"
|
|
||||||
pull_request:
|
|
||||||
paths-ignore:
|
|
||||||
- ".github/workflows/docs.yml"
|
|
||||||
- ".github/workflows/wheels*"
|
|
||||||
- ".gitmodules"
|
|
||||||
- "docs/**"
|
|
||||||
- "wheels/**"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
env:
|
|
||||||
COVERAGE_CORE: sysmon
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: windows-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
python-minor-version: [9]
|
|
||||||
|
|
||||||
timeout-minutes: 40
|
|
||||||
|
|
||||||
name: Python 3.${{ matrix.python-minor-version }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Fix line endings
|
|
||||||
run: |
|
|
||||||
git config --global core.autocrlf input
|
|
||||||
|
|
||||||
- name: Checkout Pillow
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Install Cygwin
|
|
||||||
uses: cygwin/cygwin-install-action@v6
|
|
||||||
with:
|
|
||||||
packages: >
|
|
||||||
gcc-g++
|
|
||||||
ghostscript
|
|
||||||
git
|
|
||||||
ImageMagick
|
|
||||||
jpeg
|
|
||||||
libfreetype-devel
|
|
||||||
libimagequant-devel
|
|
||||||
libjpeg-devel
|
|
||||||
liblapack-devel
|
|
||||||
liblcms2-devel
|
|
||||||
libopenjp2-devel
|
|
||||||
libraqm-devel
|
|
||||||
libtiff-devel
|
|
||||||
libwebp-devel
|
|
||||||
libxcb-devel
|
|
||||||
libxcb-xinerama0
|
|
||||||
make
|
|
||||||
netpbm
|
|
||||||
perl
|
|
||||||
python3${{ matrix.python-minor-version }}-cython
|
|
||||||
python3${{ matrix.python-minor-version }}-devel
|
|
||||||
python3${{ matrix.python-minor-version }}-ipython
|
|
||||||
python3${{ matrix.python-minor-version }}-numpy
|
|
||||||
python3${{ matrix.python-minor-version }}-sip
|
|
||||||
python3${{ matrix.python-minor-version }}-tkinter
|
|
||||||
wget
|
|
||||||
xorg-server-extra
|
|
||||||
zlib-devel
|
|
||||||
|
|
||||||
- name: Add Lapack to PATH
|
|
||||||
uses: egor-tensin/cleanup-path@v4
|
|
||||||
with:
|
|
||||||
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
|
|
||||||
|
|
||||||
- name: pip cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
|
||||||
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-${{ hashFiles('.ci/install.sh') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-
|
|
||||||
|
|
||||||
- name: Build system information
|
|
||||||
run: |
|
|
||||||
dash.exe -c "python3 .github/workflows/system-info.py"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
bash.exe .ci/install.sh
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
shell: bash.exe -eo pipefail -o igncr "{0}"
|
|
||||||
run: |
|
|
||||||
.ci/build.sh
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
bash.exe xvfb-run -s '-screen 0 1024x768x24' .ci/test.sh
|
|
||||||
|
|
||||||
- name: Prepare to upload errors
|
|
||||||
if: failure()
|
|
||||||
run: |
|
|
||||||
dash.exe -c "mkdir -p Tests/errors"
|
|
||||||
|
|
||||||
- name: Upload errors
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
if: failure()
|
|
||||||
with:
|
|
||||||
name: errors
|
|
||||||
path: Tests/errors
|
|
||||||
|
|
||||||
- name: After success
|
|
||||||
run: |
|
|
||||||
bash.exe .ci/after_success.sh
|
|
||||||
rm C:\cygwin\bin\bash.EXE
|
|
||||||
|
|
||||||
- name: Upload coverage
|
|
||||||
uses: codecov/codecov-action@v5
|
|
||||||
with:
|
|
||||||
files: ./coverage.xml
|
|
||||||
flags: GHA_Cygwin
|
|
||||||
name: Cygwin Python 3.${{ matrix.python-minor-version }}
|
|
||||||
token: ${{ secrets.CODECOV_ORG_TOKEN }}
|
|
||||||
|
|
||||||
success:
|
|
||||||
permissions:
|
|
||||||
contents: none
|
|
||||||
needs: build
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Cygwin Test Successful
|
|
||||||
steps:
|
|
||||||
- name: Success
|
|
||||||
run: echo Cygwin Test Successful
|
|
4
.github/workflows/test-windows.yml
vendored
4
.github/workflows/test-windows.yml
vendored
|
@ -35,11 +35,11 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["pypy3.11", "3.10", "3.11", "3.12", ">=3.13.5", "3.14"]
|
python-version: ["pypy3.11", "3.11", "3.12", "3.13", "3.14"]
|
||||||
architecture: ["x64"]
|
architecture: ["x64"]
|
||||||
include:
|
include:
|
||||||
# Test the oldest Python on 32-bit
|
# Test the oldest Python on 32-bit
|
||||||
- { python-version: "3.9", architecture: "x86" }
|
- { python-version: "3.10", architecture: "x86" }
|
||||||
|
|
||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
|
|
||||||
|
|
11
.github/workflows/test.yml
vendored
11
.github/workflows/test.yml
vendored
|
@ -49,18 +49,17 @@ jobs:
|
||||||
"3.12",
|
"3.12",
|
||||||
"3.11",
|
"3.11",
|
||||||
"3.10",
|
"3.10",
|
||||||
"3.9",
|
|
||||||
]
|
]
|
||||||
include:
|
include:
|
||||||
- { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
|
- { python-version: "3.12", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
|
||||||
- { python-version: "3.10", PYTHONOPTIMIZE: 2 }
|
- { python-version: "3.11", PYTHONOPTIMIZE: 2 }
|
||||||
# Free-threaded
|
# Free-threaded
|
||||||
- { python-version: "3.14t", disable-gil: true }
|
- { python-version: "3.14t", disable-gil: true }
|
||||||
- { python-version: "3.13t", disable-gil: true }
|
- { python-version: "3.13t", disable-gil: true }
|
||||||
# M1 only available for 3.10+
|
# Intel
|
||||||
- { os: "macos-13", python-version: "3.9" }
|
- { os: "macos-13", python-version: "3.10" }
|
||||||
exclude:
|
exclude:
|
||||||
- { os: "macos-latest", python-version: "3.9" }
|
- { os: "macos-latest", python-version: "3.10" }
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||||
|
|
|
@ -36,9 +36,6 @@ As of 2019, Pillow development is
|
||||||
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-mingw.yml"><img
|
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-mingw.yml"><img
|
||||||
alt="GitHub Actions build status (Test MinGW)"
|
alt="GitHub Actions build status (Test MinGW)"
|
||||||
src="https://github.com/python-pillow/Pillow/workflows/Test%20MinGW/badge.svg"></a>
|
src="https://github.com/python-pillow/Pillow/workflows/Test%20MinGW/badge.svg"></a>
|
||||||
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-cygwin.yml"><img
|
|
||||||
alt="GitHub Actions build status (Test Cygwin)"
|
|
||||||
src="https://github.com/python-pillow/Pillow/workflows/Test%20Cygwin/badge.svg"></a>
|
|
||||||
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-docker.yml"><img
|
<a href="https://github.com/python-pillow/Pillow/actions/workflows/test-docker.yml"><img
|
||||||
alt="GitHub Actions build status (Test Docker)"
|
alt="GitHub Actions build status (Test Docker)"
|
||||||
src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
|
src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
|
||||||
|
|
|
@ -10,17 +10,20 @@ import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from collections.abc import Sequence
|
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, Callable
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from packaging.version import parse as parse_version
|
from packaging.version import parse as parse_version
|
||||||
|
|
||||||
from PIL import Image, ImageFile, ImageMath, features
|
from PIL import Image, ImageFile, ImageMath, features
|
||||||
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable, Sequence
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
uploader = None
|
uploader = None
|
||||||
|
|
|
@ -2,7 +2,6 @@ from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import re
|
import re
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -10,6 +9,10 @@ from PIL import features
|
||||||
|
|
||||||
from .helper import skip_unless_feature
|
from .helper import skip_unless_feature
|
||||||
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
|
||||||
def test_check() -> None:
|
def test_check() -> None:
|
||||||
# Check the correctness of the convenience function
|
# Check the correctness of the convenience function
|
||||||
|
|
|
@ -2,12 +2,15 @@ from __future__ import annotations
|
||||||
|
|
||||||
import colorsys
|
import colorsys
|
||||||
import itertools
|
import itertools
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import assert_image_similar, hopper
|
from .helper import assert_image_similar, hopper
|
||||||
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
|
||||||
def int_to_float(i: int) -> float:
|
def int_to_float(i: int) -> float:
|
||||||
return i / 255
|
return i / 255
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import math
|
import math
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -9,6 +8,10 @@ from PIL import Image, ImageTransform
|
||||||
|
|
||||||
from .helper import assert_image_equal, assert_image_similar, hopper
|
from .helper import assert_image_equal, assert_image_similar, hopper
|
||||||
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
|
||||||
class TestImageTransform:
|
class TestImageTransform:
|
||||||
def test_sanity(self) -> None:
|
def test_sanity(self) -> None:
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
from PIL import Image, ImageChops
|
from PIL import Image, ImageChops
|
||||||
|
|
||||||
from .helper import assert_image_equal, hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
BLACK = (0, 0, 0)
|
BLACK = (0, 0, 0)
|
||||||
BROWN = (127, 64, 0)
|
BROWN = (127, 64, 0)
|
||||||
CYAN = (0, 255, 255)
|
CYAN = (0, 255, 255)
|
||||||
|
|
|
@ -211,9 +211,10 @@ def test_exceptions() -> None:
|
||||||
ImageCms.getProfileName(None) # type: ignore[arg-type]
|
ImageCms.getProfileName(None) # type: ignore[arg-type]
|
||||||
skip_missing()
|
skip_missing()
|
||||||
|
|
||||||
# Python <= 3.9: "an integer is required (got type NoneType)"
|
with pytest.raises(
|
||||||
# Python > 3.9: "'NoneType' object cannot be interpreted as an integer"
|
ImageCms.PyCMSError,
|
||||||
with pytest.raises(ImageCms.PyCMSError, match="integer"):
|
match="'NoneType' object cannot be interpreted as an integer",
|
||||||
|
):
|
||||||
ImageCms.isIntentSupported(SRGB, None, None) # type: ignore[arg-type]
|
ImageCms.isIntentSupported(SRGB, None, None) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageColor, ImageDraw, ImageFont, features
|
from PIL import Image, ImageColor, ImageDraw, ImageFont, features
|
||||||
from PIL._typing import Coords
|
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
|
@ -17,6 +14,12 @@ from .helper import (
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable, Sequence
|
||||||
|
|
||||||
|
from PIL._typing import Coords
|
||||||
|
|
||||||
BLACK = (0, 0, 0)
|
BLACK = (0, 0, 0)
|
||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
GRAY = (190, 190, 190)
|
GRAY = (190, 190, 190)
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageQt
|
from PIL import Image, ImageQt
|
||||||
|
@ -11,18 +8,8 @@ from .helper import assert_image_equal_tofile, assert_image_similar, hopper
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
TYPE_CHECKING = False
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import PyQt6
|
from pathlib import Path
|
||||||
import PySide6
|
|
||||||
|
|
||||||
QApplication = Union[PyQt6.QtWidgets.QApplication, PySide6.QtWidgets.QApplication]
|
|
||||||
QHBoxLayout = Union[PyQt6.QtWidgets.QHBoxLayout, PySide6.QtWidgets.QHBoxLayout]
|
|
||||||
QImage = Union[PyQt6.QtGui.QImage, PySide6.QtGui.QImage]
|
|
||||||
QLabel = Union[PyQt6.QtWidgets.QLabel, PySide6.QtWidgets.QLabel]
|
|
||||||
QPainter = Union[PyQt6.QtGui.QPainter, PySide6.QtGui.QPainter]
|
|
||||||
QPixmap = Union[PyQt6.QtGui.QPixmap, PySide6.QtGui.QPixmap]
|
|
||||||
QPoint = Union[PyQt6.QtCore.QPoint, PySide6.QtCore.QPoint]
|
|
||||||
QRegion = Union[PyQt6.QtGui.QRegion, PySide6.QtGui.QRegion]
|
|
||||||
QWidget = Union[PyQt6.QtWidgets.QWidget, PySide6.QtWidgets.QWidget]
|
|
||||||
|
|
||||||
if ImageQt.qt_is_installed:
|
if ImageQt.qt_is_installed:
|
||||||
from PIL.ImageQt import QPixmap
|
from PIL.ImageQt import QPixmap
|
||||||
|
@ -32,11 +19,16 @@ if ImageQt.qt_is_installed:
|
||||||
from PyQt6.QtGui import QImage, QPainter, QRegion
|
from PyQt6.QtGui import QImage, QPainter, QRegion
|
||||||
from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
||||||
elif ImageQt.qt_version == "side6":
|
elif ImageQt.qt_version == "side6":
|
||||||
from PySide6.QtCore import QPoint
|
from PySide6.QtCore import QPoint # type: ignore[assignment]
|
||||||
from PySide6.QtGui import QImage, QPainter, QRegion
|
from PySide6.QtGui import QImage, QPainter, QRegion # type: ignore[assignment]
|
||||||
from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
from PySide6.QtWidgets import ( # type: ignore[assignment]
|
||||||
|
QApplication,
|
||||||
|
QHBoxLayout,
|
||||||
|
QLabel,
|
||||||
|
QWidget,
|
||||||
|
)
|
||||||
|
|
||||||
class Example(QWidget): # type: ignore[misc]
|
class Example(QWidget):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
@ -47,9 +39,9 @@ if ImageQt.qt_is_installed:
|
||||||
pixmap1 = getattr(ImageQt.QPixmap, "fromImage")(qimage)
|
pixmap1 = getattr(ImageQt.QPixmap, "fromImage")(qimage)
|
||||||
|
|
||||||
# hbox
|
# hbox
|
||||||
QHBoxLayout(self) # type: ignore[operator]
|
QHBoxLayout(self)
|
||||||
|
|
||||||
lbl = QLabel(self) # type: ignore[operator]
|
lbl = QLabel(self)
|
||||||
# Segfault in the problem
|
# Segfault in the problem
|
||||||
lbl.setPixmap(pixmap1.copy())
|
lbl.setPixmap(pixmap1.copy())
|
||||||
|
|
||||||
|
@ -63,7 +55,7 @@ def roundtrip(expected: Image.Image) -> None:
|
||||||
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
|
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
|
||||||
def test_sanity(tmp_path: Path) -> None:
|
def test_sanity(tmp_path: Path) -> None:
|
||||||
# Segfault test
|
# Segfault test
|
||||||
app: QApplication | None = QApplication([]) # type: ignore[operator]
|
app: QApplication | None = QApplication([])
|
||||||
ex = Example()
|
ex = Example()
|
||||||
assert app # Silence warning
|
assert app # Silence warning
|
||||||
assert ex # Silence warning
|
assert ex # Silence warning
|
||||||
|
@ -84,11 +76,11 @@ def test_sanity(tmp_path: Path) -> None:
|
||||||
imageqt = ImageQt.ImageQt(im)
|
imageqt = ImageQt.ImageQt(im)
|
||||||
data = getattr(QPixmap, "fromImage")(imageqt)
|
data = getattr(QPixmap, "fromImage")(imageqt)
|
||||||
qt_format = getattr(QImage, "Format") if ImageQt.qt_version == "6" else QImage
|
qt_format = getattr(QImage, "Format") if ImageQt.qt_version == "6" else QImage
|
||||||
qimage = QImage(128, 128, getattr(qt_format, "Format_ARGB32")) # type: ignore[operator]
|
qimage = QImage(128, 128, getattr(qt_format, "Format_ARGB32"))
|
||||||
painter = QPainter(qimage) # type: ignore[operator]
|
painter = QPainter(qimage)
|
||||||
image_label = QLabel() # type: ignore[operator]
|
image_label = QLabel()
|
||||||
image_label.setPixmap(data)
|
image_label.setPixmap(data)
|
||||||
image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128)) # type: ignore[operator]
|
image_label.render(painter, QPoint(0, 0), QRegion(0, 0, 128, 128))
|
||||||
painter.end()
|
painter.end()
|
||||||
rendered_tempfile = str(tmp_path / f"temp_rendered_{mode}.png")
|
rendered_tempfile = str(tmp_path / f"temp_rendered_{mode}.png")
|
||||||
qimage.save(rendered_tempfile)
|
qimage.save(rendered_tempfile)
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import ImageQt
|
from PIL import ImageQt
|
||||||
|
|
||||||
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
|
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
|
||||||
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
pytestmark = pytest.mark.skipif(
|
pytestmark = pytest.mark.skipif(
|
||||||
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
|
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
|
||||||
)
|
)
|
||||||
|
@ -21,7 +23,7 @@ def test_sanity(mode: str, tmp_path: Path) -> None:
|
||||||
src = hopper(mode)
|
src = hopper(mode)
|
||||||
data = ImageQt.toqimage(src)
|
data = ImageQt.toqimage(src)
|
||||||
|
|
||||||
assert isinstance(data, QImage) # type: ignore[arg-type, misc]
|
assert isinstance(data, QImage)
|
||||||
assert not data.isNull()
|
assert not data.isNull()
|
||||||
|
|
||||||
# reload directly from the qimage
|
# reload directly from the qimage
|
||||||
|
|
|
@ -2,8 +2,6 @@ from __future__ import annotations
|
||||||
|
|
||||||
import shutil
|
import shutil
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
|
||||||
from typing import IO, Callable
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -11,6 +9,12 @@ from PIL import GifImagePlugin, Image, JpegImagePlugin
|
||||||
|
|
||||||
from .helper import djpeg_available, is_win32, netpbm_available
|
from .helper import djpeg_available, is_win32, netpbm_available
|
||||||
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import IO
|
||||||
|
|
||||||
TEST_JPG = "Tests/images/hopper.jpg"
|
TEST_JPG = "Tests/images/hopper.jpg"
|
||||||
TEST_GIF = "Tests/images/hopper.gif"
|
TEST_GIF = "Tests/images/hopper.gif"
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Callable
|
from collections.abc import Callable
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,6 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
|
||||||
:target: https://github.com/python-pillow/Pillow/actions/workflows/test-mingw.yml
|
:target: https://github.com/python-pillow/Pillow/actions/workflows/test-mingw.yml
|
||||||
:alt: GitHub Actions build status (Test MinGW)
|
:alt: GitHub Actions build status (Test MinGW)
|
||||||
|
|
||||||
.. image:: https://github.com/python-pillow/Pillow/workflows/Test%20Cygwin/badge.svg
|
|
||||||
:target: https://github.com/python-pillow/Pillow/actions/workflows/test-cygwin.yml
|
|
||||||
:alt: GitHub Actions build status (Test Cygwin)
|
|
||||||
|
|
||||||
.. image:: https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg
|
.. image:: https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg
|
||||||
:target: https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml
|
:target: https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml
|
||||||
:alt: GitHub Actions build status (Wheels)
|
:alt: GitHub Actions build status (Wheels)
|
||||||
|
|
|
@ -19,13 +19,13 @@ These platforms are built and tested for every change.
|
||||||
+==================================+============================+=====================+
|
+==================================+============================+=====================+
|
||||||
| Alpine | 3.12 | x86-64 |
|
| Alpine | 3.12 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Amazon Linux 2 | 3.9 | x86-64 |
|
| Amazon Linux 2 | 3.10 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Amazon Linux 2023 | 3.9 | x86-64 |
|
| Amazon Linux 2023 | 3.11 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Arch | 3.13 | x86-64 |
|
| Arch | 3.13 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| CentOS Stream 9 | 3.9 | x86-64 |
|
| CentOS Stream 9 | 3.10 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| CentOS Stream 10 | 3.12 | x86-64 |
|
| CentOS Stream 10 | 3.12 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
|
@ -37,27 +37,25 @@ These platforms are built and tested for every change.
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Gentoo | 3.12 | x86-64 |
|
| Gentoo | 3.12 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| macOS 13 Ventura | 3.9 | x86-64 |
|
| macOS 13 Ventura | 3.10 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 |
|
| macOS 14 Sonoma | 3.11, 3.12, 3.13, 3.14 | arm64 |
|
||||||
| | 3.14, PyPy3 | |
|
| | PyPy3 | |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
|
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Ubuntu Linux 24.04 LTS (Noble) | 3.9, 3.10, 3.11, | x86-64 |
|
| Ubuntu Linux 24.04 LTS (Noble) | 3.10, 3.11, 3.12, 3.13, | x86-64 |
|
||||||
| | 3.12, 3.13, 3.14, PyPy3 | |
|
| | 3.14, PyPy3 | |
|
||||||
| +----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
| | 3.12 | arm64v8, ppc64le, |
|
| | 3.12 | arm64v8, ppc64le, |
|
||||||
| | | s390x |
|
| | | s390x |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Windows Server 2022 | 3.9 | x86 |
|
| Windows Server 2022 | 3.10 | x86 |
|
||||||
| +----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
| | 3.10, 3.11, 3.12, 3.13, | x86-64 |
|
| | 3.11, 3.12, 3.13, 3.14, | x86-64 |
|
||||||
| | 3.14, PyPy3 | |
|
| | PyPy3 | |
|
||||||
| +----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
| | 3.12 (MinGW) | x86-64 |
|
| | 3.12 (MinGW) | x86-64 |
|
||||||
| +----------------------------+---------------------+
|
|
||||||
| | 3.9 (Cygwin) | x86-64 |
|
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -53,11 +53,6 @@ on some Python versions.
|
||||||
|
|
||||||
An object that supports the read method.
|
An object that supports the read method.
|
||||||
|
|
||||||
.. py:data:: TypeGuard
|
|
||||||
:value: typing.TypeGuard
|
|
||||||
|
|
||||||
See :py:obj:`typing.TypeGuard`.
|
|
||||||
|
|
||||||
:mod:`~PIL._util` module
|
:mod:`~PIL._util` module
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,12 @@ TODO
|
||||||
Backwards incompatible changes
|
Backwards incompatible changes
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
|
Python 3.9
|
||||||
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
Pillow has dropped support for Python 3.9,
|
||||||
|
which reached end-of-life in October 2025.
|
||||||
|
|
||||||
ImageFile.raise_oserror
|
ImageFile.raise_oserror
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,10 @@ license-files = [ "LICENSE" ]
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Jeffrey A. Clark", email = "aclark@aclark.net" },
|
{ name = "Jeffrey A. Clark", email = "aclark@aclark.net" },
|
||||||
]
|
]
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.10"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 6 - Mature",
|
"Development Status :: 6 - Mature",
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
"Programming Language :: Python :: 3.9",
|
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
|
@ -76,9 +75,6 @@ optional-dependencies.tests = [
|
||||||
"trove-classifiers>=2024.10.12",
|
"trove-classifiers>=2024.10.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
optional-dependencies.typing = [
|
|
||||||
"typing-extensions; python_version<'3.10'",
|
|
||||||
]
|
|
||||||
optional-dependencies.xmp = [
|
optional-dependencies.xmp = [
|
||||||
"defusedxml",
|
"defusedxml",
|
||||||
]
|
]
|
||||||
|
@ -189,8 +185,8 @@ lint.ignore = [
|
||||||
"PT011", # pytest-raises-too-broad
|
"PT011", # pytest-raises-too-broad
|
||||||
"PT012", # pytest-raises-with-multiple-statements
|
"PT012", # pytest-raises-with-multiple-statements
|
||||||
"PT017", # pytest-assert-in-except
|
"PT017", # pytest-assert-in-except
|
||||||
"PYI026", # flake8-pyi: typing.TypeAlias added in Python 3.10
|
|
||||||
"PYI034", # flake8-pyi: typing.Self added in Python 3.11
|
"PYI034", # flake8-pyi: typing.Self added in Python 3.11
|
||||||
|
"UP038", # pyupgrade: deprecated rule
|
||||||
]
|
]
|
||||||
lint.per-file-ignores."Tests/oss-fuzz/fuzz_font.py" = [
|
lint.per-file-ignores."Tests/oss-fuzz/fuzz_font.py" = [
|
||||||
"I002",
|
"I002",
|
||||||
|
@ -216,7 +212,7 @@ testpaths = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
python_version = "3.9"
|
python_version = "3.10"
|
||||||
pretty = true
|
pretty = true
|
||||||
disallow_any_generics = true
|
disallow_any_generics = true
|
||||||
enable_error_code = "ignore-without-code"
|
enable_error_code = "ignore-without-code"
|
||||||
|
|
|
@ -31,7 +31,7 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import IO, Any, Literal, NamedTuple, Union, cast
|
from typing import Any, NamedTuple, cast
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
Image,
|
Image,
|
||||||
|
@ -49,6 +49,8 @@ from ._util import DeferredError
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
TYPE_CHECKING = False
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from typing import IO, Literal
|
||||||
|
|
||||||
from . import _imaging
|
from . import _imaging
|
||||||
from ._typing import Buffer
|
from ._typing import Buffer
|
||||||
|
|
||||||
|
@ -535,7 +537,7 @@ def _normalize_mode(im: Image.Image) -> Image.Image:
|
||||||
return im.convert("L")
|
return im.convert("L")
|
||||||
|
|
||||||
|
|
||||||
_Palette = Union[bytes, bytearray, list[int], ImagePalette.ImagePalette]
|
_Palette = bytes | bytearray | list[int] | ImagePalette.ImagePalette
|
||||||
|
|
||||||
|
|
||||||
def _normalize_palette(
|
def _normalize_palette(
|
||||||
|
|
|
@ -21,10 +21,14 @@ See the GIMP distribution for more information.)
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from math import log, pi, sin, sqrt
|
from math import log, pi, sin, sqrt
|
||||||
from typing import IO, Callable
|
|
||||||
|
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
from typing import IO
|
||||||
|
|
||||||
EPSILON = 1e-10
|
EPSILON = 1e-10
|
||||||
"""""" # Enable auto-doc for data member
|
"""""" # Enable auto-doc for data member
|
||||||
|
|
||||||
|
|
|
@ -34,20 +34,23 @@ from __future__ import annotations
|
||||||
import math
|
import math
|
||||||
import struct
|
import struct
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from types import ModuleType
|
from typing import cast
|
||||||
from typing import Any, AnyStr, Callable, Union, cast
|
|
||||||
|
|
||||||
from . import Image, ImageColor
|
from . import Image, ImageColor
|
||||||
from ._typing import Coords
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
from types import ModuleType
|
||||||
|
from typing import Any, AnyStr
|
||||||
|
|
||||||
|
from . import ImageDraw2, ImageFont
|
||||||
|
from ._typing import Coords
|
||||||
|
|
||||||
# experimental access to the outline API
|
# experimental access to the outline API
|
||||||
Outline: Callable[[], Image.core._Outline] = Image.core.outline
|
Outline: Callable[[], Image.core._Outline] = Image.core.outline
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
_Ink = float | tuple[int, ...] | str
|
||||||
if TYPE_CHECKING:
|
|
||||||
from . import ImageDraw2, ImageFont
|
|
||||||
|
|
||||||
_Ink = Union[float, tuple[int, ...], str]
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A simple 2D drawing interface for PIL images.
|
A simple 2D drawing interface for PIL images.
|
||||||
|
|
|
@ -19,11 +19,14 @@ from __future__ import annotations
|
||||||
import abc
|
import abc
|
||||||
import functools
|
import functools
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from types import ModuleType
|
from typing import cast
|
||||||
from typing import Any, Callable, cast
|
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
TYPE_CHECKING = False
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
from types import ModuleType
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from . import _imaging
|
from . import _imaging
|
||||||
from ._typing import NumpyArray
|
from ._typing import NumpyArray
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,15 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import builtins
|
import builtins
|
||||||
from types import CodeType
|
|
||||||
from typing import Any, Callable
|
|
||||||
|
|
||||||
from . import Image, _imagingmath
|
from . import Image, _imagingmath
|
||||||
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
from types import CodeType
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class _Operand:
|
class _Operand:
|
||||||
"""Wraps an image operand, providing standard operators"""
|
"""Wraps an image operand, providing standard operators"""
|
||||||
|
|
|
@ -19,23 +19,18 @@ from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Any, Callable, Union
|
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
from ._util import is_path
|
from ._util import is_path
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
TYPE_CHECKING = False
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import PyQt6
|
from collections.abc import Callable
|
||||||
import PySide6
|
from typing import Any
|
||||||
|
|
||||||
from . import ImageFile
|
from . import ImageFile
|
||||||
|
|
||||||
QBuffer: type
|
QBuffer: type
|
||||||
QByteArray = Union[PyQt6.QtCore.QByteArray, PySide6.QtCore.QByteArray]
|
|
||||||
QIODevice = Union[PyQt6.QtCore.QIODevice, PySide6.QtCore.QIODevice]
|
|
||||||
QImage = Union[PyQt6.QtGui.QImage, PySide6.QtGui.QImage]
|
|
||||||
QPixmap = Union[PyQt6.QtGui.QPixmap, PySide6.QtGui.QPixmap]
|
|
||||||
|
|
||||||
qt_version: str | None
|
qt_version: str | None
|
||||||
qt_versions = [
|
qt_versions = [
|
||||||
|
@ -49,11 +44,15 @@ for version, qt_module in qt_versions:
|
||||||
try:
|
try:
|
||||||
qRgba: Callable[[int, int, int, int], int]
|
qRgba: Callable[[int, int, int, int], int]
|
||||||
if qt_module == "PyQt6":
|
if qt_module == "PyQt6":
|
||||||
from PyQt6.QtCore import QBuffer, QIODevice
|
from PyQt6.QtCore import QBuffer, QByteArray, QIODevice
|
||||||
from PyQt6.QtGui import QImage, QPixmap, qRgba
|
from PyQt6.QtGui import QImage, QPixmap, qRgba
|
||||||
elif qt_module == "PySide6":
|
elif qt_module == "PySide6":
|
||||||
from PySide6.QtCore import QBuffer, QIODevice
|
from PySide6.QtCore import ( # type: ignore[assignment]
|
||||||
from PySide6.QtGui import QImage, QPixmap, qRgba
|
QBuffer,
|
||||||
|
QByteArray,
|
||||||
|
QIODevice,
|
||||||
|
)
|
||||||
|
from PySide6.QtGui import QImage, QPixmap, qRgba # type: ignore[assignment]
|
||||||
except (ImportError, RuntimeError):
|
except (ImportError, RuntimeError):
|
||||||
continue
|
continue
|
||||||
qt_is_installed = True
|
qt_is_installed = True
|
||||||
|
@ -183,7 +182,7 @@ def _toqclass_helper(im: Image.Image | str | QByteArray) -> dict[str, Any]:
|
||||||
|
|
||||||
if qt_is_installed:
|
if qt_is_installed:
|
||||||
|
|
||||||
class ImageQt(QImage): # type: ignore[misc]
|
class ImageQt(QImage):
|
||||||
def __init__(self, im: Image.Image | str | QByteArray) -> None:
|
def __init__(self, im: Image.Image | str | QByteArray) -> None:
|
||||||
"""
|
"""
|
||||||
An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
|
An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
|
||||||
|
|
|
@ -16,10 +16,12 @@
|
||||||
##
|
##
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
|
||||||
class Iterator:
|
class Iterator:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
from typing import BinaryIO, Callable
|
|
||||||
|
|
||||||
from . import FontFile, Image
|
from . import FontFile, Image
|
||||||
from ._binary import i8
|
from ._binary import i8
|
||||||
|
@ -27,6 +26,11 @@ from ._binary import i16le as l16
|
||||||
from ._binary import i32be as b32
|
from ._binary import i32be as b32
|
||||||
from ._binary import i32le as l32
|
from ._binary import i32le as l32
|
||||||
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
from typing import BinaryIO
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# declarations
|
# declarations
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,15 @@ import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import zlib
|
import zlib
|
||||||
from typing import IO, Any, NamedTuple, Union
|
from typing import Any, NamedTuple
|
||||||
|
|
||||||
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import IO
|
||||||
|
|
||||||
|
_DictBase = collections.UserDict[str | bytes, Any]
|
||||||
|
else:
|
||||||
|
_DictBase = collections.UserDict
|
||||||
|
|
||||||
|
|
||||||
# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set
|
# see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set
|
||||||
|
@ -251,13 +259,6 @@ class PdfArray(list[Any]):
|
||||||
return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]"
|
return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]"
|
||||||
|
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
_DictBase = collections.UserDict[Union[str, bytes], Any]
|
|
||||||
else:
|
|
||||||
_DictBase = collections.UserDict
|
|
||||||
|
|
||||||
|
|
||||||
class PdfDict(_DictBase):
|
class PdfDict(_DictBase):
|
||||||
def __setattr__(self, key: str, value: Any) -> None:
|
def __setattr__(self, key: str, value: Any) -> None:
|
||||||
if key == "data":
|
if key == "data":
|
||||||
|
|
|
@ -47,22 +47,24 @@ import math
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import warnings
|
import warnings
|
||||||
from collections.abc import Iterator, MutableMapping
|
from collections.abc import Callable, MutableMapping
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
from numbers import Number, Rational
|
from numbers import Number, Rational
|
||||||
from typing import IO, Any, Callable, NoReturn, cast
|
from typing import IO, Any, cast
|
||||||
|
|
||||||
from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags
|
from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags
|
||||||
from ._binary import i16be as i16
|
from ._binary import i16be as i16
|
||||||
from ._binary import i32be as i32
|
from ._binary import i32be as i32
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
from ._typing import StrOrBytesPath
|
|
||||||
from ._util import DeferredError, is_path
|
from ._util import DeferredError, is_path
|
||||||
from .TiffTags import TYPES
|
from .TiffTags import TYPES
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
TYPE_CHECKING = False
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ._typing import Buffer, IntegralLike
|
from collections.abc import Iterator
|
||||||
|
from typing import NoReturn
|
||||||
|
|
||||||
|
from ._typing import Buffer, IntegralLike, StrOrBytesPath
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import datetime
|
import datetime
|
||||||
import sys
|
import sys
|
||||||
from typing import Literal, SupportsFloat, TypedDict
|
from typing import Literal, SupportsFloat, TypeAlias, TypedDict
|
||||||
|
|
||||||
from ._typing import CapsuleType
|
from ._typing import CapsuleType
|
||||||
|
|
||||||
littlecms_version: str | None
|
littlecms_version: str | None
|
||||||
|
|
||||||
_Tuple3f = tuple[float, float, float]
|
_Tuple3f: TypeAlias = tuple[float, float, float]
|
||||||
_Tuple2x3f = tuple[_Tuple3f, _Tuple3f]
|
_Tuple2x3f: TypeAlias = tuple[_Tuple3f, _Tuple3f]
|
||||||
_Tuple3x3f = tuple[_Tuple3f, _Tuple3f, _Tuple3f]
|
_Tuple3x3f: TypeAlias = tuple[_Tuple3f, _Tuple3f, _Tuple3f]
|
||||||
|
|
||||||
class _IccMeasurementCondition(TypedDict):
|
class _IccMeasurementCondition(TypedDict):
|
||||||
observer: int
|
observer: int
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from typing import Any, Callable
|
from collections.abc import Callable
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from . import ImageFont, _imaging
|
from . import ImageFont, _imaging
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from typing import Any, Protocol, TypeVar, Union
|
from typing import Any, Protocol, TypeVar
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
TYPE_CHECKING = False
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -26,19 +26,8 @@ if sys.version_info >= (3, 12):
|
||||||
else:
|
else:
|
||||||
Buffer = Any
|
Buffer = Any
|
||||||
|
|
||||||
if sys.version_info >= (3, 10):
|
|
||||||
from typing import TypeGuard
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
from typing_extensions import TypeGuard
|
|
||||||
except ImportError:
|
|
||||||
|
|
||||||
class TypeGuard: # type: ignore[no-redef]
|
Coords = Sequence[float] | Sequence[Sequence[float]]
|
||||||
def __class_getitem__(cls, item: Any) -> type[bool]:
|
|
||||||
return bool
|
|
||||||
|
|
||||||
|
|
||||||
Coords = Union[Sequence[float], Sequence[Sequence[float]]]
|
|
||||||
|
|
||||||
|
|
||||||
_T_co = TypeVar("_T_co", covariant=True)
|
_T_co = TypeVar("_T_co", covariant=True)
|
||||||
|
@ -48,7 +37,7 @@ class SupportsRead(Protocol[_T_co]):
|
||||||
def read(self, length: int = ..., /) -> _T_co: ...
|
def read(self, length: int = ..., /) -> _T_co: ...
|
||||||
|
|
||||||
|
|
||||||
StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]]
|
StrOrBytesPath = str | bytes | os.PathLike[str] | os.PathLike[bytes]
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"]
|
__all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead"]
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Any, NoReturn
|
|
||||||
|
|
||||||
from ._typing import StrOrBytesPath, TypeGuard
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import Any, NoReturn, TypeGuard
|
||||||
|
|
||||||
|
from ._typing import StrOrBytesPath
|
||||||
|
|
||||||
|
|
||||||
def is_path(f: Any) -> TypeGuard[StrOrBytesPath]:
|
def is_path(f: Any) -> TypeGuard[StrOrBytesPath]:
|
||||||
|
|
4
tox.ini
4
tox.ini
|
@ -3,7 +3,7 @@ requires =
|
||||||
tox>=4.2
|
tox>=4.2
|
||||||
env_list =
|
env_list =
|
||||||
lint
|
lint
|
||||||
py{py3, 314, 313, 312, 311, 310, 39}
|
py{py3, 314, 313, 312, 311, 310}
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps =
|
deps =
|
||||||
|
@ -29,7 +29,5 @@ commands =
|
||||||
skip_install = true
|
skip_install = true
|
||||||
deps =
|
deps =
|
||||||
-r .ci/requirements-mypy.txt
|
-r .ci/requirements-mypy.txt
|
||||||
extras =
|
|
||||||
typing
|
|
||||||
commands =
|
commands =
|
||||||
mypy conftest.py selftest.py setup.py docs src winbuild Tests {posargs}
|
mypy conftest.py selftest.py setup.py docs src winbuild Tests {posargs}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user