Skip to content

Commit

Permalink
Merge branch 'main' into type_hints
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere committed May 30, 2024
2 parents b2ce2f6 + c757439 commit 6affb12
Show file tree
Hide file tree
Showing 110 changed files with 1,113 additions and 851 deletions.
2 changes: 1 addition & 1 deletion .ci/requirements-cibw.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
cibuildwheel==2.17.0
cibuildwheel==2.18.1
1 change: 1 addition & 0 deletions .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ BinPackParameters: false
BreakBeforeBraces: Attach
ColumnLimit: 88
DerivePointerAlignment: false
IndentGotoLabels: false
IndentWidth: 4
Language: Cpp
PointerAlignment: Right
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/wheels-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then
else
yum install -y fribidi
fi
if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ]; then
if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ] && !([[ "$OSTYPE" == "darwin"* ]] && [[ $(python3 --version) == *"3.13."* ]]); then
python3 -m pip install numpy
fi

Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
- cp310
- cp311
- cp312
- cp313
spec:
- manylinux2014
- manylinux_2_28
Expand Down Expand Up @@ -80,6 +81,7 @@ jobs:
CIBW_ARCHS: "aarch64"
# Likewise, select only one Python version per job to speed this up.
CIBW_BUILD: "${{ matrix.python-version }}-${{ matrix.spec == 'musllinux' && 'musllinux' || 'manylinux' }}*"
CIBW_PRERELEASE_PYTHONS: True
# Extra options for manylinux.
CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.spec }}
CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.spec }}
Expand Down Expand Up @@ -133,6 +135,7 @@ jobs:
CIBW_BUILD: ${{ matrix.build }}
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_PRERELEASE_PYTHONS: True
CIBW_SKIP: pp38-*
CIBW_TEST_SKIP: cp38-macosx_arm64
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
Expand Down Expand Up @@ -204,6 +207,7 @@ jobs:
CIBW_ARCHS: ${{ matrix.cibw_arch }}
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
CIBW_CACHE_PATH: "C:\\cibw"
CIBW_PRERELEASE_PYTHONS: True
CIBW_SKIP: pp38-*
CIBW_TEST_SKIP: "*-win_arm64"
CIBW_TEST_COMMAND: 'docker run --rm
Expand Down
17 changes: 12 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.4
rev: v0.4.3
hooks:
- id: ruff
args: [--exit-non-zero-on-fix]

- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.3.0
rev: 24.4.2
hooks:
- id: black

Expand All @@ -23,13 +23,20 @@ repos:
- id: remove-tabs
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)

- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v18.1.4
hooks:
- id: clang-format
types: [c]
exclude: ^src/thirdparty/

- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- id: rst-backticks

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.6.0
hooks:
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
Expand All @@ -43,7 +50,7 @@ repos:
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/

- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.28.1
rev: 0.28.2
hooks:
- id: check-github-workflows
- id: check-readthedocs
Expand All @@ -55,7 +62,7 @@ repos:
- id: sphinx-lint

- repo: https://github.com/tox-dev/pyproject-fmt
rev: 1.7.0
rev: 1.8.0
hooks:
- id: pyproject-fmt

Expand Down
9 changes: 9 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ Changelog (Pillow)
10.4.0 (unreleased)
-------------------

- Added ImageDraw circle() #8085
[void4, hugovk, radarhere]

- Add mypy target to Makefile #8077
[Yay295]

- Added more modes to Image.MODES #7984
[radarhere]

- Deprecate BGR;15, BGR;16 and BGR;24 modes #7978
[radarhere, hugovk]

Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,8 @@ lint-fix:
python3 -m black .
python3 -c "import ruff" > /dev/null 2>&1 || python3 -m pip install ruff
python3 -m ruff --fix .

.PHONY: mypy
mypy:
python3 -c "import tox" > /dev/null 2>&1 || python3 -m pip install tox
python3 -m tox -e mypy
52 changes: 11 additions & 41 deletions Tests/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,33 +29,6 @@
uploader = "github_actions"


modes = (
"1",
"L",
"LA",
"La",
"P",
"PA",
"F",
"I",
"I;16",
"I;16L",
"I;16B",
"I;16N",
"RGB",
"RGBA",
"RGBa",
"RGBX",
"BGR;15",
"BGR;16",
"BGR;24",
"CMYK",
"YCbCr",
"HSV",
"LAB",
)


