diff --git a/Tests/test_imagemath_lambda_eval.py b/Tests/test_imagemath_lambda_eval.py index 5769c903e..360325780 100644 --- a/Tests/test_imagemath_lambda_eval.py +++ b/Tests/test_imagemath_lambda_eval.py @@ -1,5 +1,9 @@ from __future__ import annotations +from typing import Any + +import pytest + from PIL import Image, ImageMath @@ -19,7 +23,7 @@ I = Image.new("I", (1, 1), 4) # noqa: E741 A2 = A.resize((2, 2)) B2 = B.resize((2, 2)) -images = {"A": A, "B": B, "F": F, "I": I} +images: dict[str, Any] = {"A": A, "B": B, "F": F, "I": I} def test_sanity() -> None: @@ -30,13 +34,13 @@ def test_sanity() -> None: == "I 3" ) assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], images)) + pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], **images)) == "I 3" ) assert ( pixel( ImageMath.lambda_eval( - lambda args: args["float"](args["A"]) + args["B"], images + lambda args: args["float"](args["A"]) + args["B"], **images ) ) == "F 3.0" @@ -44,42 +48,47 @@ def test_sanity() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["int"](args["float"](args["A"]) + args["B"]), images + lambda args: args["int"](args["float"](args["A"]) + args["B"]), **images ) ) == "I 3" ) +def test_options_deprecated() -> None: + with pytest.warns(DeprecationWarning): + assert ImageMath.lambda_eval(lambda args: 1, images) == 1 + + def test_ops() -> None: - assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, images)) == "I -1" + assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, **images)) == "I -1" assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], images)) + pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], **images)) == "I 3" ) assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] - args["B"], images)) + pixel(ImageMath.lambda_eval(lambda args: args["A"] - args["B"], **images)) == "I -1" ) assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] * args["B"], images)) + pixel(ImageMath.lambda_eval(lambda args: args["A"] * args["B"], **images)) == "I 2" ) assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] / args["B"], images)) + pixel(ImageMath.lambda_eval(lambda args: args["A"] / args["B"], **images)) == "I 0" ) - assert pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 2, images)) == "I 4" + assert pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 2, **images)) == "I 4" assert ( - pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 33, images)) + pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 33, **images)) == "I 2147483647" ) assert ( pixel( ImageMath.lambda_eval( - lambda args: args["float"](args["A"]) + args["B"], images + lambda args: args["float"](args["A"]) + args["B"], **images ) ) == "F 3.0" @@ -87,7 +96,7 @@ def test_ops() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["float"](args["A"]) - args["B"], images + lambda args: args["float"](args["A"]) - args["B"], **images ) ) == "F -1.0" @@ -95,7 +104,7 @@ def test_ops() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["float"](args["A"]) * args["B"], images + lambda args: args["float"](args["A"]) * args["B"], **images ) ) == "F 2.0" @@ -103,31 +112,33 @@ def test_ops() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["float"](args["A"]) / args["B"], images + lambda args: args["float"](args["A"]) / args["B"], **images ) ) == "F 0.5" ) assert ( - pixel(ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 2, images)) + pixel( + ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 2, **images) + ) == "F 4.0" ) assert ( pixel( - ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 33, images) + ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 33, **images) ) == "F 8589934592.0" ) def test_logical() -> None: - assert pixel(ImageMath.lambda_eval(lambda args: not args["A"], images)) == 0 + assert pixel(ImageMath.lambda_eval(lambda args: not args["A"], **images)) == 0 assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] and args["B"], images)) + pixel(ImageMath.lambda_eval(lambda args: args["A"] and args["B"], **images)) == "L 2" ) assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] or args["B"], images)) + pixel(ImageMath.lambda_eval(lambda args: args["A"] or args["B"], **images)) == "L 1" ) @@ -136,7 +147,7 @@ def test_convert() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["convert"](args["A"] + args["B"], "L"), images + lambda args: args["convert"](args["A"] + args["B"], "L"), **images ) ) == "L 3" @@ -144,7 +155,7 @@ def test_convert() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["convert"](args["A"] + args["B"], "1"), images + lambda args: args["convert"](args["A"] + args["B"], "1"), **images ) ) == "1 0" @@ -152,7 +163,7 @@ def test_convert() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["convert"](args["A"] + args["B"], "RGB"), images + lambda args: args["convert"](args["A"] + args["B"], "RGB"), **images ) ) == "RGB (3, 3, 3)" @@ -163,7 +174,7 @@ def test_compare() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["min"](args["A"], args["B"]), images + lambda args: args["min"](args["A"], args["B"]), **images ) ) == "I 1" @@ -171,13 +182,13 @@ def test_compare() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["max"](args["A"], args["B"]), images + lambda args: args["max"](args["A"], args["B"]), **images ) ) == "I 2" ) - assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 1, images)) == "I 1" - assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 2, images)) == "I 0" + assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 1, **images)) == "I 1" + assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 2, **images)) == "I 0" def test_one_image_larger() -> None: diff --git a/Tests/test_imagemath_unsafe_eval.py b/Tests/test_imagemath_unsafe_eval.py index 7b8a562d7..b7ac84691 100644 --- a/Tests/test_imagemath_unsafe_eval.py +++ b/Tests/test_imagemath_unsafe_eval.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Any + import pytest from PIL import Image, ImageMath @@ -21,16 +23,16 @@ I = Image.new("I", (1, 1), 4) # noqa: E741 A2 = A.resize((2, 2)) B2 = B.resize((2, 2)) -images = {"A": A, "B": B, "F": F, "I": I} +images: dict[str, Any] = {"A": A, "B": B, "F": F, "I": I} def test_sanity() -> None: assert ImageMath.unsafe_eval("1") == 1 assert ImageMath.unsafe_eval("1+A", A=2) == 3 assert pixel(ImageMath.unsafe_eval("A+B", A=A, B=B)) == "I 3" - assert pixel(ImageMath.unsafe_eval("A+B", images)) == "I 3" - assert pixel(ImageMath.unsafe_eval("float(A)+B", images)) == "F 3.0" - assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", images)) == "I 3" + assert pixel(ImageMath.unsafe_eval("A+B", **images)) == "I 3" + assert pixel(ImageMath.unsafe_eval("float(A)+B", **images)) == "F 3.0" + assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", **images)) == "I 3" def test_eval_deprecated() -> None: @@ -38,23 +40,28 @@ def test_eval_deprecated() -> None: assert ImageMath.eval("1") == 1 +def test_options_deprecated() -> None: + with pytest.warns(DeprecationWarning): + assert ImageMath.unsafe_eval("1", images) == 1 + + def test_ops() -> None: - assert pixel(ImageMath.unsafe_eval("-A", images)) == "I -1" - assert pixel(ImageMath.unsafe_eval("+B", images)) == "L 2" + assert pixel(ImageMath.unsafe_eval("-A", **images)) == "I -1" + assert pixel(ImageMath.unsafe_eval("+B", **images)) == "L 2" - assert pixel(ImageMath.unsafe_eval("A+B", images)) == "I 3" - assert pixel(ImageMath.unsafe_eval("A-B", images)) == "I -1" - assert pixel(ImageMath.unsafe_eval("A*B", images)) == "I 2" - assert pixel(ImageMath.unsafe_eval("A/B", images)) == "I 0" - assert pixel(ImageMath.unsafe_eval("B**2", images)) == "I 4" - assert pixel(ImageMath.unsafe_eval("B**33", images)) == "I 2147483647" + assert pixel(ImageMath.unsafe_eval("A+B", **images)) == "I 3" + assert pixel(ImageMath.unsafe_eval("A-B", **images)) == "I -1" + assert pixel(ImageMath.unsafe_eval("A*B", **images)) == "I 2" + assert pixel(ImageMath.unsafe_eval("A/B", **images)) == "I 0" + assert pixel(ImageMath.unsafe_eval("B**2", **images)) == "I 4" + assert pixel(ImageMath.unsafe_eval("B**33", **images)) == "I 2147483647" - assert pixel(ImageMath.unsafe_eval("float(A)+B", images)) == "F 3.0" - assert pixel(ImageMath.unsafe_eval("float(A)-B", images)) == "F -1.0" - assert pixel(ImageMath.unsafe_eval("float(A)*B", images)) == "F 2.0" - assert pixel(ImageMath.unsafe_eval("float(A)/B", images)) == "F 0.5" - assert pixel(ImageMath.unsafe_eval("float(B)**2", images)) == "F 4.0" - assert pixel(ImageMath.unsafe_eval("float(B)**33", images)) == "F 8589934592.0" + assert pixel(ImageMath.unsafe_eval("float(A)+B", **images)) == "F 3.0" + assert pixel(ImageMath.unsafe_eval("float(A)-B", **images)) == "F -1.0" + assert pixel(ImageMath.unsafe_eval("float(A)*B", **images)) == "F 2.0" + assert pixel(ImageMath.unsafe_eval("float(A)/B", **images)) == "F 0.5" + assert pixel(ImageMath.unsafe_eval("float(B)**2", **images)) == "F 4.0" + assert pixel(ImageMath.unsafe_eval("float(B)**33", **images)) == "F 8589934592.0" @pytest.mark.parametrize( @@ -72,33 +79,33 @@ def test_prevent_exec(expression: str) -> None: def test_prevent_double_underscores() -> None: with pytest.raises(ValueError): - ImageMath.unsafe_eval("1", {"__": None}) + ImageMath.unsafe_eval("1", __=None) def test_prevent_builtins() -> None: with pytest.raises(ValueError): - ImageMath.unsafe_eval("(lambda: exec('exit()'))()", {"exec": None}) + ImageMath.unsafe_eval("(lambda: exec('exit()'))()", exec=None) def test_logical() -> None: - assert pixel(ImageMath.unsafe_eval("not A", images)) == 0 - assert pixel(ImageMath.unsafe_eval("A and B", images)) == "L 2" - assert pixel(ImageMath.unsafe_eval("A or B", images)) == "L 1" + assert pixel(ImageMath.unsafe_eval("not A", **images)) == 0 + assert pixel(ImageMath.unsafe_eval("A and B", **images)) == "L 2" + assert pixel(ImageMath.unsafe_eval("A or B", **images)) == "L 1" def test_convert() -> None: - assert pixel(ImageMath.unsafe_eval("convert(A+B, 'L')", images)) == "L 3" - assert pixel(ImageMath.unsafe_eval("convert(A+B, '1')", images)) == "1 0" + assert pixel(ImageMath.unsafe_eval("convert(A+B, 'L')", **images)) == "L 3" + assert pixel(ImageMath.unsafe_eval("convert(A+B, '1')", **images)) == "1 0" assert ( - pixel(ImageMath.unsafe_eval("convert(A+B, 'RGB')", images)) == "RGB (3, 3, 3)" + pixel(ImageMath.unsafe_eval("convert(A+B, 'RGB')", **images)) == "RGB (3, 3, 3)" ) def test_compare() -> None: - assert pixel(ImageMath.unsafe_eval("min(A, B)", images)) == "I 1" - assert pixel(ImageMath.unsafe_eval("max(A, B)", images)) == "I 2" - assert pixel(ImageMath.unsafe_eval("A == 1", images)) == "I 1" - assert pixel(ImageMath.unsafe_eval("A == 2", images)) == "I 0" + assert pixel(ImageMath.unsafe_eval("min(A, B)", **images)) == "I 1" + assert pixel(ImageMath.unsafe_eval("max(A, B)", **images)) == "I 2" + assert pixel(ImageMath.unsafe_eval("A == 1", **images)) == "I 1" + assert pixel(ImageMath.unsafe_eval("A == 2", **images)) == "I 0" def test_one_image_larger() -> None: diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 792fd1c70..2f5800e07 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -109,6 +109,15 @@ ImageDraw.getdraw hints parameter The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated. +ImageMath.lambda_eval and ImageMath.unsafe_eval options parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 11.0.0 + +The ``options`` parameter in :py:meth:`~PIL.ImageMath.lambda_eval()` and +:py:meth:`~PIL.ImageMath.unsafe_eval()` has been deprecated. One or more keyword +arguments can be used instead. + Removed features ---------------- diff --git a/docs/reference/ImageMath.rst b/docs/reference/ImageMath.rst index 2535db711..a72ca92d0 100644 --- a/docs/reference/ImageMath.rst +++ b/docs/reference/ImageMath.rst @@ -37,10 +37,10 @@ Example: Using the :py:mod:`~PIL.ImageMath` module :param expression: A function that receives a dictionary. :param options: Values to add to the function's dictionary, mapping image - names to Image instances. You can use one or more keyword - arguments instead of a dictionary, as shown in the above - example. Note that the names must be valid Python - identifiers. + names to Image instances. Deprecated. + You can instead use one or more keyword arguments, as + shown in the above example. Note that the names must be + valid Python identifiers. :return: An image, an integer value, a floating point value, or a pixel tuple, depending on the expression. @@ -62,10 +62,10 @@ Example: Using the :py:mod:`~PIL.ImageMath` module syntax. In addition to the standard operators, you can also use the functions described below. :param options: Values to add to the function's dictionary, mapping image - names to Image instances. You can use one or more keyword - arguments instead of a dictionary, as shown in the above - example. Note that the names must be valid Python - identifiers. + names to Image instances. Deprecated. + You can instead use one or more keyword arguments, as + shown in the above example. Note that the names must be + valid Python identifiers. :return: An image, an integer value, a floating point value, or a pixel tuple, depending on the expression. diff --git a/docs/releasenotes/11.0.0.rst b/docs/releasenotes/11.0.0.rst index 964423ae0..bb6179de8 100644 --- a/docs/releasenotes/11.0.0.rst +++ b/docs/releasenotes/11.0.0.rst @@ -43,10 +43,12 @@ similarly removed. Deprecations ============ -TODO -^^^^ +ImageMath.lambda_eval and ImageMath.unsafe_eval options parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +The ``options`` parameter in :py:meth:`~PIL.ImageMath.lambda_eval()` and +:py:meth:`~PIL.ImageMath.unsafe_eval()` has been deprecated. One or more +keyword arguments can be used instead. API Changes =========== diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index 6664434ea..e79eb3ec9 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -249,14 +249,20 @@ def lambda_eval( :py:func:`~PIL.Image.merge` function. :param expression: A function that receives a dictionary. - :param options: Values to add to the function's dictionary. You - can either use a dictionary, or one or more keyword - arguments. + :param options: Values to add to the function's dictionary. Deprecated. + You can instead use one or more keyword arguments. :return: The expression result. This is usually an image object, but can also be an integer, a floating point value, or a pixel tuple, depending on the expression. """ + if options: + deprecate( + "ImageMath.lambda_eval options", + 12, + "ImageMath.lambda_eval keyword arguments", + ) + args: dict[str, Any] = ops.copy() args.update(options) args.update(kw) @@ -287,14 +293,20 @@ def unsafe_eval( :py:func:`~PIL.Image.merge` function. :param expression: A string containing a Python-style expression. - :param options: Values to add to the evaluation context. You - can either use a dictionary, or one or more keyword - arguments. + :param options: Values to add to the evaluation context. Deprecated. + You can instead use one or more keyword arguments. :return: The evaluated expression. This is usually an image object, but can also be an integer, a floating point value, or a pixel tuple, depending on the expression. """ + if options: + deprecate( + "ImageMath.unsafe_eval options", + 12, + "ImageMath.unsafe_eval keyword arguments", + ) + # build execution namespace args: dict[str, Any] = ops.copy() for k in list(options.keys()) + list(kw.keys()):