From 0e6429b6ab416de8f3ca0b3d49a65ba8c44df4a3 Mon Sep 17 00:00:00 2001 From: benoit74 Date: Tue, 26 May 2026 15:46:09 +0000 Subject: [PATCH] Fix `format_for` to not modify input BytesIO position --- CHANGELOG.md | 1 + src/zimscraperlib/image/probing.py | 6 ++++++ tests/image/test_image.py | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e41d8ca..f91b56d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improve contribution setup instructions: use `hatch shell`, clarify commands must be run from local clone root (#153) - Bring coverage back to 100% (#293) - Fix `Creator.config_indexing` docstring to reflect that only the full-text index is toggled; title indexing is always performed by libzim (#294) +- Fix `format_for` to not modify input BytesIO position (#296) ### Changed diff --git a/src/zimscraperlib/image/probing.py b/src/zimscraperlib/image/probing.py index 5f40874..6a8d263 100644 --- a/src/zimscraperlib/image/probing.py +++ b/src/zimscraperlib/image/probing.py @@ -68,6 +68,9 @@ def format_for( ) -> str | None: """Pillow format of a given filename, either Pillow-detected or from suffix""" if not from_suffix: + original_position = 0 + if isinstance(src, io.BytesIO): + original_position = src.tell() try: with PIL.Image.open(src) as img: return img.format @@ -85,6 +88,9 @@ def format_for( return "SVG" else: # pragma: no cover raise + finally: + if isinstance(src, io.BytesIO): + src.seek(original_position) if not isinstance(src, pathlib.Path): raise ValueError( diff --git a/tests/image/test_image.py b/tests/image/test_image.py index ed925cf..3b1174e 100644 --- a/tests/image/test_image.py +++ b/tests/image/test_image.py @@ -1058,6 +1058,25 @@ def test_format_for_cannot_use_suffix_with_byte_array(): assert format_for(src=io.BytesIO(), from_suffix=True) +@pytest.mark.parametrize( + "seek_to_zero", + [ + (True), + (False), + ], +) +def test_format_for_does_not_alter_buffer( + svg_image: pathlib.Path, *, seek_to_zero: bool +): + svg_bytes = io.BytesIO() + svg_bytes.write(svg_image.read_bytes()) + if seek_to_zero: + svg_bytes.seek(0) + original_position = svg_bytes.tell() + assert format_for(svg_bytes, from_suffix=False) == "SVG" + assert svg_bytes.tell() == original_position + + def test_is_valid_image( png_image: pathlib.Path, png_image2: pathlib.Path,