def upload(a: Image.Image, b: Image.Image) -> str | None:
if uploader == "show":
# local img.show for errors.
Expand Down Expand Up @@ -201,12 +174,13 @@ def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
def skip_unless_feature_version(
feature: str, required: str, reason: str | None = None
) -> pytest.MarkDecorator:
if not features.check(feature):
version = features.version(feature)
if version is None:
return pytest.mark.skip(f"{feature} not available")
if reason is None:
reason = f"{feature} is older than {required}"
version_required = parse_version(required)
version_available = parse_version(features.version(feature))
version_available = parse_version(version)
return pytest.mark.skipif(version_available < version_required, reason=reason)


Expand All @@ -216,12 +190,13 @@ def mark_if_feature_version(
version_blacklist: str,
reason: str | None = None,
) -> pytest.MarkDecorator:
if not features.check(feature):
version = features.version(feature)
if version is None:
return pytest.mark.pil_noop_mark()
if reason is None:
reason = f"{feature} is {version_blacklist}"
version_required = parse_version(version_blacklist)
version_available = parse_version(features.version(feature))
version_available = parse_version(version)
if (
version_available.major == version_required.major
and version_available.minor == version_required.minor
Expand All @@ -247,16 +222,11 @@ def _get_mem_usage(self) -> float:
from resource import RUSAGE_SELF, getrusage

mem = getrusage(RUSAGE_SELF).ru_maxrss
if sys.platform == "darwin":
# man 2 getrusage:
# ru_maxrss
# This is the maximum resident set size utilized (in bytes).
return mem / 1024 # Kb
# linux
# man 2 getrusage
# ru_maxrss (since Linux 2.6.32)
# This is the maximum resident set size used (in kilobytes).
return mem # Kb
# man 2 getrusage:
# ru_maxrss
# This is the maximum resident set size utilized
# in bytes on macOS, in kilobytes on Linux
return mem / 1024 if sys.platform == "darwin" else mem

def _test_leak(self, core: Callable[[], None]) -> None:
start_mem = self._get_mem_usage()
Expand Down
5 changes: 3 additions & 2 deletions Tests/oss-fuzz/test_fuzzers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@

if sys.platform.startswith("win32"):
pytest.skip("Fuzzer is linux only", allow_module_level=True)
if features.check("libjpeg_turbo"):
version = packaging.version.parse(features.version("libjpeg_turbo"))
libjpeg_turbo_version = features.version("libjpeg_turbo")
if libjpeg_turbo_version is not None:
version = packaging.version.parse(libjpeg_turbo_version)
if version.major == 2 and version.minor == 0:
pytestmark = pytest.mark.valgrind_known_error(
reason="Known failing with libjpeg_turbo 2.0"
Expand Down
10 changes: 7 additions & 3 deletions Tests/test_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test_version() -> None:
# Check the correctness of the convenience function
# and the format of version numbers

def test(name: str, function: Callable[[str], bool]) -> None:
def test(name: str, function: Callable[[str], str | None]) -> None:
version = features.version(name)
if not features.check(name):
assert version is None
Expand Down Expand Up @@ -67,12 +67,16 @@ def test_webp_anim() -> None:

@skip_unless_feature("libjpeg_turbo")
def test_libjpeg_turbo_version() -> None:
assert re.search(r"\d+\.\d+\.\d+$", features.version("libjpeg_turbo"))
version = features.version("libjpeg_turbo")
assert version is not None
assert re.search(r"\d+\.\d+\.\d+$", version)


@skip_unless_feature("libimagequant")
def test_libimagequant_version() -> None:
assert re.search(r"\d+\.\d+\.\d+$", features.version("libimagequant"))
version = features.version("libimagequant")
assert version is not None
assert re.search(r"\d+\.\d+\.\d+$", version)


@pytest.mark.parametrize("feature", features.modules)
Expand Down
4 changes: 1 addition & 3 deletions Tests/test_file_eps.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,9 +336,7 @@ def test_readline_psfile(tmp_path: Path) -> None:
strings = ["something", "else", "baz", "bif"]

def _test_readline(t: EpsImagePlugin.PSFile, ending: str) -> None:
ending = "Failure with line ending: %s" % (
"".join("%s" % ord(s) for s in ending)
)
ending = f"Failure with line ending: {''.join(str(ord(s)) for s in ending)}"
assert t.readline().strip("\r\n") == "something", ending
assert t.readline().strip("\r\n") == "else", ending
assert t.readline().strip("\r\n") == "baz", ending
Expand Down
4 changes: 3 additions & 1 deletion Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ def gen_random_image(self, size: tuple[int, int], mode: str = "RGB") -> Image.Im

def test_sanity(self) -> None:
# internal version number
assert re.search(r"\d+\.\d+$", features.version_codec("jpg"))
version = features.version_codec("jpg")
assert version is not None
assert re.search(r"\d+\.\d+$", version)

with Image.open(TEST_FILE) as im:
im.load()
Expand Down
4 changes: 3 additions & 1 deletion Tests/test_file_jpeg2k.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ def roundtrip(im: Image.Image, **options: Any) -> Image.Image:

def test_sanity() -> None:
# Internal version number
assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("jpg_2000"))
version = features.version_codec("jpg_2000")
assert version is not None
assert re.search(r"\d+\.\d+\.\d+$", version)

with Image.open("Tests/images/test-card-lossless.jp2") as im:
px = im.load()
Expand Down
4 changes: 3 additions & 1 deletion Tests/test_file_libtiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ def _assert_noerr(self, tmp_path: Path, im: TiffImagePlugin.TiffImageFile) -> No

class TestFileLibTiff(LibTiffTestCase):
def test_version(self) -> None:
assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("libtiff"))
version = features.version_codec("libtiff")
assert version is not None
assert re.search(r"\d+\.\d+\.\d+$", version)

