Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add library version numbers to public API #4700

Merged
merged 4 commits into from
Jun 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions Tests/test_features.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import io
import re

import pytest
from PIL import features
Expand All @@ -21,6 +22,27 @@ def test_check():
assert features.check_feature(feature) == features.check(feature)


def test_version():
# Check the correctness of the convenience function
# and the format of version numbers

def test(name, function):
version = features.version(name)
if not features.check(name):
assert version is None
else:
assert function(name) == version
if name != "PIL":
assert version is None or re.search(r"\d+(\.\d+)*$", version)

for module in features.modules:
test(module, features.version_module)
for codec in features.codecs:
test(codec, features.version_codec)
for feature in features.features:
test(feature, features.version_feature)


@skip_unless_feature("webp")
def test_webp_transparency():
assert features.check("transp_webp") != _webp.WebPDecoderBuggyAlpha()
Expand All @@ -37,9 +59,22 @@ def test_webp_anim():
assert features.check("webp_anim") == _webp.HAVE_WEBPANIM


@skip_unless_feature("libjpeg_turbo")
def test_libjpeg_turbo_version():
assert re.search(r"\d+\.\d+\.\d+$", features.version("libjpeg_turbo"))


@skip_unless_feature("libimagequant")
def test_libimagequant_version():
assert re.search(r"\d+\.\d+\.\d+$", features.version("libimagequant"))


def test_check_modules():
for feature in features.modules:
assert features.check_module(feature) in [True, False]


def test_check_codecs():
for feature in features.codecs:
assert features.check_codec(feature) in [True, False]

Expand All @@ -64,6 +99,8 @@ def test_unsupported_codec():
# Act / Assert
with pytest.raises(ValueError):
features.check_codec(codec)
with pytest.raises(ValueError):
features.version_codec(codec)


def test_unsupported_module():
Expand All @@ -72,6 +109,8 @@ def test_unsupported_module():
# Act / Assert
with pytest.raises(ValueError):
features.check_module(module)
with pytest.raises(ValueError):
features.version_module(module)


def test_pilinfo():
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_file_icns.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
import sys

import pytest
from PIL import IcnsImagePlugin, Image
from PIL import IcnsImagePlugin, Image, features

from .helper import assert_image_equal, assert_image_similar

# sample icon file
TEST_FILE = "Tests/images/pillow.icns"

ENABLE_JPEG2K = hasattr(Image.core, "jp2klib_version")
ENABLE_JPEG2K = features.check_codec("jpg_2000")


def test_sanity():
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from io import BytesIO

import pytest
from PIL import ExifTags, Image, ImageFile, JpegImagePlugin
from PIL import ExifTags, Image, ImageFile, JpegImagePlugin, features

