From 56238d56f127c329e7c072149fd47082a4e93eba Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Sat, 18 May 2024 12:54:01 +0200 Subject: [PATCH] For Python 3.13: A drop-in replacement for imghdr.what() --- .github/workflows/tests.yml | 1 + puremagic/main.py | 29 +++++++++ test/test_main.py | 116 ++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 test/test_main.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3e1c5f5..d2add50 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,6 +13,7 @@ jobs: build: strategy: + fail-fast: false matrix: os: [ubuntu-latest] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] diff --git a/puremagic/main.py b/puremagic/main.py index b5a81e3..f2abfdd 100644 --- a/puremagic/main.py +++ b/puremagic/main.py @@ -35,6 +35,10 @@ "multi_part_dict", ] +# Convert puremagic extensions to imghdr extensions +imghdr_exts = {"dib": "bmp", "jfif": "jpeg", "jpg": "jpeg", "rst": "rast", "sun": "rast", "tif": "tiff"} + + here = os.path.abspath(os.path.dirname(__file__)) PureMagic = namedtuple( @@ -387,5 +391,30 @@ def command_line_entry(*args): print("'{0}' : could not be Identified".format(fn)) +def what(file: Union[os.PathLike, str, None], h: Union[str, bytes, None]) -> str: + """A drop-in replacement for `imghdr.what()` which was removed from the standard + library in Python 3.13. + + Usage: + ```python + # Replace... + import imghdr + ext = imghdr.what(...) + # with... + import puremagic + ext = puremagic.what(...) + # --- + # Or replace... + from imghdr import what + # with... + from puremagic import what + ``` + + Source code for `imghdr`: https://github.com/python/cpython/blob/3.12/Lib/imghdr.py + """ + ext = (_magic(h, b"", False) if h else from_file(file, False)).lstrip(".") + return imghdr_exts.get(ext, ext) + + if __name__ == "__main__": command_line_entry() diff --git a/test/test_main.py b/test/test_main.py new file mode 100644 index 0000000..05eb419 --- /dev/null +++ b/test/test_main.py @@ -0,0 +1,116 @@ +from pathlib import Path +from sys import version_info +from warnings import filterwarnings + +try: # imghdr was removed from the standard library in Python 3.13 + filterwarnings("ignore", category=DeprecationWarning) + from imghdr import what as imghdr_what +except ModuleNotFoundError: + imghdr_what = None + +import pytest + +from puremagic.main import PureError, what + +files = "bmp exr gif jpg pbm pgm png ppm ras rgb tif webp xbm".split() +files = "bmp gif jpg png tif webp".split() # TODO: (cclauss) Add the remainin files + + +@pytest.mark.skipif(imghdr_what is None, reason="imghdr was removed from the standard library in Python 3.13") +@pytest.mark.parametrize("file", files) +def test_what_from_file(file, h=None): + file = f"test/resources/images/test.{file}" + assert what(file, h) == imghdr_what(file, h) + file = Path(file).resolve() + assert what(file, h) == imghdr_what(file, h) + + +string_tests = [ + ("bmp", b"BM"), + ("bmp", "424d"), + ("bmp", "424d787878785c3030305c303030"), + ("exr", "762f3101"), + ("exr", b"\x76\x2f\x31\x01"), + ("gif", "474946383761"), + ("gif", b"GIF87a"), + ("gif", b"GIF89a"), + ("pbm", b"P1 "), + ("pbm", b"P1\n"), + ("pbm", b"P1\r"), + ("pbm", b"P1\t"), + ("pbm", b"P4 "), + ("pbm", b"P4\n"), + ("pbm", b"P4\r"), + ("pbm", b"P4\t"), + ("pgm", b"P2 "), + ("pgm", b"P2\n"), + ("pgm", b"P2\r"), + ("pgm", b"P2\t"), + ("pgm", b"P5 "), + ("pgm", b"P5\n"), + ("pgm", b"P5\r"), + ("pgm", b"P5\t"), + ("png", "89504e470d0a1a0a"), + ("png", b"\211PNG\r\n\032\n"), + ("ppm", b"P3 "), + ("ppm", b"P3\n"), + ("ppm", b"P3\r"), + ("ppm", b"P3\t"), + ("ppm", b"P6 "), + ("ppm", b"P6\n"), + ("ppm", b"P6\r"), + ("ppm", b"P6\t"), + ("rast", b"\x59\xA6\x6A\x95"), + ("webp", b"RIFF____WEBP"), +] + + +@pytest.mark.skipif(imghdr_what is None, reason="imghdr was removed from the standard library in Python 3.13") +@pytest.mark.parametrize("expected, h", string_tests) +def test_what_from_string(expected, h): + if isinstance(h, str): + h = bytes.fromhex(h) + assert imghdr_what(None, h) == what(None, h) == expected + + +@pytest.mark.skipif(imghdr_what is None, reason="imghdr was removed from the standard library in Python 3.13") +@pytest.mark.parametrize( + "expected, h", + [ + ("jpeg", "ffd8ffdb"), + ("jpeg", b"\xff\xd8\xff\xdb"), + ], +) +def test_puremagic_what_from_string_py311(expected, h): + """ + These tests fail with imghdr on Python < 3.11. + TODO: (cclauss) Document these imghdr fails on Python < 3.11 + """ + if isinstance(h, str): + h = bytes.fromhex(h) + assert what(None, h) == expected + if version_info < (3, 11): # TODO: Document these imghdr fails + expected = None + assert imghdr_what(None, h) == expected + + +@pytest.mark.skipif(imghdr_what is None, reason="imghdr was removed from the standard library in Python 3.13") +@pytest.mark.parametrize( + "expected, h", + [ + ("jpeg", b"______JFIF"), + ("jpeg", b"______Exif"), + ("rgb", b"\001\332"), + ("tiff", b"II"), + ("tiff", b"MM"), + ("xbm", b"#define "), + ], +) +def test_puremagic_what_from_string_todo(expected, h): + """ + These tests pass with imghdr but fail with puremagic. + TODO: (cclauss) Fix these puremagic fails + """ + assert imghdr_what(None, h) == expected + with pytest.raises(PureError): # TODO: Fix these puremagic fails + what(None, h)