def test_g4_tiff(self, tmp_path: Path) -> None:
"""Test the ordinary file path load path"""
Expand Down
39 changes: 39 additions & 0 deletions Tests/test_file_mpeg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from __future__ import annotations

from io import BytesIO

import pytest

from PIL import Image, MpegImagePlugin


def test_identify() -> None:
# Arrange
b = BytesIO(b"\x00\x00\x01\xb3\x01\x00\x01")

# Act
with Image.open(b) as im:
# Assert
assert im.format == "MPEG"

assert im.mode == "RGB"
assert im.size == (16, 1)


def test_invalid_file() -> None:
# Arrange
invalid_file = "Tests/images/flower.jpg"

# Act / Assert
with pytest.raises(SyntaxError):
MpegImagePlugin.MpegImageFile(invalid_file)


def test_load() -> None:
# Arrange
b = BytesIO(b"\x00\x00\x01\xb3\x01\x00\x01")

with Image.open(b) as im:
# Act / Assert: cannot load
with pytest.raises(OSError):
im.load()
6 changes: 3 additions & 3 deletions Tests/test_file_png.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ def get_chunks(self, filename: str) -> list[bytes]:

def test_sanity(self, tmp_path: Path) -> None:
# internal version number
assert re.search(
r"\d+(\.\d+){1,3}(\.zlib\-ng)?$", features.version_codec("zlib")
)
version = features.version_codec("zlib")
assert version is not None
assert re.search(r"\d+(\.\d+){1,3}(\.zlib\-ng)?$", version)

test_file = str(tmp_path / "temp.png")

Expand Down
4 changes: 3 additions & 1 deletion Tests/test_file_webp.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ def setup_method(self) -> None:
def test_version(self) -> None:
_webp.WebPDecoderVersion()
_webp.WebPDecoderBuggyAlpha()
assert re.search(r"\d+\.\d+\.\d+$", features.version_module("webp"))
version = features.version_module("webp")
assert version is not None
assert re.search(r"\d+\.\d+\.\d+$", version)

def test_read_rgb(self) -> None:
"""
Expand Down
10 changes: 6 additions & 4 deletions Tests/test_file_webp_animated.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ def test_write_animation_L(tmp_path: Path) -> None:
assert_image_similar(im, orig.convert("RGBA"), 32.9)

if is_big_endian():
webp = parse_version(features.version_module("webp"))
if webp < parse_version("1.2.2"):
version = features.version_module("webp")
assert version is not None
if parse_version(version) < parse_version("1.2.2"):
pytest.skip("Fails with libwebp earlier than 1.2.2")
orig.seek(orig.n_frames - 1)
im.seek(im.n_frames - 1)
Expand All @@ -78,8 +79,9 @@ def check(temp_file) -> None:

# Compare second frame to original
if is_big_endian():
webp = parse_version(features.version_module("webp"))
if webp < parse_version("1.2.2"):
version = features.version_module("webp")
assert version is not None
if parse_version(version) < parse_version("1.2.2"):
pytest.skip("Fails with libwebp earlier than 1.2.2")
im.seek(1)
im.load()
Expand Down
Loading

0 comments on commit 6affb12

Please sign in to comment.