from .helper import (
assert_image,
Expand Down Expand Up @@ -41,7 +41,7 @@ def gen_random_image(self, size, mode="RGB"):
def test_sanity(self):

# internal version number
assert re.search(r"\d+\.\d+$", Image.core.jpeglib_version)
assert re.search(r"\d+\.\d+$", features.version_codec("jpg"))

with Image.open(TEST_FILE) as im:
im.load()
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_file_jpeg2k.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from io import BytesIO

import pytest
from PIL import Image, ImageFile, Jpeg2KImagePlugin
from PIL import Image, ImageFile, Jpeg2KImagePlugin, features

from .helper import (
assert_image_equal,
Expand Down Expand Up @@ -35,7 +35,7 @@ def roundtrip(im, **options):

def test_sanity():
# Internal version number
assert re.search(r"\d+\.\d+\.\d+$", Image.core.jp2klib_version)
assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("jpg_2000"))

with Image.open("Tests/images/test-card-lossless.jp2") as im:
px = im.load()
Expand Down
6 changes: 5 additions & 1 deletion Tests/test_file_libtiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import itertools
import logging
import os
import re
from collections import namedtuple
from ctypes import c_float

import pytest
from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags
from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features

from .helper import (
assert_image_equal,
Expand Down Expand Up @@ -47,6 +48,9 @@ def _assert_noerr(self, tmp_path, im):


class TestFileLibTiff(LibTiffTestCase):
def test_version(self):
assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("libtiff"))

def test_g4_tiff(self, tmp_path):
"""Test the ordinary file path load path"""

Expand Down
4 changes: 2 additions & 2 deletions Tests/test_file_png.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from io import BytesIO

import pytest
from PIL import Image, ImageFile, PngImagePlugin
from PIL import Image, ImageFile, PngImagePlugin, features

from .helper import (
PillowLeakTestCase,
Expand Down Expand Up @@ -73,7 +73,7 @@ def get_chunks(self, filename):
def test_sanity(self, tmp_path):

# internal version number
assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", Image.core.zlib_version)
assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib"))

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
@@ -1,7 +1,8 @@
import io
import re

import pytest
from PIL import Image, WebPImagePlugin
from PIL import Image, WebPImagePlugin, features

from .helper import (
assert_image_similar,
Expand Down Expand Up @@ -38,6 +39,7 @@ def setup_method(self):
def test_version(self):
_webp.WebPDecoderVersion()
_webp.WebPDecoderBuggyAlpha()
assert re.search(r"\d+\.\d+\.\d+$", features.version_module("webp"))

def test_read_rgb(self):
"""
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_imagecms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from io import BytesIO

import pytest
from PIL import Image, ImageMode
from PIL import Image, ImageMode, features

from .helper import assert_image, assert_image_equal, assert_image_similar, hopper

Expand Down Expand Up @@ -46,7 +46,7 @@ def test_sanity():
assert list(map(type, v)) == [str, str, str, str]

# internal version number
assert re.search(r"\d+\.\d+$", ImageCms.core.littlecms_version)
assert re.search(r"\d+\.\d+$", features.version_module("littlecms2"))

skip_missing()
i = ImageCms.profileToProfile(hopper(), SRGB, SRGB)
Expand Down
12 changes: 6 additions & 6 deletions Tests/test_imagefont.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from io import BytesIO

import pytest
from PIL import Image, ImageDraw, ImageFont
from PIL import Image, ImageDraw, ImageFont, features

from .helper import (
assert_image_equal,
Expand Down Expand Up @@ -40,7 +40,7 @@ class TestImageFont:

@classmethod
def setup_class(self):
freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version)
freetype = distutils.version.StrictVersion(features.version_module("freetype2"))

self.metrics = self.METRICS["Default"]
for conditions, metrics in self.METRICS.items():
Expand All @@ -67,7 +67,7 @@ def get_font(self):
)

def test_sanity(self):
assert re.search(r"\d+\.\d+\.\d+$", ImageFont.core.freetype2_version)
assert re.search(r"\d+\.\d+\.\d+$", features.version_module("freetype2"))

def test_font_properties(self):
ttf = self.get_font()
Expand Down Expand Up @@ -619,7 +619,7 @@ def test_complex_font_settings(self):
def test_variation_get(self):
font = self.get_font()

freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version)
freetype = distutils.version.StrictVersion(features.version_module("freetype2"))
if freetype < "2.9.1":
with pytest.raises(NotImplementedError):
font.get_variation_names()
Expand Down Expand Up @@ -691,7 +691,7 @@ def _check_text(self, font, path, epsilon):
def test_variation_set_by_name(self):
font = self.get_font()

freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version)
freetype = distutils.version.StrictVersion(features.version_module("freetype2"))
if freetype < "2.9.1":
with pytest.raises(NotImplementedError):
font.set_variation_by_name("Bold")
Expand All @@ -715,7 +715,7 @@ def test_variation_set_by_name(self):
def test_variation_set_by_axes(self):
font = self.get_font()

freetype = distutils.version.StrictVersion(ImageFont.core.freetype2_version)
freetype = distutils.version.StrictVersion(features.version_module("freetype2"))
if freetype < "2.9.1":
with pytest.raises(NotImplementedError):
font.set_variation_by_axes([100])
Expand Down
18 changes: 12 additions & 6 deletions docs/reference/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The :py:mod:`PIL.features` module can be used to detect which Pillow features ar

.. autofunction:: PIL.features.pilinfo
.. autofunction:: PIL.features.check
.. autofunction:: PIL.features.version
.. autofunction:: PIL.features.get_supported

Modules
Expand All @@ -16,45 +17,50 @@ Modules
Support for the following modules can be checked:

* ``pil``: The Pillow core module, required for all functionality.
* ``tkinter``: Tkinter support.
* ``tkinter``: Tkinter support. Version number not available.
* ``freetype2``: FreeType font support via :py:func:`PIL.ImageFont.truetype`.
* ``littlecms2``: LittleCMS 2 support via :py:mod:`PIL.ImageCms`.
* ``webp``: WebP image support.

.. autofunction:: PIL.features.check_module
.. autofunction:: PIL.features.version_module
.. autofunction:: PIL.features.get_supported_modules

Codecs
------

These are only checked during Pillow compilation.
Support for these is only checked during Pillow compilation.
If the required library was uninstalled from the system, the ``pil`` core module may fail to load instead.
Except for ``jpg``, the version number is checked at run-time.

Support for the following codecs can be checked:

* ``jpg``: (compile time) Libjpeg support, required for JPEG based image formats.
* ``jpg``: (compile time) Libjpeg support, required for JPEG based image formats. Only compile time version number is available.
* ``jpg_2000``: (compile time) OpenJPEG support, required for JPEG 2000 image formats.
* ``zlib``: (compile time) Zlib support, required for zlib compressed formats, such as PNG.
* ``libtiff``: (compile time) LibTIFF support, required for TIFF based image formats.

.. autofunction:: PIL.features.check_codec
.. autofunction:: PIL.features.version_codec
.. autofunction:: PIL.features.get_supported_codecs

Features
--------

Some of these are only checked during Pillow compilation.
If the required library was uninstalled from the system, the relevant module may fail to load instead.
Feature version numbers are available only where stated.

Support for the following features can be checked:

* ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg.
* ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg. Compile-time version number is available.
* ``transp_webp``: Support for transparency in WebP images.
* ``webp_mux``: (compile time) Support for EXIF data in WebP images.
* ``webp_anim``: (compile time) Support for animated WebP images.
* ``raqm``: Raqm library, required for ``ImageFont.LAYOUT_RAQM`` in :py:func:`PIL.ImageFont.truetype`.
* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`.
* ``raqm``: Raqm library, required for ``ImageFont.LAYOUT_RAQM`` in :py:func:`PIL.ImageFont.truetype`. Run-time version number is available for Raqm 0.7.0 or newer.
* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. Run-time version number is available.
* ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library.

.. autofunction:: PIL.features.check_feature
.. autofunction:: PIL.features.version_feature
.. autofunction:: PIL.features.get_supported_features
4 changes: 2 additions & 2 deletions src/PIL/IcnsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
import sys
import tempfile

from PIL import Image, ImageFile, PngImagePlugin
from PIL import Image, ImageFile, PngImagePlugin, features
from PIL._binary import i8

enable_jpeg2k = hasattr(Image.core, "jp2klib_version")
enable_jpeg2k = features.check_codec("jpg_2000")
if enable_jpeg2k:
from PIL import Jpeg2KImagePlugin

Expand Down
Loading