From 0074c3bf349f0055aaff73fd7864c6a5e785b220 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 23 Dec 2024 11:45:36 +1100 Subject: [PATCH 01/27] Assert that a tuple is returned by getpixel() --- Tests/test_file_dds.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 9a826ebe8..7cc4d79d4 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -331,11 +331,13 @@ def test_dxt5_colorblock_alpha_issue_4142() -> None: with Image.open("Tests/images/dxt5-colorblock-alpha-issue-4142.dds") as im: px = im.getpixel((0, 0)) + assert isinstance(px, tuple) assert px[0] != 0 assert px[1] != 0 assert px[2] != 0 px = im.getpixel((1, 0)) + assert isinstance(px, tuple) assert px[0] != 0 assert px[1] != 0 assert px[2] != 0 From 5d5543b35caacef1a7f5912caa14b9d257680291 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 23 Dec 2024 11:57:27 +1100 Subject: [PATCH 02/27] Assert that load() does not return None --- Tests/test_file_eps.py | 8 ++++++-- Tests/test_file_gbr.py | 6 +++++- Tests/test_file_gif.py | 22 +++++++++++++++++----- Tests/test_file_icns.py | 8 ++++++-- Tests/test_file_ico.py | 4 +++- Tests/test_file_jpeg2k.py | 1 + Tests/test_file_ppm.py | 1 + Tests/test_file_tga.py | 8 ++++++-- Tests/test_file_wal.py | 8 ++++++-- Tests/test_file_wmf.py | 4 +++- 10 files changed, 54 insertions(+), 16 deletions(-) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 672c04a4d..a0c2f9216 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -95,10 +95,14 @@ def test_sanity(filename: str, size: tuple[int, int], scale: int) -> None: @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") def test_load() -> None: with Image.open(FILE1) as im: - assert im.load()[0, 0] == (255, 255, 255) + px = im.load() + assert px is not None + assert px[0, 0] == (255, 255, 255) # Test again now that it has already been loaded once - assert im.load()[0, 0] == (255, 255, 255) + px = im.load() + assert px is not None + assert px[0, 0] == (255, 255, 255) def test_binary() -> None: diff --git a/Tests/test_file_gbr.py b/Tests/test_file_gbr.py index be98b08f2..5b59cc07a 100644 --- a/Tests/test_file_gbr.py +++ b/Tests/test_file_gbr.py @@ -14,10 +14,14 @@ def test_gbr_file() -> None: def test_load() -> None: with Image.open("Tests/images/gbr.gbr") as im: + px = im.load() + assert px is not None assert im.load()[0, 0] == (0, 0, 0, 0) # Test again now that it has already been loaded once - assert im.load()[0, 0] == (0, 0, 0, 0) + px = im.load() + assert px is not None + assert px[0, 0] == (0, 0, 0, 0) def test_multiple_load_operations() -> None: diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 5d46b157d..f25d819ea 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -86,12 +86,16 @@ def test_invalid_file() -> None: def test_l_mode_transparency() -> None: with Image.open("Tests/images/no_palette_with_transparency.gif") as im: assert im.mode == "L" - assert im.load()[0, 0] == 128 + px = im.load() + assert px is not None + assert px[0, 0] == 128 assert im.info["transparency"] == 255 im.seek(1) assert im.mode == "L" - assert im.load()[0, 0] == 128 + px = im.load() + assert px is not None + assert px[0, 0] == 128 def test_l_mode_after_rgb() -> None: @@ -310,7 +314,9 @@ def test_loading_multiple_palettes(path: str, mode: str) -> None: with Image.open(path) as im: assert im.mode == "P" first_frame_colors = im.palette.colors.keys() - original_color = im.convert("RGB").load()[0, 0] + px = im.convert("RGB").load() + assert px is not None + original_color = px[0, 0] im.seek(1) assert im.mode == mode @@ -318,10 +324,14 @@ def test_loading_multiple_palettes(path: str, mode: str) -> None: im = im.convert("RGB") # Check a color only from the old palette - assert im.load()[0, 0] == original_color + px = im.load() + assert px is not None + assert px[0, 0] == original_color # Check a color from the new palette - assert im.load()[24, 24] not in first_frame_colors + px = im.load() + assert px is not None + assert px[24, 24] not in first_frame_colors def test_headers_saving_for_animated_gifs(tmp_path: Path) -> None: @@ -488,6 +498,7 @@ def test_eoferror() -> None: def test_first_frame_transparency() -> None: with Image.open("Tests/images/first_frame_transparency.gif") as im: px = im.load() + assert px is not None assert px[0, 0] == im.info["transparency"] @@ -528,6 +539,7 @@ def test_dispose_background_transparency() -> None: with Image.open("Tests/images/dispose_bgnd_transparency.gif") as img: img.seek(2) px = img.load() + assert px is not None assert px[35, 30][3] == 0 diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 141b88dfa..94f16aeec 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -32,10 +32,14 @@ def test_sanity() -> None: def test_load() -> None: with Image.open(TEST_FILE) as im: - assert im.load()[0, 0] == (0, 0, 0, 0) + px = im.load() + assert px is not None + assert px[0, 0] == (0, 0, 0, 0) # Test again now that it has already been loaded once - assert im.load()[0, 0] == (0, 0, 0, 0) + px = im.load() + assert px is not None + assert px[0, 0] == (0, 0, 0, 0) def test_save(tmp_path: Path) -> None: diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 37770498a..10f3aac9a 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -24,7 +24,9 @@ def test_sanity() -> None: def test_load() -> None: with Image.open(TEST_ICO_FILE) as im: - assert im.load()[0, 0] == (1, 1, 9, 255) + px = im.load() + assert px is not None + assert px[0, 0] == (1, 1, 9, 255) def test_mask() -> None: diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index fbf72ae05..b761fcd37 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -63,6 +63,7 @@ def test_sanity() -> None: with Image.open("Tests/images/test-card-lossless.jp2") as im: px = im.load() + assert px is not None assert px[0, 0] == (0, 0, 0) assert im.mode == "RGB" assert im.size == (640, 480) diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index fb08d613a..f4acedb30 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -79,6 +79,7 @@ def test_arbitrary_maxval( assert im.mode == mode px = im.load() + assert px is not None assert tuple(px[x, 0] for x in range(3)) == pixels diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index a03a6a6e1..63d1e7615 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -213,10 +213,14 @@ def test_save_orientation(tmp_path: Path) -> None: def test_horizontal_orientations() -> None: # These images have been manually hexedited to have the relevant orientations with Image.open("Tests/images/rgb32rle_top_right.tga") as im: - assert im.load()[90, 90][:3] == (0, 0, 0) + px = im.load() + assert px is not None + assert px[90, 90][:3] == (0, 0, 0) with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im: - assert im.load()[90, 90][:3] == (0, 255, 0) + px = im.load() + assert px is not None + assert px[90, 90][:3] == (0, 255, 0) def test_save_rle(tmp_path: Path) -> None: diff --git a/Tests/test_file_wal.py b/Tests/test_file_wal.py index b34975e83..b15d79d61 100644 --- a/Tests/test_file_wal.py +++ b/Tests/test_file_wal.py @@ -21,7 +21,11 @@ def test_open() -> None: def test_load() -> None: with WalImageFile.open(TEST_FILE) as im: - assert im.load()[0, 0] == 122 + px = im.load() + assert px is not None + assert px[0, 0] == 122 # Test again now that it has already been loaded once - assert im.load()[0, 0] == 122 + px = im.load() + assert px is not None + assert px[0, 0] == 122 diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index 2f1f8cdbc..f849453f6 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -32,7 +32,9 @@ def test_load_raw() -> None: def test_load() -> None: with Image.open("Tests/images/drawing.emf") as im: if hasattr(Image.core, "drawwmf"): - assert im.load()[0, 0] == (255, 255, 255) + px = im.load() + assert px is not None + assert px[0, 0] == (255, 255, 255) def test_load_zero_inch() -> None: From 601a56def1dfdacd76759302595d9d904f2467c8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 23 Dec 2024 12:03:13 +1100 Subject: [PATCH 03/27] Assert palette is not None --- Tests/test_file_gif.py | 2 ++ Tests/test_file_jpeg2k.py | 2 ++ Tests/test_file_tga.py | 1 + Tests/test_image.py | 1 + Tests/test_image_convert.py | 1 + Tests/test_image_transform.py | 1 + Tests/test_imagepalette.py | 1 + 7 files changed, 9 insertions(+) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index f25d819ea..9fe5f4fbb 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -313,6 +313,7 @@ def test_roundtrip_save_all_1(tmp_path: Path) -> None: def test_loading_multiple_palettes(path: str, mode: str) -> None: with Image.open(path) as im: assert im.mode == "P" + assert im.palette is not None first_frame_colors = im.palette.colors.keys() px = im.convert("RGB").load() assert px is not None @@ -1325,6 +1326,7 @@ def test_palette_save_all_P(tmp_path: Path) -> None: with Image.open(out) as im: # Assert that the frames are correct, and each frame has the same palette assert_image_equal(im.convert("RGB"), frames[0].convert("RGB")) + assert im.palette is not None assert im.palette.palette == im.global_palette.palette im.seek(1) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index b761fcd37..af6b9b2db 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -413,6 +413,7 @@ def test_subsampling_decode(name: str) -> None: def test_pclr() -> None: with Image.open(f"{EXTRA_DIR}/issue104_jpxstream.jp2") as im: assert im.mode == "P" + assert im.palette is not None assert len(im.palette.colors) == 256 assert im.palette.colors[(255, 255, 255)] == 0 @@ -420,6 +421,7 @@ def test_pclr() -> None: f"{EXTRA_DIR}/147af3f1083de4393666b7d99b01b58b_signal_sigsegv_130c531_6155_5136.jp2" ) as im: assert im.mode == "P" + assert im.palette is not None assert len(im.palette.colors) == 139 assert im.palette.colors[(0, 0, 0, 0)] == 0 diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index 63d1e7615..b6396bd64 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -72,6 +72,7 @@ def test_palette_depth_8(tmp_path: Path) -> None: def test_palette_depth_16(tmp_path: Path) -> None: with Image.open("Tests/images/p_16.tga") as im: + assert im.palette is not None assert im.palette.mode == "RGBA" assert_image_equal_tofile(im.convert("RGBA"), "Tests/images/p_16.png") diff --git a/Tests/test_image.py b/Tests/test_image.py index c8df474f4..1d7bd19ca 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -662,6 +662,7 @@ class TestImage: im.putpalette(list(range(256)) * 4, "RGBA") im_remapped = im.remap_palette(list(range(256))) assert_image_equal(im, im_remapped) + assert im.palette is not None assert im.palette.palette == im_remapped.palette.palette # Test illegal image mode diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 6a925975e..03061ceb1 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -236,6 +236,7 @@ def test_gif_with_rgba_palette_to_p() -> None: with Image.open("Tests/images/hopper.gif") as im: im.info["transparency"] = 255 im.load() + assert im.palette is not None assert im.palette.mode == "RGB" im_p = im.convert("P") diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 7e83396de..77916929b 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -47,6 +47,7 @@ class TestImageTransform: transformed = im.transform( im.size, Image.Transform.AFFINE, [1, 0, 0, 0, 1, 0] ) + assert im.palette is not None assert im.palette.palette == transformed.palette.palette def test_extent(self) -> None: diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 6cf0079dd..e2f8308ea 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -17,6 +17,7 @@ def test_sanity() -> None: def test_reload() -> None: with Image.open("Tests/images/hopper.gif") as im: original = im.copy() + assert im.palette is not None im.palette.dirty = 1 assert_image_equal(im.convert("RGB"), original.convert("RGB")) From 120ba1c13d482b6f8763ab287ac5811a838f8828 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 8 Jan 2025 14:01:06 +0800 Subject: [PATCH 04/27] Rewrite the install_name of the ZLIB-NG library on macOS. --- .github/workflows/wheels-dependencies.sh | 8 ++++++++ pyproject.toml | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 58621bca1..2eac4d3d7 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -72,6 +72,14 @@ function build_zlib_ng { && ./configure --prefix=$BUILD_PREFIX --zlib-compat \ && make -j4 \ && make install) + + if [ -n "$IS_MACOS" ]; then + # Ensure that on macOS, the library name is an absolute path, not an + # @rpath, so that delocate picks up the right library (and doesn't need + # DYLD_LIBRARY_PATH to be set). The default Makefile doesn't have an + # option to control the install_name. + install_name_tool -id $BUILD_PREFIX/lib/libz.1.dylib $BUILD_PREFIX/lib/libz.1.dylib + fi touch zlib-stamp } diff --git a/pyproject.toml b/pyproject.toml index 2c6c7bcd0..aaaba0032 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,7 +104,6 @@ test-extras = "tests" [tool.cibuildwheel.macos.environment] PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" -DYLD_LIBRARY_PATH = "$(pwd)/build/deps/darwin/lib" [tool.black] exclude = "wheels/multibuild" From 1b0095fad45db67c723215e1f9235f839b2637d9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Feb 2025 17:23:41 +1100 Subject: [PATCH 05/27] Pass CFLAGS to build_simple directly --- .github/workflows/wheels-dependencies.sh | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index dffb36085..1dd8d5660 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -54,13 +54,10 @@ BROTLI_VERSION=1.1.0 function build_pkg_config { if [ -e pkg-config-stamp ]; then return; fi # This essentially duplicates the Homebrew recipe - ORIGINAL_CFLAGS=$CFLAGS - CFLAGS="$CFLAGS -Wno-int-conversion" - build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \ + CFLAGS="$CFLAGS -Wno-int-conversion" build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \ --disable-debug --disable-host-tool --with-internal-glib \ --with-pc-path=$BUILD_PREFIX/share/pkgconfig:$BUILD_PREFIX/lib/pkgconfig \ --with-system-include-path=$(xcrun --show-sdk-path --sdk macosx)/usr/include - CFLAGS=$ORIGINAL_CFLAGS export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config touch pkg-config-stamp } @@ -130,15 +127,13 @@ function build { build_lcms2 build_openjpeg - ORIGINAL_CFLAGS=$CFLAGS - CFLAGS="$CFLAGS -O3 -DNDEBUG" + webp_cflags="-O3 -DNDEBUG" if [[ -n "$IS_MACOS" ]]; then - CFLAGS="$CFLAGS -Wl,-headerpad_max_install_names" + webp_cflags="$webp_cflags -Wl,-headerpad_max_install_names" fi - build_simple libwebp $LIBWEBP_VERSION \ + CFLAGS="$CFLAGS $webp_cflags" build_simple libwebp $LIBWEBP_VERSION \ https://storage.googleapis.com/downloads.webmproject.org/releases/webp tar.gz \ --enable-libwebpmux --enable-libwebpdemux - CFLAGS=$ORIGINAL_CFLAGS build_brotli From 166d0b94d938c97ba06f2718a8772d1f8d88ac60 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Feb 2025 21:00:54 +1100 Subject: [PATCH 06/27] Use boolean format argument for irreversible --- src/encode.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/encode.c b/src/encode.c index 74dd4a3fd..2a9fd3805 100644 --- a/src/encode.c +++ b/src/encode.c @@ -1253,7 +1253,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) { PyObject *quality_layers = NULL; Py_ssize_t num_resolutions = 0; PyObject *cblk_size = NULL, *precinct_size = NULL; - PyObject *irreversible = NULL; + int irreversible = 0; char *progression = "LRCP"; OPJ_PROG_ORDER prog_order; char *cinema_mode = "no"; @@ -1267,7 +1267,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, - "ss|OOOsOnOOOssbbnz#p", + "ss|OOOsOnOOpssbbnz#p", &mode, &format, &offset, @@ -1402,7 +1402,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) { precinct_size, &context->precinct_width, &context->precinct_height ); - context->irreversible = PyObject_IsTrue(irreversible); + context->irreversible = irreversible; context->progression = prog_order; context->cinema_mode = cine_mode; context->mct = mct; From b59dea60a6a7c83545f83f9e1f723c1a40f3f7cb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 8 Feb 2025 21:07:25 +1100 Subject: [PATCH 07/27] Simplify Python code by receiving tuple from C --- src/PIL/WebPImagePlugin.py | 3 +-- src/_webp.c | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 066fe551f..cbbc24af0 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -46,8 +46,7 @@ class WebPImageFile(ImageFile.ImageFile): self._decoder = _webp.WebPAnimDecoder(self.fp.read()) # Get info from decoder - width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info() - self._size = width, height + self._size, loop_count, bgcolor, frame_count, mode = self._decoder.get_info() self.info["loop"] = loop_count bg_a, bg_r, bg_g, bg_b = ( (bgcolor >> 24) & 0xFF, diff --git a/src/_webp.c b/src/_webp.c index 26a5ebbc6..48b1c0a74 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -449,7 +449,7 @@ _anim_decoder_get_info(PyObject *self) { WebPAnimInfo *info = &(decp->info); return Py_BuildValue( - "IIIIIs", + "(II)IIIs", info->canvas_width, info->canvas_height, info->loop_count, From bfa2d64e0e41285d7cbc1016eb98b56b51255575 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 9 Feb 2025 16:02:50 +1100 Subject: [PATCH 08/27] Use member names to initialize PyTypeObjects --- src/_imaging.c | 125 ++++++++-------------------------------------- src/_imagingcms.c | 71 ++++---------------------- src/_imagingft.c | 36 +++---------- src/_webp.c | 70 ++++---------------------- src/decode.c | 36 +++---------- src/display.c | 36 +++---------- src/encode.c | 36 +++---------- src/outline.c | 35 ++----------- src/path.c | 38 +++----------- 9 files changed, 79 insertions(+), 404 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index ee373e964..6482bcc5e 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -3769,102 +3769,29 @@ static PySequenceMethods image_as_sequence = { /* type description */ static PyTypeObject Imaging_Type = { - PyVarObject_HEAD_INIT(NULL, 0) "ImagingCore", /*tp_name*/ - sizeof(ImagingObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)_dealloc, /*tp_dealloc*/ - 0, /*tp_vectorcall_offset*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - &image_as_sequence, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - methods, /*tp_methods*/ - 0, /*tp_members*/ - getsetters, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingCore", + .tp_basicsize = sizeof(ImagingObject), + .tp_dealloc = (destructor)_dealloc, + .tp_as_sequence = &image_as_sequence, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = methods, + .tp_getset = getsetters, }; static PyTypeObject ImagingFont_Type = { - PyVarObject_HEAD_INIT(NULL, 0) "ImagingFont", /*tp_name*/ - sizeof(ImagingFontObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)_font_dealloc, /*tp_dealloc*/ - 0, /*tp_vectorcall_offset*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - _font_methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingFont", + .tp_basicsize = sizeof(ImagingFontObject), + .tp_dealloc = (destructor)_font_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = _font_methods, }; static PyTypeObject ImagingDraw_Type = { - PyVarObject_HEAD_INIT(NULL, 0) "ImagingDraw", /*tp_name*/ - sizeof(ImagingDrawObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)_draw_dealloc, /*tp_dealloc*/ - 0, /*tp_vectorcall_offset*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - _draw_methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingDraw", + .tp_basicsize = sizeof(ImagingDrawObject), + .tp_dealloc = (destructor)_draw_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = _draw_methods, }; static PyMappingMethods pixel_access_as_mapping = { @@ -3876,20 +3803,10 @@ static PyMappingMethods pixel_access_as_mapping = { /* type description */ static PyTypeObject PixelAccess_Type = { - PyVarObject_HEAD_INIT(NULL, 0) "PixelAccess", /*tp_name*/ - sizeof(PixelAccessObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)pixel_access_dealloc, /*tp_dealloc*/ - 0, /*tp_vectorcall_offset*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - &pixel_access_as_mapping, /*tp_as_mapping*/ - 0 /*tp_hash*/ + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "PixelAccess", + .tp_basicsize = sizeof(PixelAccessObject), + .tp_dealloc = (destructor)pixel_access_dealloc, + .tp_as_mapping = &pixel_access_as_mapping, }; /* -------------------------------------------------------------------- */ diff --git a/src/_imagingcms.c b/src/_imagingcms.c index 6037e8bc4..e177feee9 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -1410,36 +1410,12 @@ static struct PyGetSetDef cms_profile_getsetters[] = { }; static PyTypeObject CmsProfile_Type = { - PyVarObject_HEAD_INIT(NULL, 0) "PIL.ImageCms.core.CmsProfile", /*tp_name*/ - sizeof(CmsProfileObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)cms_profile_dealloc, /*tp_dealloc*/ - 0, /*tp_vectorcall_offset*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - cms_profile_methods, /*tp_methods*/ - 0, /*tp_members*/ - cms_profile_getsetters, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "PIL.ImageCms.core.CmsProfile", + .tp_basicsize = sizeof(CmsProfileObject), + .tp_dealloc = (destructor)cms_profile_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = cms_profile_methods, + .tp_getset = cms_profile_getsetters, }; static struct PyMethodDef cms_transform_methods[] = { @@ -1447,36 +1423,11 @@ static struct PyMethodDef cms_transform_methods[] = { }; static PyTypeObject CmsTransform_Type = { - PyVarObject_HEAD_INIT(NULL, 0) "PIL.ImageCms.core.CmsTransform", /*tp_name*/ - sizeof(CmsTransformObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)cms_transform_dealloc, /*tp_dealloc*/ - 0, /*tp_vectorcall_offset*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - cms_transform_methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "PIL.ImageCms.core.CmsTransform", + .tp_basicsize = sizeof(CmsTransformObject), + .tp_dealloc = (destructor)cms_transform_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = cms_transform_methods, }; static int diff --git a/src/_imagingft.c b/src/_imagingft.c index 7d754e168..922c3da32 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1518,36 +1518,12 @@ static struct PyGetSetDef font_getsetters[] = { }; static PyTypeObject Font_Type = { - PyVarObject_HEAD_INIT(NULL, 0) "Font", /*tp_name*/ - sizeof(FontObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)font_dealloc, /*tp_dealloc*/ - 0, /*tp_vectorcall_offset*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - font_methods, /*tp_methods*/ - 0, /*tp_members*/ - font_getsetters, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "Font", + .tp_basicsize = sizeof(FontObject), + .tp_dealloc = (destructor)font_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = font_methods, + .tp_getset = font_getsetters, }; static PyMethodDef _functions[] = { diff --git a/src/_webp.c b/src/_webp.c index 26a5ebbc6..942f275da 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -530,36 +530,11 @@ static struct PyMethodDef _anim_encoder_methods[] = { // WebPAnimEncoder type definition static PyTypeObject WebPAnimEncoder_Type = { - PyVarObject_HEAD_INIT(NULL, 0) "WebPAnimEncoder", /*tp_name */ - sizeof(WebPAnimEncoderObject), /*tp_basicsize */ - 0, /*tp_itemsize */ - /* methods */ - (destructor)_anim_encoder_dealloc, /*tp_dealloc*/ - 0, /*tp_vectorcall_offset*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - _anim_encoder_methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "WebPAnimEncoder", + .tp_basicsize = sizeof(WebPAnimEncoderObject), + .tp_dealloc = (destructor)_anim_encoder_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = _anim_encoder_methods, }; // WebPAnimDecoder methods @@ -573,36 +548,11 @@ static struct PyMethodDef _anim_decoder_methods[] = { // WebPAnimDecoder type definition static PyTypeObject WebPAnimDecoder_Type = { - PyVarObject_HEAD_INIT(NULL, 0) "WebPAnimDecoder", /*tp_name */ - sizeof(WebPAnimDecoderObject), /*tp_basicsize */ - 0, /*tp_itemsize */ - /* methods */ - (destructor)_anim_decoder_dealloc, /*tp_dealloc*/ - 0, /*tp_vectorcall_offset*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - _anim_decoder_methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "WebPAnimDecoder", + .tp_basicsize = sizeof(WebPAnimDecoderObject), + .tp_dealloc = (destructor)_anim_decoder_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = _anim_decoder_methods, }; /* -------------------------------------------------------------------- */ diff --git a/src/decode.c b/src/decode.c index 1f2c22491..26211a95f 100644 --- a/src/decode.c +++ b/src/decode.c @@ -256,36 +256,12 @@ static struct PyGetSetDef getseters[] = { }; static PyTypeObject ImagingDecoderType = { - PyVarObject_HEAD_INIT(NULL, 0) "ImagingDecoder", /*tp_name*/ - sizeof(ImagingDecoderObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)_dealloc, /*tp_dealloc*/ - 0, /*tp_vectorcall_offset*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - methods, /*tp_methods*/ - 0, /*tp_members*/ - getseters, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingDecoder", + .tp_basicsize = sizeof(ImagingDecoderObject), + .tp_dealloc = (destructor)_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = methods, + .tp_getset = getseters, }; /* -------------------------------------------------------------------- */ diff --git a/src/display.c b/src/display.c index 36ab3b237..004c7866b 100644 --- a/src/display.c +++ b/src/display.c @@ -248,36 +248,12 @@ static struct PyGetSetDef getsetters[] = { }; static PyTypeObject ImagingDisplayType = { - PyVarObject_HEAD_INIT(NULL, 0) "ImagingDisplay", /*tp_name*/ - sizeof(ImagingDisplayObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)_delete, /*tp_dealloc*/ - 0, /*tp_vectorcall_offset*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - methods, /*tp_methods*/ - 0, /*tp_members*/ - getsetters, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingDisplay", + .tp_basicsize = sizeof(ImagingDisplayObject), + .tp_dealloc = (destructor)_delete, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = methods, + .tp_getset = getsetters, }; PyObject * diff --git a/src/encode.c b/src/encode.c index 74dd4a3fd..dd7355811 100644 --- a/src/encode.c +++ b/src/encode.c @@ -323,36 +323,12 @@ static struct PyGetSetDef getseters[] = { }; static PyTypeObject ImagingEncoderType = { - PyVarObject_HEAD_INIT(NULL, 0) "ImagingEncoder", /*tp_name*/ - sizeof(ImagingEncoderObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)_dealloc, /*tp_dealloc*/ - 0, /*tp_vectorcall_offset*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - methods, /*tp_methods*/ - 0, /*tp_members*/ - getseters, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingEncoder", + .tp_basicsize = sizeof(ImagingEncoderObject), + .tp_dealloc = (destructor)_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = methods, + .tp_getset = getseters, }; /* -------------------------------------------------------------------- */ diff --git a/src/outline.c b/src/outline.c index 4aa6bd59e..6eea07c5d 100644 --- a/src/outline.c +++ b/src/outline.c @@ -149,34 +149,9 @@ static struct PyMethodDef _outline_methods[] = { }; static PyTypeObject OutlineType = { - PyVarObject_HEAD_INIT(NULL, 0) "Outline", /*tp_name*/ - sizeof(OutlineObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)_outline_dealloc, /*tp_dealloc*/ - 0, /*tp_vectorcall_offset*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - _outline_methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "Outline", + .tp_basicsize = sizeof(OutlineObject), + .tp_dealloc = (destructor)_outline_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = _outline_methods, }; diff --git a/src/path.c b/src/path.c index b508df2ac..24820173e 100644 --- a/src/path.c +++ b/src/path.c @@ -598,34 +598,12 @@ static PyMappingMethods path_as_mapping = { }; static PyTypeObject PyPathType = { - PyVarObject_HEAD_INIT(NULL, 0) "Path", /*tp_name*/ - sizeof(PyPathObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)path_dealloc, /*tp_dealloc*/ - 0, /*tp_vectorcall_offset*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - &path_as_sequence, /*tp_as_sequence*/ - &path_as_mapping, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - methods, /*tp_methods*/ - 0, /*tp_members*/ - getsetters, /*tp_getset*/ + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "Path", + .tp_basicsize = sizeof(PyPathObject), + .tp_dealloc = (destructor)path_dealloc, + .tp_as_sequence = &path_as_sequence, + .tp_as_mapping = &path_as_mapping, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = methods, + .tp_getset = getsetters, }; From 422c0f607d04470729768c3204273894c9be9e46 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 9 Feb 2025 16:03:38 +1100 Subject: [PATCH 09/27] Use default tp_flags --- src/_imaging.c | 3 --- src/_imagingcms.c | 2 -- src/_imagingft.c | 1 - src/_webp.c | 2 -- src/decode.c | 1 - src/display.c | 1 - src/encode.c | 1 - src/outline.c | 1 - src/path.c | 1 - 9 files changed, 13 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 6482bcc5e..d5c21fd86 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -3773,7 +3773,6 @@ static PyTypeObject Imaging_Type = { .tp_basicsize = sizeof(ImagingObject), .tp_dealloc = (destructor)_dealloc, .tp_as_sequence = &image_as_sequence, - .tp_flags = Py_TPFLAGS_DEFAULT, .tp_methods = methods, .tp_getset = getsetters, }; @@ -3782,7 +3781,6 @@ static PyTypeObject ImagingFont_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingFont", .tp_basicsize = sizeof(ImagingFontObject), .tp_dealloc = (destructor)_font_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, .tp_methods = _font_methods, }; @@ -3790,7 +3788,6 @@ static PyTypeObject ImagingDraw_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingDraw", .tp_basicsize = sizeof(ImagingDrawObject), .tp_dealloc = (destructor)_draw_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, .tp_methods = _draw_methods, }; diff --git a/src/_imagingcms.c b/src/_imagingcms.c index e177feee9..ea2f70186 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -1413,7 +1413,6 @@ static PyTypeObject CmsProfile_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "PIL.ImageCms.core.CmsProfile", .tp_basicsize = sizeof(CmsProfileObject), .tp_dealloc = (destructor)cms_profile_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, .tp_methods = cms_profile_methods, .tp_getset = cms_profile_getsetters, }; @@ -1426,7 +1425,6 @@ static PyTypeObject CmsTransform_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "PIL.ImageCms.core.CmsTransform", .tp_basicsize = sizeof(CmsTransformObject), .tp_dealloc = (destructor)cms_transform_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, .tp_methods = cms_transform_methods, }; diff --git a/src/_imagingft.c b/src/_imagingft.c index 922c3da32..62dab73e5 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1521,7 +1521,6 @@ static PyTypeObject Font_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "Font", .tp_basicsize = sizeof(FontObject), .tp_dealloc = (destructor)font_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, .tp_methods = font_methods, .tp_getset = font_getsetters, }; diff --git a/src/_webp.c b/src/_webp.c index 942f275da..c280d9513 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -533,7 +533,6 @@ static PyTypeObject WebPAnimEncoder_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "WebPAnimEncoder", .tp_basicsize = sizeof(WebPAnimEncoderObject), .tp_dealloc = (destructor)_anim_encoder_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, .tp_methods = _anim_encoder_methods, }; @@ -551,7 +550,6 @@ static PyTypeObject WebPAnimDecoder_Type = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "WebPAnimDecoder", .tp_basicsize = sizeof(WebPAnimDecoderObject), .tp_dealloc = (destructor)_anim_decoder_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, .tp_methods = _anim_decoder_methods, }; diff --git a/src/decode.c b/src/decode.c index 26211a95f..03db1ce35 100644 --- a/src/decode.c +++ b/src/decode.c @@ -259,7 +259,6 @@ static PyTypeObject ImagingDecoderType = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingDecoder", .tp_basicsize = sizeof(ImagingDecoderObject), .tp_dealloc = (destructor)_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, .tp_methods = methods, .tp_getset = getseters, }; diff --git a/src/display.c b/src/display.c index 004c7866b..a05387504 100644 --- a/src/display.c +++ b/src/display.c @@ -251,7 +251,6 @@ static PyTypeObject ImagingDisplayType = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingDisplay", .tp_basicsize = sizeof(ImagingDisplayObject), .tp_dealloc = (destructor)_delete, - .tp_flags = Py_TPFLAGS_DEFAULT, .tp_methods = methods, .tp_getset = getsetters, }; diff --git a/src/encode.c b/src/encode.c index dd7355811..f610d6638 100644 --- a/src/encode.c +++ b/src/encode.c @@ -326,7 +326,6 @@ static PyTypeObject ImagingEncoderType = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ImagingEncoder", .tp_basicsize = sizeof(ImagingEncoderObject), .tp_dealloc = (destructor)_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, .tp_methods = methods, .tp_getset = getseters, }; diff --git a/src/outline.c b/src/outline.c index 6eea07c5d..32ab9109c 100644 --- a/src/outline.c +++ b/src/outline.c @@ -152,6 +152,5 @@ static PyTypeObject OutlineType = { PyVarObject_HEAD_INIT(NULL, 0).tp_name = "Outline", .tp_basicsize = sizeof(OutlineObject), .tp_dealloc = (destructor)_outline_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, .tp_methods = _outline_methods, }; diff --git a/src/path.c b/src/path.c index 24820173e..5affe3a1f 100644 --- a/src/path.c +++ b/src/path.c @@ -603,7 +603,6 @@ static PyTypeObject PyPathType = { .tp_dealloc = (destructor)path_dealloc, .tp_as_sequence = &path_as_sequence, .tp_as_mapping = &path_as_mapping, - .tp_flags = Py_TPFLAGS_DEFAULT, .tp_methods = methods, .tp_getset = getsetters, }; From c566a81c647834d5789ab0cc7680eb51effcddec Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 10 Feb 2025 21:47:37 +1100 Subject: [PATCH 10/27] Updated libimagequant to 4.3.4 --- winbuild/build_prepare.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 54b5d983f..0ea8f0f9f 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -116,6 +116,7 @@ V = { "HARFBUZZ": "10.2.0", "JPEGTURBO": "3.1.0", "LCMS2": "2.16", + "LIBIMAGEQUANT": "4.3.4", "LIBPNG": "1.6.46", "LIBWEBP": "1.5.0", "OPENJPEG": "2.5.3", @@ -335,24 +336,15 @@ DEPS: dict[str, dict[str, Any]] = { "libs": [r"bin\*.lib"], }, "libimagequant": { - # commit: Merge branch 'master' into msvc (matches 2.17.0 tag) - "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip", - "filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip", + "url": "https://github.com/ImageOptim/libimagequant/archive/{V['LIBIMAGEQUANT']}.tar.gz", + "filename": f"libimagequant-{V['LIBIMAGEQUANT']}.tar.gz", "license": "COPYRIGHT", - "patch": { - "CMakeLists.txt": { - "if(OPENMP_FOUND)": "if(false)", - "install": "#install", - # libimagequant does not detect MSVC x86_arm64 cross-compiler correctly - "if(${{CMAKE_SYSTEM_PROCESSOR}} STREQUAL ARM64)": "if({architecture} STREQUAL ARM64)", # noqa: E501 - } - }, "build": [ - *cmds_cmake("imagequant_a"), - cmd_copy("imagequant_a.lib", "imagequant.lib"), + cmd_cd("imagequant-sys"), + "cargo build --release", ], - "headers": [r"*.h"], - "libs": [r"imagequant.lib"], + "headers": ["libimagequant.h"], + "libs": [r"..\target\release\imagequant_sys.lib"], }, "harfbuzz": { "url": f"https://github.com/harfbuzz/harfbuzz/archive/{V['HARFBUZZ']}.zip", From 45d8d8056767988e0ea58a8676a5244d334b37b8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 11 Feb 2025 11:36:55 +1100 Subject: [PATCH 11/27] Updated zlib-ng to 2.2.4 --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index dffb36085..edf5ba937 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -45,7 +45,7 @@ OPENJPEG_VERSION=2.5.3 XZ_VERSION=5.6.4 TIFF_VERSION=4.6.0 LCMS2_VERSION=2.16 -ZLIB_NG_VERSION=2.2.3 +ZLIB_NG_VERSION=2.2.4 LIBWEBP_VERSION=1.5.0 BZIP2_VERSION=1.0.8 LIBXCB_VERSION=1.17.0 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 54b5d983f..73e3699d7 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -121,7 +121,7 @@ V = { "OPENJPEG": "2.5.3", "TIFF": "4.6.0", "XZ": "5.6.4", - "ZLIBNG": "2.2.3", + "ZLIBNG": "2.2.4", } V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2]) From 8020d423bc42688af8fb83b70d951dbf9daf34ad Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 12 Feb 2025 18:36:14 +1100 Subject: [PATCH 12/27] Use monkeypatch --- Tests/test_file_gif.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 46215db1f..396e09ba9 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1345,7 +1345,7 @@ def test_save_I(tmp_path: Path) -> None: assert_image_equal(reloaded.convert("L"), im.convert("L")) -def test_getdata() -> None: +def test_getdata(monkeypatch: pytest.MonkeyPatch) -> None: # Test getheader/getdata against legacy values. # Create a 'P' image with holes in the palette. im = Image._wedge().resize((16, 16), Image.Resampling.NEAREST) @@ -1354,23 +1354,21 @@ def test_getdata() -> None: passed_palette = bytes(255 - i // 3 for i in range(768)) - GifImagePlugin._FORCE_OPTIMIZE = True - try: - h = GifImagePlugin.getheader(im, passed_palette) - d = GifImagePlugin.getdata(im) + monkeypatch.setattr(GifImagePlugin, "_FORCE_OPTIMIZE", True) - import pickle + h = GifImagePlugin.getheader(im, passed_palette) + d = GifImagePlugin.getdata(im) - # Enable to get target values on pre-refactor version - # with open('Tests/images/gif_header_data.pkl', 'wb') as f: - # pickle.dump((h, d), f, 1) - with open("Tests/images/gif_header_data.pkl", "rb") as f: - (h_target, d_target) = pickle.load(f) + import pickle - assert h == h_target - assert d == d_target - finally: - GifImagePlugin._FORCE_OPTIMIZE = False + # Enable to get target values on pre-refactor version + # with open('Tests/images/gif_header_data.pkl', 'wb') as f: + # pickle.dump((h, d), f, 1) + with open("Tests/images/gif_header_data.pkl", "rb") as f: + (h_target, d_target) = pickle.load(f) + + assert h == h_target + assert d == d_target def test_lzw_bits() -> None: From 8f4bfe1fe5c782329314333eac6284f32ff84b7b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 12 Feb 2025 19:12:27 +1100 Subject: [PATCH 13/27] Only crop when saving with disposal method 2 if transparency is present --- Tests/test_file_gif.py | 15 +++++++++++++++ src/PIL/GifImagePlugin.py | 28 +++++++++++++++++----------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 46215db1f..2f0116434 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -762,6 +762,21 @@ def test_dispose2_previous_frame(tmp_path: Path) -> None: assert im.getpixel((0, 0)) == (0, 0, 0, 255) +def test_dispose2_without_transparency(tmp_path: Path) -> None: + out = str(tmp_path / "temp.gif") + + im = Image.new("P", (100, 100)) + + im2 = Image.new("P", (100, 100), (0, 0, 0)) + im2.putpixel((50, 50), (255, 0, 0)) + + im.save(out, save_all=True, append_images=[im2], disposal=2) + + with Image.open(out) as reloaded: + reloaded.seek(1) + assert reloaded.tile[0].extents == (0, 0, 100, 100) + + def test_transparency_in_second_frame(tmp_path: Path) -> None: out = str(tmp_path / "temp.gif") with Image.open("Tests/images/different_transparency.gif") as im: diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 47022d584..ff7262efc 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -689,16 +689,21 @@ def _write_multiple_frames( im_frames[-1].encoderinfo["duration"] += encoderinfo["duration"] continue if im_frames[-1].encoderinfo.get("disposal") == 2: - if background_im is None: - color = im.encoderinfo.get( - "transparency", im.info.get("transparency", (0, 0, 0)) - ) - background = _get_background(im_frame, color) - background_im = Image.new("P", im_frame.size, background) - first_palette = im_frames[0].im.palette - assert first_palette is not None - background_im.putpalette(first_palette, first_palette.mode) - bbox = _getbbox(background_im, im_frame)[1] + # To appear correctly in viewers using a convention, + # only consider transparency, and not background color + color = im.encoderinfo.get( + "transparency", im.info.get("transparency") + ) + if color is not None: + if background_im is None: + background = _get_background(im_frame, color) + background_im = Image.new("P", im_frame.size, background) + first_palette = im_frames[0].im.palette + assert first_palette is not None + background_im.putpalette(first_palette, first_palette.mode) + bbox = _getbbox(background_im, im_frame)[1] + else: + bbox = (0, 0) + im_frame.size elif encoderinfo.get("optimize") and im_frame.mode != "1": if "transparency" not in encoderinfo: assert im_frame.palette is not None @@ -764,7 +769,8 @@ def _write_multiple_frames( if not palette: frame_data.encoderinfo["include_color_table"] = True - im_frame = im_frame.crop(frame_data.bbox) + if frame_data.bbox != (0, 0) + im_frame.size: + im_frame = im_frame.crop(frame_data.bbox) offset = frame_data.bbox[:2] _write_frame_data(fp, im_frame, offset, frame_data.encoderinfo) return True From 9f0398ef3239cacf0f0250305f4ccb1db9f6c738 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Feb 2025 21:07:43 +1100 Subject: [PATCH 14/27] Removed unused code --- Tests/helper.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index e7b0db1d6..764935f87 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -9,7 +9,6 @@ import os import shutil import subprocess import sys -import sysconfig import tempfile from collections.abc import Sequence from functools import lru_cache @@ -342,10 +341,6 @@ def is_pypy() -> bool: return hasattr(sys, "pypy_translation_info") -def is_mingw() -> bool: - return sysconfig.get_platform() == "mingw" - - class CachedProperty: def __init__(self, func: Callable[[Any], Any]) -> None: self.func = func From 1c18d29c34789c802e2cfc73d841019bc5f06ca1 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 15 Feb 2025 13:26:06 +0200 Subject: [PATCH 15/27] Remove unused bdf_slant and bdf_spacing variables --- src/PIL/BdfFontFile.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/PIL/BdfFontFile.py b/src/PIL/BdfFontFile.py index bc1416c74..bfd66aa6a 100644 --- a/src/PIL/BdfFontFile.py +++ b/src/PIL/BdfFontFile.py @@ -26,17 +26,6 @@ from typing import BinaryIO from . import FontFile, Image -bdf_slant = { - "R": "Roman", - "I": "Italic", - "O": "Oblique", - "RI": "Reverse Italic", - "RO": "Reverse Oblique", - "OT": "Other", -} - -bdf_spacing = {"P": "Proportional", "M": "Monospaced", "C": "Cell"} - def bdf_char( f: BinaryIO, From 8261348fff5b5a653767c348a44a01d98238a190 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 15 Feb 2025 14:27:52 +0200 Subject: [PATCH 16/27] Don't call pip in tox --- tox.ini | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tox.ini b/tox.ini index e79d88500..4065245ee 100644 --- a/tox.ini +++ b/tox.ini @@ -11,12 +11,8 @@ deps = extras = tests commands = - make clean - {envpython} -m pip install . {envpython} selftest.py {envpython} -m pytest -W always {posargs} -allowlist_externals = - make [testenv:lint] skip_install = true From ff960b884188855c4a8afb2a2724674d80b31e04 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 15 Feb 2025 14:23:59 +0200 Subject: [PATCH 17/27] Remove debug Image._wedge --- Tests/test_file_gif.py | 2 +- Tests/test_format_hsv.py | 28 +++++++++++++--------------- src/PIL/Image.py | 9 --------- src/_imaging.c | 1 - 4 files changed, 14 insertions(+), 26 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 396e09ba9..974aedeb6 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1348,7 +1348,7 @@ def test_save_I(tmp_path: Path) -> None: def test_getdata(monkeypatch: pytest.MonkeyPatch) -> None: # Test getheader/getdata against legacy values. # Create a 'P' image with holes in the palette. - im = Image._wedge().resize((16, 16), Image.Resampling.NEAREST) + im = Image.linear_gradient(mode="L").resize((16, 16), Image.Resampling.NEAREST) im.putpalette(ImagePalette.ImagePalette("RGB")) im.info = {"background": 0} diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index c07024a2c..9cbf18566 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -22,28 +22,26 @@ def test_sanity() -> None: Image.new("HSV", (100, 100)) -def wedge() -> Image.Image: - w = Image._wedge() - w90 = w.rotate(90) +def linear_gradient() -> Image.Image: + im = Image.linear_gradient(mode="L") + im90 = im.rotate(90) - (px, h) = w.size + (px, h) = im.size r = Image.new("L", (px * 3, h)) g = r.copy() b = r.copy() - r.paste(w, (0, 0)) - r.paste(w90, (px, 0)) + r.paste(im, (0, 0)) + r.paste(im90, (px, 0)) - g.paste(w90, (0, 0)) - g.paste(w, (2 * px, 0)) + g.paste(im90, (0, 0)) + g.paste(im, (2 * px, 0)) - b.paste(w, (px, 0)) - b.paste(w90, (2 * px, 0)) + b.paste(im, (px, 0)) + b.paste(im90, (2 * px, 0)) - img = Image.merge("RGB", (r, g, b)) - - return img + return Image.merge("RGB", (r, g, b)) def to_xxx_colorsys( @@ -79,8 +77,8 @@ def to_rgb_colorsys(im: Image.Image) -> Image.Image: return to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB") -def test_wedge() -> None: - src = wedge().resize((3 * 32, 32), Image.Resampling.BILINEAR) +def test_linear_gradient() -> None: + src = linear_gradient().resize((3 * 32, 32), Image.Resampling.BILINEAR) im = src.convert("HSV") comparable = to_hsv_colorsys(src) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index e723b6a2e..a5243549f 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2996,15 +2996,6 @@ class ImageTransformHandler: # -------------------------------------------------------------------- # Factories -# -# Debugging - - -def _wedge() -> Image: - """Create grayscale wedge (for debugging only)""" - - return Image()._new(core.wedge("L")) - def _check_size(size: Any) -> None: """ diff --git a/src/_imaging.c b/src/_imaging.c index ee373e964..daaa56c75 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -4256,7 +4256,6 @@ static PyMethodDef functions[] = { {"effect_noise", (PyCFunction)_effect_noise, METH_VARARGS}, {"linear_gradient", (PyCFunction)_linear_gradient, METH_VARARGS}, {"radial_gradient", (PyCFunction)_radial_gradient, METH_VARARGS}, - {"wedge", (PyCFunction)_linear_gradient, METH_VARARGS}, /* Compatibility */ /* Drawing support stuff */ {"font", (PyCFunction)_font_new, METH_VARARGS}, From 028f0d6ea9263928f29e5b62fa67d95870f57144 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 15 Feb 2025 14:42:28 +0200 Subject: [PATCH 18/27] Remove unused data read --- Tests/test_file_gif.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 974aedeb6..6a295e89a 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -22,9 +22,6 @@ from .helper import ( # sample gif stream TEST_GIF = "Tests/images/hopper.gif" -with open(TEST_GIF, "rb") as f: - data = f.read() - def test_sanity() -> None: with Image.open(TEST_GIF) as im: From 126026e5e544ed35a7ea82349f41a1281facccf9 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 15 Feb 2025 14:44:00 +0200 Subject: [PATCH 19/27] Don't shadow builtin open --- Tests/test_file_gif.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 6a295e89a..d50842019 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -34,12 +34,12 @@ def test_sanity() -> None: @pytest.mark.skipif(is_pypy(), reason="Requires CPython") def test_unclosed_file() -> None: - def open() -> None: + def open_test_image() -> None: im = Image.open(TEST_GIF) im.load() with pytest.warns(ResourceWarning): - open() + open_test_image() def test_closed_file() -> None: From 7f414846a3ef1cc5a740a74aa6cdeb49b58a373d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 16 Feb 2025 05:08:22 +1100 Subject: [PATCH 20/27] Don't shadow builtin open --- Tests/test_file_dcx.py | 4 ++-- Tests/test_file_fli.py | 4 ++-- Tests/test_file_im.py | 4 ++-- Tests/test_file_mpo.py | 4 ++-- Tests/test_file_psd.py | 4 ++-- Tests/test_file_spider.py | 4 ++-- Tests/test_file_tiff.py | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Tests/test_file_dcx.py b/Tests/test_file_dcx.py index 5deacd878..ab6b9f983 100644 --- a/Tests/test_file_dcx.py +++ b/Tests/test_file_dcx.py @@ -26,12 +26,12 @@ def test_sanity() -> None: @pytest.mark.skipif(is_pypy(), reason="Requires CPython") def test_unclosed_file() -> None: - def open() -> None: + def open_test_image() -> None: im = Image.open(TEST_FILE) im.load() with pytest.warns(ResourceWarning): - open() + open_test_image() def test_closed_file() -> None: diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 876561a88..8adbd30f5 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -52,12 +52,12 @@ def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None: @pytest.mark.skipif(is_pypy(), reason="Requires CPython") def test_unclosed_file() -> None: - def open() -> None: + def open_test_image() -> None: im = Image.open(static_test_file) im.load() with pytest.warns(ResourceWarning): - open() + open_test_image() def test_closed_file() -> None: diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index 1d3fa485f..d29998801 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -31,12 +31,12 @@ def test_name_limit(tmp_path: Path) -> None: @pytest.mark.skipif(is_pypy(), reason="Requires CPython") def test_unclosed_file() -> None: - def open() -> None: + def open_test_image() -> None: im = Image.open(TEST_IM) im.load() with pytest.warns(ResourceWarning): - open() + open_test_image() def test_closed_file() -> None: diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 66fa29177..311085cf7 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -38,12 +38,12 @@ def test_sanity(test_file: str) -> None: @pytest.mark.skipif(is_pypy(), reason="Requires CPython") def test_unclosed_file() -> None: - def open() -> None: + def open_test_image() -> None: im = Image.open(test_files[0]) im.load() with pytest.warns(ResourceWarning): - open() + open_test_image() def test_closed_file() -> None: diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 5f22001f3..1793c269d 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -25,12 +25,12 @@ def test_sanity() -> None: @pytest.mark.skipif(is_pypy(), reason="Requires CPython") def test_unclosed_file() -> None: - def open() -> None: + def open_test_image() -> None: im = Image.open(test_file) im.load() with pytest.warns(ResourceWarning): - open() + open_test_image() def test_closed_file() -> None: diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 713db848d..cdb7b3e0b 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -24,12 +24,12 @@ def test_sanity() -> None: @pytest.mark.skipif(is_pypy(), reason="Requires CPython") def test_unclosed_file() -> None: - def open() -> None: + def open_test_image() -> None: im = Image.open(TEST_FILE) im.load() with pytest.warns(ResourceWarning): - open() + open_test_image() def test_closed_file() -> None: diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index fe8f69848..a8a407963 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -63,12 +63,12 @@ class TestFileTiff: @pytest.mark.skipif(is_pypy(), reason="Requires CPython") def test_unclosed_file(self) -> None: - def open() -> None: + def open_test_image() -> None: im = Image.open("Tests/images/multipage.tiff") im.load() with pytest.warns(ResourceWarning): - open() + open_test_image() def test_closed_file(self) -> None: with warnings.catch_warnings(): From 0fbe1860c4f2688a7e18a1b4e525b4b5fb1c5d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Sun, 16 Feb 2025 16:32:24 +0100 Subject: [PATCH 21/27] Update `pythoncapi_compat.h` to fix building with PyPy3.11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update `pythoncapi_compat.h` to upstream commit c84545f0e1e21757d4901f75c47333d25a3fcff0, which includes fixes necessary for Pillow to build against PyPy3.11. Otherwise, it fails due to duplicate declarations: ``` In file included from src/encode.c:28: src/thirdparty/pythoncapi_compat.h:295:1: error: static declaration of ‘PyThreadState_GetInterpreter’ follows non-static declaration 295 | PyThreadState_GetInterpreter(PyThreadState *tstate) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ In file included from /usr/include/pypy3.11/Python.h:80, from src/encode.c:26: /usr/include/pypy3.11/pystate.h:35:33: note: previous declaration of ‘PyThreadState_GetInterpreter’ with type ‘PyInterpreterState *(PyThreadState *)’ {aka ‘struct _is *(struct _ts *)’} 35 | PyAPI_FUNC(PyInterpreterState*) PyThreadState_GetInterpreter(PyThreadState *tstate); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` --- src/thirdparty/pythoncapi_compat.h | 521 ++++++++++++++++++++++++++++- 1 file changed, 514 insertions(+), 7 deletions(-) diff --git a/src/thirdparty/pythoncapi_compat.h b/src/thirdparty/pythoncapi_compat.h index ca23d5ffa..04fcf61e0 100644 --- a/src/thirdparty/pythoncapi_compat.h +++ b/src/thirdparty/pythoncapi_compat.h @@ -10,7 +10,7 @@ // https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h // // This file was vendored from the following commit: -// https://github.com/python/pythoncapi-compat/commit/0041177c4f348c8952b4c8980b2c90856e61c7c7 +// https://github.com/python/pythoncapi-compat/commit/c84545f0e1e21757d4901f75c47333d25a3fcff0 // // SPDX-License-Identifier: 0BSD @@ -22,11 +22,15 @@ extern "C" { #endif #include +#include // offsetof() // Python 3.11.0b4 added PyFrame_Back() to Python.h #if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION) # include "frameobject.h" // PyFrameObject, PyFrame_GetBack() #endif +#if PY_VERSION_HEX < 0x030C00A3 +# include // T_SHORT, READONLY +#endif #ifndef _Py_CAST @@ -290,7 +294,7 @@ PyFrame_GetVarString(PyFrameObject *frame, const char *name) // bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5 -#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION) +#if PY_VERSION_HEX < 0x030900A5 || (defined(PYPY_VERSION) && PY_VERSION_HEX < 0x030B0000) static inline PyInterpreterState * PyThreadState_GetInterpreter(PyThreadState *tstate) { @@ -583,7 +587,7 @@ static inline int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) return 0; } *pobj = Py_NewRef(obj); - return (*pobj != NULL); + return 1; } #endif @@ -921,7 +925,7 @@ static inline int PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg) { PyObject **dict = _PyObject_GetDictPtr(obj); - if (*dict == NULL) { + if (dict == NULL || *dict == NULL) { return -1; } Py_VISIT(*dict); @@ -932,7 +936,7 @@ static inline void PyObject_ClearManagedDict(PyObject *obj) { PyObject **dict = _PyObject_GetDictPtr(obj); - if (*dict == NULL) { + if (dict == NULL || *dict == NULL) { return; } Py_CLEAR(*dict); @@ -1207,11 +1211,11 @@ static inline int PyTime_PerfCounter(PyTime_t *result) #endif // gh-111389 added hash constants to Python 3.13.0a5. These constants were -// added first as private macros to Python 3.4.0b1 and PyPy 7.3.9. +// added first as private macros to Python 3.4.0b1 and PyPy 7.3.8. #if (!defined(PyHASH_BITS) \ && ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \ || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \ - && PYPY_VERSION_NUM >= 0x07090000))) + && PYPY_VERSION_NUM >= 0x07030800))) # define PyHASH_BITS _PyHASH_BITS # define PyHASH_MODULUS _PyHASH_MODULUS # define PyHASH_INF _PyHASH_INF @@ -1523,6 +1527,36 @@ static inline int PyLong_GetSign(PyObject *obj, int *sign) } #endif +// gh-126061 added PyLong_IsPositive/Negative/Zero() to Python in 3.14.0a2 +#if PY_VERSION_HEX < 0x030E00A2 +static inline int PyLong_IsPositive(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == 1; +} + +static inline int PyLong_IsNegative(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == -1; +} + +static inline int PyLong_IsZero(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == 0; +} +#endif + // gh-124502 added PyUnicode_Equal() to Python 3.14.0a0 #if PY_VERSION_HEX < 0x030E00A0 @@ -1693,6 +1727,479 @@ static inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue) #endif +// gh-102471 added import and export API for integers to 3.14.0a2. +#if PY_VERSION_HEX < 0x030E00A2 && PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) +// Helpers to access PyLongObject internals. +static inline void +_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size) +{ +#if PY_VERSION_HEX >= 0x030C0000 + op->long_value.lv_tag = (uintptr_t)(1 - sign) | ((uintptr_t)(size) << 3); +#elif PY_VERSION_HEX >= 0x030900A4 + Py_SET_SIZE(op, sign * size); +#else + Py_SIZE(op) = sign * size; +#endif +} + +static inline Py_ssize_t +_PyLong_DigitCount(const PyLongObject *op) +{ +#if PY_VERSION_HEX >= 0x030C0000 + return (Py_ssize_t)(op->long_value.lv_tag >> 3); +#else + return _PyLong_Sign((PyObject*)op) < 0 ? -Py_SIZE(op) : Py_SIZE(op); +#endif +} + +static inline digit* +_PyLong_GetDigits(const PyLongObject *op) +{ +#if PY_VERSION_HEX >= 0x030C0000 + return (digit*)(op->long_value.ob_digit); +#else + return (digit*)(op->ob_digit); +#endif +} + +typedef struct PyLongLayout { + uint8_t bits_per_digit; + uint8_t digit_size; + int8_t digits_order; + int8_t digit_endianness; +} PyLongLayout; + +typedef struct PyLongExport { + int64_t value; + uint8_t negative; + Py_ssize_t ndigits; + const void *digits; + Py_uintptr_t _reserved; +} PyLongExport; + +typedef struct PyLongWriter PyLongWriter; + +static inline const PyLongLayout* +PyLong_GetNativeLayout(void) +{ + static const PyLongLayout PyLong_LAYOUT = { + PyLong_SHIFT, + sizeof(digit), + -1, // least significant first + PY_LITTLE_ENDIAN ? -1 : 1, + }; + + return &PyLong_LAYOUT; +} + +static inline int +PyLong_Export(PyObject *obj, PyLongExport *export_long) +{ + if (!PyLong_Check(obj)) { + memset(export_long, 0, sizeof(*export_long)); + PyErr_Format(PyExc_TypeError, "expected int, got %s", + Py_TYPE(obj)->tp_name); + return -1; + } + + // Fast-path: try to convert to a int64_t + PyLongObject *self = (PyLongObject*)obj; + int overflow; +#if SIZEOF_LONG == 8 + long value = PyLong_AsLongAndOverflow(obj, &overflow); +#else + // Windows has 32-bit long, so use 64-bit long long instead + long long value = PyLong_AsLongLongAndOverflow(obj, &overflow); +#endif + Py_BUILD_ASSERT(sizeof(value) == sizeof(int64_t)); + // the function cannot fail since obj is a PyLongObject + assert(!(value == -1 && PyErr_Occurred())); + + if (!overflow) { + export_long->value = value; + export_long->negative = 0; + export_long->ndigits = 0; + export_long->digits = 0; + export_long->_reserved = 0; + } + else { + export_long->value = 0; + export_long->negative = _PyLong_Sign(obj) < 0; + export_long->ndigits = _PyLong_DigitCount(self); + if (export_long->ndigits == 0) { + export_long->ndigits = 1; + } + export_long->digits = _PyLong_GetDigits(self); + export_long->_reserved = (Py_uintptr_t)Py_NewRef(obj); + } + return 0; +} + +static inline void +PyLong_FreeExport(PyLongExport *export_long) +{ + PyObject *obj = (PyObject*)export_long->_reserved; + + if (obj) { + export_long->_reserved = 0; + Py_DECREF(obj); + } +} + +static inline PyLongWriter* +PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits) +{ + if (ndigits <= 0) { + PyErr_SetString(PyExc_ValueError, "ndigits must be positive"); + return NULL; + } + assert(digits != NULL); + + PyLongObject *obj = _PyLong_New(ndigits); + if (obj == NULL) { + return NULL; + } + _PyLong_SetSignAndDigitCount(obj, negative?-1:1, ndigits); + + *digits = _PyLong_GetDigits(obj); + return (PyLongWriter*)obj; +} + +static inline void +PyLongWriter_Discard(PyLongWriter *writer) +{ + PyLongObject *obj = (PyLongObject *)writer; + + assert(Py_REFCNT(obj) == 1); + Py_DECREF(obj); +} + +static inline PyObject* +PyLongWriter_Finish(PyLongWriter *writer) +{ + PyObject *obj = (PyObject *)writer; + PyLongObject *self = (PyLongObject*)obj; + Py_ssize_t j = _PyLong_DigitCount(self); + Py_ssize_t i = j; + int sign = _PyLong_Sign(obj); + + assert(Py_REFCNT(obj) == 1); + + // Normalize and get singleton if possible + while (i > 0 && _PyLong_GetDigits(self)[i-1] == 0) { + --i; + } + if (i != j) { + if (i == 0) { + sign = 0; + } + _PyLong_SetSignAndDigitCount(self, sign, i); + } + if (i <= 1) { + long val = sign * (long)(_PyLong_GetDigits(self)[0]); + Py_DECREF(obj); + return PyLong_FromLong(val); + } + + return obj; +} +#endif + + +#if PY_VERSION_HEX < 0x030C00A3 +# define Py_T_SHORT T_SHORT +# define Py_T_INT T_INT +# define Py_T_LONG T_LONG +# define Py_T_FLOAT T_FLOAT +# define Py_T_DOUBLE T_DOUBLE +# define Py_T_STRING T_STRING +# define _Py_T_OBJECT T_OBJECT +# define Py_T_CHAR T_CHAR +# define Py_T_BYTE T_BYTE +# define Py_T_UBYTE T_UBYTE +# define Py_T_USHORT T_USHORT +# define Py_T_UINT T_UINT +# define Py_T_ULONG T_ULONG +# define Py_T_STRING_INPLACE T_STRING_INPLACE +# define Py_T_BOOL T_BOOL +# define Py_T_OBJECT_EX T_OBJECT_EX +# define Py_T_LONGLONG T_LONGLONG +# define Py_T_ULONGLONG T_ULONGLONG +# define Py_T_PYSSIZET T_PYSSIZET + +# if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) +# define _Py_T_NONE T_NONE +# endif + +# define Py_READONLY READONLY +# define Py_AUDIT_READ READ_RESTRICTED +# define _Py_WRITE_RESTRICTED PY_WRITE_RESTRICTED +#endif + + +// gh-127350 added Py_fopen() and Py_fclose() to Python 3.14a4 +#if PY_VERSION_HEX < 0x030E00A4 +static inline FILE* Py_fopen(PyObject *path, const char *mode) +{ +#if 0x030400A2 <= PY_VERSION_HEX && !defined(PYPY_VERSION) + PyAPI_FUNC(FILE*) _Py_fopen_obj(PyObject *path, const char *mode); + + return _Py_fopen_obj(path, mode); +#else + FILE *f; + PyObject *bytes; +#if PY_VERSION_HEX >= 0x03000000 + if (!PyUnicode_FSConverter(path, &bytes)) { + return NULL; + } +#else + if (!PyString_Check(path)) { + PyErr_SetString(PyExc_TypeError, "except str"); + return NULL; + } + bytes = Py_NewRef(path); +#endif + const char *path_bytes = PyBytes_AS_STRING(bytes); + + f = fopen(path_bytes, mode); + Py_DECREF(bytes); + + if (f == NULL) { + PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path); + return NULL; + } + return f; +#endif +} + +static inline int Py_fclose(FILE *file) +{ + return fclose(file); +} +#endif + + +#if 0x03090000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030E0000 && !defined(PYPY_VERSION) +static inline PyObject* +PyConfig_Get(const char *name) +{ + typedef enum { + _PyConfig_MEMBER_INT, + _PyConfig_MEMBER_UINT, + _PyConfig_MEMBER_ULONG, + _PyConfig_MEMBER_BOOL, + _PyConfig_MEMBER_WSTR, + _PyConfig_MEMBER_WSTR_OPT, + _PyConfig_MEMBER_WSTR_LIST, + } PyConfigMemberType; + + typedef struct { + const char *name; + size_t offset; + PyConfigMemberType type; + const char *sys_attr; + } PyConfigSpec; + +#define PYTHONCAPI_COMPAT_SPEC(MEMBER, TYPE, sys_attr) \ + {#MEMBER, offsetof(PyConfig, MEMBER), \ + _PyConfig_MEMBER_##TYPE, sys_attr} + + static const PyConfigSpec config_spec[] = { + PYTHONCAPI_COMPAT_SPEC(argv, WSTR_LIST, "argv"), + PYTHONCAPI_COMPAT_SPEC(base_exec_prefix, WSTR_OPT, "base_exec_prefix"), + PYTHONCAPI_COMPAT_SPEC(base_executable, WSTR_OPT, "_base_executable"), + PYTHONCAPI_COMPAT_SPEC(base_prefix, WSTR_OPT, "base_prefix"), + PYTHONCAPI_COMPAT_SPEC(bytes_warning, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(exec_prefix, WSTR_OPT, "exec_prefix"), + PYTHONCAPI_COMPAT_SPEC(executable, WSTR_OPT, "executable"), + PYTHONCAPI_COMPAT_SPEC(inspect, BOOL, _Py_NULL), +#if 0x030C0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(int_max_str_digits, UINT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(interactive, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(module_search_paths, WSTR_LIST, "path"), + PYTHONCAPI_COMPAT_SPEC(optimization_level, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(parser_debug, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(platlibdir, WSTR, "platlibdir"), + PYTHONCAPI_COMPAT_SPEC(prefix, WSTR_OPT, "prefix"), + PYTHONCAPI_COMPAT_SPEC(pycache_prefix, WSTR_OPT, "pycache_prefix"), + PYTHONCAPI_COMPAT_SPEC(quiet, BOOL, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(stdlib_dir, WSTR_OPT, "_stdlib_dir"), +#endif + PYTHONCAPI_COMPAT_SPEC(use_environment, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(verbose, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(warnoptions, WSTR_LIST, "warnoptions"), + PYTHONCAPI_COMPAT_SPEC(write_bytecode, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(xoptions, WSTR_LIST, "_xoptions"), + PYTHONCAPI_COMPAT_SPEC(buffered_stdio, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(check_hash_pycs_mode, WSTR, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(code_debug_ranges, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(configure_c_stdio, BOOL, _Py_NULL), +#if 0x030D0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(cpu_count, INT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(dev_mode, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(dump_refs, BOOL, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(dump_refs_file, WSTR_OPT, _Py_NULL), +#endif +#ifdef Py_GIL_DISABLED + PYTHONCAPI_COMPAT_SPEC(enable_gil, INT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(faulthandler, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(filesystem_encoding, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(filesystem_errors, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(hash_seed, ULONG, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(home, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(import_time, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(install_signal_handlers, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(isolated, BOOL, _Py_NULL), +#ifdef MS_WINDOWS + PYTHONCAPI_COMPAT_SPEC(legacy_windows_stdio, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(malloc_stats, BOOL, _Py_NULL), +#if 0x030A0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(orig_argv, WSTR_LIST, "orig_argv"), +#endif + PYTHONCAPI_COMPAT_SPEC(parse_argv, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(pathconfig_warnings, BOOL, _Py_NULL), +#if 0x030C0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(perf_profiling, UINT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(program_name, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_command, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_filename, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_module, WSTR_OPT, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(safe_path, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(show_ref_count, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(site_import, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(skip_source_first_line, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(stdio_encoding, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(stdio_errors, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(tracemalloc, UINT, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(use_frozen_modules, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(use_hash_seed, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(user_site_directory, BOOL, _Py_NULL), +#if 0x030A0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(warn_default_encoding, BOOL, _Py_NULL), +#endif + }; + +#undef PYTHONCAPI_COMPAT_SPEC + + const PyConfigSpec *spec; + int found = 0; + for (size_t i=0; i < sizeof(config_spec) / sizeof(config_spec[0]); i++) { + spec = &config_spec[i]; + if (strcmp(spec->name, name) == 0) { + found = 1; + break; + } + } + if (found) { + if (spec->sys_attr != NULL) { + PyObject *value = PySys_GetObject(spec->sys_attr); + if (value == NULL) { + PyErr_Format(PyExc_RuntimeError, "lost sys.%s", spec->sys_attr); + return NULL; + } + return Py_NewRef(value); + } + + PyAPI_FUNC(const PyConfig*) _Py_GetConfig(void); + + const PyConfig *config = _Py_GetConfig(); + void *member = (char *)config + spec->offset; + switch (spec->type) { + case _PyConfig_MEMBER_INT: + case _PyConfig_MEMBER_UINT: + { + int value = *(int *)member; + return PyLong_FromLong(value); + } + case _PyConfig_MEMBER_BOOL: + { + int value = *(int *)member; + return PyBool_FromLong(value != 0); + } + case _PyConfig_MEMBER_ULONG: + { + unsigned long value = *(unsigned long *)member; + return PyLong_FromUnsignedLong(value); + } + case _PyConfig_MEMBER_WSTR: + case _PyConfig_MEMBER_WSTR_OPT: + { + wchar_t *wstr = *(wchar_t **)member; + if (wstr != NULL) { + return PyUnicode_FromWideChar(wstr, -1); + } + else { + return Py_NewRef(Py_None); + } + } + case _PyConfig_MEMBER_WSTR_LIST: + { + const PyWideStringList *list = (const PyWideStringList *)member; + PyObject *tuple = PyTuple_New(list->length); + if (tuple == NULL) { + return NULL; + } + + for (Py_ssize_t i = 0; i < list->length; i++) { + PyObject *item = PyUnicode_FromWideChar(list->items[i], -1); + if (item == NULL) { + Py_DECREF(tuple); + return NULL; + } + PyTuple_SET_ITEM(tuple, i, item); + } + return tuple; + } + default: + Py_UNREACHABLE(); + } + } + + PyErr_Format(PyExc_ValueError, "unknown config option name: %s", name); + return NULL; +} + +static inline int +PyConfig_GetInt(const char *name, int *value) +{ + PyObject *obj = PyConfig_Get(name); + if (obj == NULL) { + return -1; + } + + if (!PyLong_Check(obj)) { + Py_DECREF(obj); + PyErr_Format(PyExc_TypeError, "config option %s is not an int", name); + return -1; + } + + int as_int = PyLong_AsInt(obj); + Py_DECREF(obj); + if (as_int == -1 && PyErr_Occurred()) { + PyErr_Format(PyExc_OverflowError, + "config option %s value does not fit into a C int", name); + return -1; + } + + *value = as_int; + return 0; +} +#endif // PY_VERSION_HEX > 0x03090000 && !defined(PYPY_VERSION) + + #ifdef __cplusplus } #endif From 216690ff17d03c80860b59f5ba2311383a09525c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 7 Feb 2025 18:11:54 +0200 Subject: [PATCH 22/27] Add PyPy3.11 to CI --- .github/workflows/test-windows.yml | 2 +- .github/workflows/test.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 8faab2ef4..ef49ff332 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -35,7 +35,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["pypy3.10", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", "3.13", "3.14"] architecture: ["x64"] os: ["windows-latest"] include: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e3efe0b59..c4ad88be9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,6 +41,7 @@ jobs: "ubuntu-latest", ] python-version: [ + "pypy3.11", "pypy3.10", "3.14", "3.13t", From 9762c9e30eeb6adc6815b25ad619432f707d4632 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 17 Feb 2025 20:20:02 +1100 Subject: [PATCH 23/27] Test unexpected end of tar file --- Tests/test_file_tar.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index 49220a8b6..d1a4ca9de 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -1,6 +1,7 @@ from __future__ import annotations import warnings +from pathlib import Path import pytest @@ -29,6 +30,16 @@ def test_sanity(codec: str, test_path: str, format: str) -> None: assert im.format == format +def test_unexpected_end(tmp_path: Path) -> None: + tmpfile = str(tmp_path / "temp.tar") + with open(tmpfile, "w"): + pass + + with pytest.raises(OSError): + with TarIO.TarIO(tmpfile, "test"): + pass + + @pytest.mark.skipif(is_pypy(), reason="Requires CPython") def test_unclosed_file() -> None: with pytest.warns(ResourceWarning): From 152d982644f4474d14597b4cba878903db400a67 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 17 Feb 2025 20:20:45 +1100 Subject: [PATCH 24/27] Test missing subfile --- Tests/test_file_tar.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index d1a4ca9de..e1a6a55d7 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -40,6 +40,12 @@ def test_unexpected_end(tmp_path: Path) -> None: pass +def test_cannot_find_subfile(tmp_path: Path) -> None: + with pytest.raises(OSError): + with TarIO.TarIO(TEST_TAR_FILE, "test"): + pass + + @pytest.mark.skipif(is_pypy(), reason="Requires CPython") def test_unclosed_file() -> None: with pytest.warns(ResourceWarning): From 15e4c1a72451672d035e9e1525ccac539252e742 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 15 Feb 2025 21:34:25 +0200 Subject: [PATCH 25/27] Fix ShellCheck --- .ci/install.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/install.sh b/.ci/install.sh index 5c20e7f37..e61752750 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -2,12 +2,12 @@ aptget_update() { - if [ ! -z $1 ]; then + if [ -n "$1" ]; then echo "" echo "Retrying apt-get update..." echo "" fi - output=`sudo apt-get update 2>&1` + output=$(sudo apt-get update 2>&1) echo "$output" if [[ $output == *[WE]:\ * ]]; then return 1 From 017b16b803347bceb19361230c751267fab6e660 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 17 Feb 2025 21:48:09 +1100 Subject: [PATCH 26/27] Removed argument Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Tests/test_file_tar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index e1a6a55d7..9fc3edcd7 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -40,7 +40,7 @@ def test_unexpected_end(tmp_path: Path) -> None: pass -def test_cannot_find_subfile(tmp_path: Path) -> None: +def test_cannot_find_subfile() -> None: with pytest.raises(OSError): with TarIO.TarIO(TEST_TAR_FILE, "test"): pass From 19010bb301e559dd6c9b255c9e3134bbde31297f Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 17 Feb 2025 21:49:08 +1100 Subject: [PATCH 27/27] Use match Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Tests/test_file_tar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index 9fc3edcd7..084d0f288 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -35,13 +35,13 @@ def test_unexpected_end(tmp_path: Path) -> None: with open(tmpfile, "w"): pass - with pytest.raises(OSError): + with pytest.raises(OSError, match="unexpected end of tar file"): with TarIO.TarIO(tmpfile, "test"): pass def test_cannot_find_subfile() -> None: - with pytest.raises(OSError): + with pytest.raises(OSError, match="cannot find subfile"): with TarIO.TarIO(TEST_TAR_FILE, "test"): pass