mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-11-10 19:56:47 +03:00
Merge pull request #8153 from radarhere/type_hint
This commit is contained in:
commit
920698eea7
|
@ -18,7 +18,7 @@ from typing import Any, Callable, Sequence
|
|||
import pytest
|
||||
from packaging.version import parse as parse_version
|
||||
|
||||
from PIL import Image, ImageMath, features
|
||||
from PIL import Image, ImageFile, ImageMath, features
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -240,7 +240,7 @@ class PillowLeakTestCase:
|
|||
# helpers
|
||||
|
||||
|
||||
def fromstring(data: bytes) -> Image.Image:
|
||||
def fromstring(data: bytes) -> ImageFile.ImageFile:
|
||||
return Image.open(BytesIO(data))
|
||||
|
||||
|
||||
|
|
|
@ -1033,8 +1033,10 @@ class TestFileJpeg:
|
|||
|
||||
def test_repr_jpeg(self) -> None:
|
||||
im = hopper()
|
||||
b = im._repr_jpeg_()
|
||||
assert b is not None
|
||||
|
||||
with Image.open(BytesIO(im._repr_jpeg_())) as repr_jpeg:
|
||||
with Image.open(BytesIO(b)) as repr_jpeg:
|
||||
assert repr_jpeg.format == "JPEG"
|
||||
assert_image_similar(im, repr_jpeg, 17)
|
||||
|
||||
|
|
|
@ -535,8 +535,10 @@ class TestFilePng:
|
|||
|
||||
def test_repr_png(self) -> None:
|
||||
im = hopper()
|
||||
b = im._repr_png_()
|
||||
assert b is not None
|
||||
|
||||
with Image.open(BytesIO(im._repr_png_())) as repr_png:
|
||||
with Image.open(BytesIO(b)) as repr_png:
|
||||
assert repr_png.format == "PNG"
|
||||
assert_image_equal(im, repr_png)
|
||||
|
||||
|
@ -768,14 +770,10 @@ class TestFilePng:
|
|||
def test_save_stdout(self, buffer: bool) -> None:
|
||||
old_stdout = sys.stdout
|
||||
|
||||
if buffer:
|
||||
|
||||
class MyStdOut:
|
||||
buffer = BytesIO()
|
||||
|
||||
mystdout = MyStdOut()
|
||||
else:
|
||||
mystdout = BytesIO()
|
||||
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
||||
|
||||
sys.stdout = mystdout
|
||||
|
||||
|
@ -785,7 +783,7 @@ class TestFilePng:
|
|||
# Reset stdout
|
||||
sys.stdout = old_stdout
|
||||
|
||||
if buffer:
|
||||
if isinstance(mystdout, MyStdOut):
|
||||
mystdout = mystdout.buffer
|
||||
with Image.open(mystdout) as reloaded:
|
||||
assert_image_equal_tofile(reloaded, TEST_PNG_FILE)
|
||||
|
|
|
@ -368,14 +368,10 @@ def test_mimetypes(tmp_path: Path) -> None:
|
|||
def test_save_stdout(buffer: bool) -> None:
|
||||
old_stdout = sys.stdout
|
||||
|
||||
if buffer:
|
||||
|
||||
class MyStdOut:
|
||||
buffer = BytesIO()
|
||||
|
||||
mystdout = MyStdOut()
|
||||
else:
|
||||
mystdout = BytesIO()
|
||||
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
||||
|
||||
sys.stdout = mystdout
|
||||
|
||||
|
@ -385,7 +381,7 @@ def test_save_stdout(buffer: bool) -> None:
|
|||
# Reset stdout
|
||||
sys.stdout = old_stdout
|
||||
|
||||
if buffer:
|
||||
if isinstance(mystdout, MyStdOut):
|
||||
mystdout = mystdout.buffer
|
||||
with Image.open(mystdout) as reloaded:
|
||||
assert_image_equal_tofile(reloaded, TEST_FILE)
|
||||
|
|
|
@ -120,7 +120,7 @@ class TestFileTiff:
|
|||
def test_set_legacy_api(self) -> None:
|
||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
with pytest.raises(Exception) as e:
|
||||
ifd.legacy_api = None
|
||||
ifd.legacy_api = False
|
||||
assert str(e.value) == "Not allowing setting of legacy api"
|
||||
|
||||
def test_xyres_tiff(self) -> None:
|
||||
|
|
|
@ -34,7 +34,7 @@ class TestDefaultFontLeak(TestTTypeFontLeak):
|
|||
|
||||
def test_leak(self) -> None:
|
||||
if features.check_module("freetype2"):
|
||||
ImageFont.core = _util.DeferredError(ImportError)
|
||||
ImageFont.core = _util.DeferredError(ImportError("Disabled for testing"))
|
||||
try:
|
||||
default_font = ImageFont.load_default()
|
||||
finally:
|
||||
|
|
|
@ -393,13 +393,13 @@ class TestImage:
|
|||
|
||||
# errors
|
||||
with pytest.raises(ValueError):
|
||||
source.alpha_composite(over, "invalid source")
|
||||
source.alpha_composite(over, "invalid destination") # type: ignore[arg-type]
|
||||
with pytest.raises(ValueError):
|
||||
source.alpha_composite(over, (0, 0), "invalid destination")
|
||||
source.alpha_composite(over, (0, 0), "invalid source") # type: ignore[arg-type]
|
||||
with pytest.raises(ValueError):
|
||||
source.alpha_composite(over, 0)
|
||||
source.alpha_composite(over, 0) # type: ignore[arg-type]
|
||||
with pytest.raises(ValueError):
|
||||
source.alpha_composite(over, (0, 0), 0)
|
||||
source.alpha_composite(over, (0, 0), 0) # type: ignore[arg-type]
|
||||
with pytest.raises(ValueError):
|
||||
source.alpha_composite(over, (0, 0), (0, -1))
|
||||
|
||||
|
|
|
@ -16,7 +16,9 @@ def draft_roundtrip(
|
|||
im = Image.new(in_mode, in_size)
|
||||
data = tostring(im, "JPEG")
|
||||
im = fromstring(data)
|
||||
mode, box = im.draft(req_mode, req_size)
|
||||
result = im.draft(req_mode, req_size)
|
||||
assert result is not None
|
||||
box = result[1]
|
||||
scale, _ = im.decoderconfig
|
||||
assert box[:2] == (0, 0)
|
||||
assert (im.width - scale) < box[2] <= im.width
|
||||
|
|
|
@ -338,3 +338,8 @@ class TestImagingPaste:
|
|||
|
||||
im.copy().paste(im2)
|
||||
im.copy().paste(im2, (0, 0))
|
||||
|
||||
def test_incorrect_abbreviated_form(self) -> None:
|
||||
im = Image.new("L", (1, 1))
|
||||
with pytest.raises(ValueError):
|
||||
im.paste(im, im, im)
|
||||
|
|
|
@ -61,4 +61,4 @@ def test_f_lut() -> None:
|
|||
def test_f_mode() -> None:
|
||||
im = hopper("F")
|
||||
with pytest.raises(ValueError):
|
||||
im.point(None)
|
||||
im.point([])
|
||||
|
|
|
@ -113,13 +113,13 @@ def test_array_F() -> None:
|
|||
def test_not_flattened() -> None:
|
||||
im = Image.new("L", (1, 1))
|
||||
with pytest.raises(TypeError):
|
||||
im.putdata([[0]]) # type: ignore[list-item]
|
||||
im.putdata([[0]])
|
||||
with pytest.raises(TypeError):
|
||||
im.putdata([[0]], 2) # type: ignore[list-item]
|
||||
im.putdata([[0]], 2)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
im = Image.new("I", (1, 1))
|
||||
im.putdata([[0]]) # type: ignore[list-item]
|
||||
im.putdata([[0]])
|
||||
with pytest.raises(TypeError):
|
||||
im = Image.new("F", (1, 1))
|
||||
im.putdata([[0]]) # type: ignore[list-item]
|
||||
im.putdata([[0]])
|
||||
|
|
|
@ -445,7 +445,7 @@ class TestCoreResampleBox:
|
|||
im.resize((32, 32), resample, (20, 20, 100, 20))
|
||||
|
||||
with pytest.raises(TypeError, match="must be sequence of length 4"):
|
||||
im.resize((32, 32), resample, (im.width, im.height))
|
||||
im.resize((32, 32), resample, (im.width, im.height)) # type: ignore[arg-type]
|
||||
|
||||
with pytest.raises(ValueError, match="can't be negative"):
|
||||
im.resize((32, 32), resample, (-20, 20, 100, 100))
|
||||
|
|
|
@ -103,7 +103,7 @@ def test_sanity() -> None:
|
|||
|
||||
|
||||
def test_flags() -> None:
|
||||
assert ImageCms.Flags.NONE == 0
|
||||
assert ImageCms.Flags.NONE.value == 0
|
||||
assert ImageCms.Flags.GRIDPOINTS(0) == ImageCms.Flags.NONE
|
||||
assert ImageCms.Flags.GRIDPOINTS(256) == ImageCms.Flags.NONE
|
||||
|
||||
|
@ -569,9 +569,9 @@ def assert_aux_channel_preserved(
|
|||
for delta in nine_grid_deltas:
|
||||
channel_data.paste(
|
||||
channel_pattern,
|
||||
tuple(
|
||||
paste_offset[c] + delta[c] * channel_pattern.size[c]
|
||||
for c in range(2)
|
||||
(
|
||||
paste_offset[0] + delta[0] * channel_pattern.size[0],
|
||||
paste_offset[1] + delta[1] * channel_pattern.size[1],
|
||||
),
|
||||
)
|
||||
chans.append(channel_data)
|
||||
|
@ -642,7 +642,8 @@ def test_auxiliary_channels_isolated() -> None:
|
|||
# convert with and without AUX data, test colors are equal
|
||||
src_colorSpace = cast(Literal["LAB", "XYZ", "sRGB"], src_format[1])
|
||||
source_profile = ImageCms.createProfile(src_colorSpace)
|
||||
destination_profile = ImageCms.createProfile(dst_format[1])
|
||||
dst_colorSpace = cast(Literal["LAB", "XYZ", "sRGB"], dst_format[1])
|
||||
destination_profile = ImageCms.createProfile(dst_colorSpace)
|
||||
source_image = src_format[3]
|
||||
test_transform = ImageCms.buildTransform(
|
||||
source_profile,
|
||||
|
|
|
@ -1581,7 +1581,7 @@ def test_compute_regular_polygon_vertices_input_error_handling(
|
|||
error_message: str,
|
||||
) -> None:
|
||||
with pytest.raises(expected_error) as e:
|
||||
ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)
|
||||
ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, rotation) # type: ignore[arg-type]
|
||||
assert str(e.value) == error_message
|
||||
|
||||
|
||||
|
|
|
@ -51,9 +51,10 @@ def test_sanity() -> None:
|
|||
pen = ImageDraw2.Pen("blue", width=7)
|
||||
draw.line(list(range(10)), pen)
|
||||
|
||||
draw, handler = ImageDraw.getdraw(im)
|
||||
draw2, handler = ImageDraw.getdraw(im)
|
||||
assert draw2 is not None
|
||||
pen = ImageDraw2.Pen("blue", width=7)
|
||||
draw.line(list(range(10)), pen)
|
||||
draw2.line(list(range(10)), pen)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bbox", BBOX)
|
||||
|
|
|
@ -381,7 +381,7 @@ class TestPyEncoder(CodecsTest):
|
|||
def test_encode(self) -> None:
|
||||
encoder = ImageFile.PyEncoder(None)
|
||||
with pytest.raises(NotImplementedError):
|
||||
encoder.encode(None)
|
||||
encoder.encode(0)
|
||||
|
||||
bytes_consumed, errcode = encoder.encode_to_pyfd()
|
||||
assert bytes_consumed == 0
|
||||
|
|
|
@ -209,7 +209,7 @@ def test_getlength(
|
|||
assert length == length_raqm
|
||||
|
||||
|
||||
def test_float_size() -> None:
|
||||
def test_float_size(layout_engine: ImageFont.Layout) -> None:
|
||||
lengths = []
|
||||
for size in (48, 48.5, 49):
|
||||
f = ImageFont.truetype(
|
||||
|
@ -494,8 +494,8 @@ def test_default_font() -> None:
|
|||
assert_image_equal_tofile(im, "Tests/images/default_font_freetype.png")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", (None, "1", "RGBA"))
|
||||
def test_getbbox(font: ImageFont.FreeTypeFont, mode: str | None) -> None:
|
||||
@pytest.mark.parametrize("mode", ("", "1", "RGBA"))
|
||||
def test_getbbox(font: ImageFont.FreeTypeFont, mode: str) -> None:
|
||||
assert (0, 4, 12, 16) == font.getbbox("A", mode)
|
||||
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ original_core = ImageFont.core
|
|||
|
||||
def setup_module() -> None:
|
||||
if features.check_module("freetype2"):
|
||||
ImageFont.core = _util.DeferredError(ImportError)
|
||||
ImageFont.core = _util.DeferredError(ImportError("Disabled for testing"))
|
||||
|
||||
|
||||
def teardown_module() -> None:
|
||||
|
@ -76,3 +76,8 @@ def test_oom() -> None:
|
|||
font = ImageFont.ImageFont()
|
||||
font._load_pilfont_data(fp, Image.new("L", (1, 1)))
|
||||
font.getmask("A" * 1_000_000)
|
||||
|
||||
|
||||
def test_freetypefont_without_freetype() -> None:
|
||||
with pytest.raises(ImportError):
|
||||
ImageFont.truetype("Tests/fonts/FreeMono.ttf")
|
||||
|
|
|
@ -45,7 +45,7 @@ def test_getcolor() -> None:
|
|||
|
||||
# Test unknown color specifier
|
||||
with pytest.raises(ValueError):
|
||||
palette.getcolor("unknown")
|
||||
palette.getcolor("unknown") # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_getcolor_rgba_color_rgb_palette() -> None:
|
||||
|
|
|
@ -54,14 +54,10 @@ def test_stdout(buffer: bool) -> None:
|
|||
# Temporarily redirect stdout
|
||||
old_stdout = sys.stdout
|
||||
|
||||
if buffer:
|
||||
|
||||
class MyStdOut:
|
||||
buffer = BytesIO()
|
||||
|
||||
mystdout = MyStdOut()
|
||||
else:
|
||||
mystdout = BytesIO()
|
||||
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
||||
|
||||
sys.stdout = mystdout
|
||||
|
||||
|
@ -71,6 +67,6 @@ def test_stdout(buffer: bool) -> None:
|
|||
# Reset stdout
|
||||
sys.stdout = old_stdout
|
||||
|
||||
if buffer:
|
||||
if isinstance(mystdout, MyStdOut):
|
||||
mystdout = mystdout.buffer
|
||||
assert mystdout.getvalue() != b""
|
||||
|
|
|
@ -1168,7 +1168,7 @@ class Image:
|
|||
def quantize(
|
||||
self,
|
||||
colors: int = 256,
|
||||
method: Quantize | None = None,
|
||||
method: int | None = None,
|
||||
kmeans: int = 0,
|
||||
palette=None,
|
||||
dither: Dither = Dither.FLOYDSTEINBERG,
|
||||
|
@ -1309,7 +1309,7 @@ class Image:
|
|||
return im.crop((x0, y0, x1, y1))
|
||||
|
||||
def draft(
|
||||
self, mode: str | None, size: tuple[int, int]
|
||||
self, mode: str | None, size: tuple[int, int] | None
|
||||
) -> tuple[str, tuple[int, int, float, float]] | None:
|
||||
"""
|
||||
Configures the image file loader so it returns a version of the
|
||||
|
@ -1744,7 +1744,7 @@ class Image:
|
|||
def paste(
|
||||
self,
|
||||
im: Image | str | float | tuple[float, ...],
|
||||
box: tuple[int, int, int, int] | tuple[int, int] | None = None,
|
||||
box: Image | tuple[int, int, int, int] | tuple[int, int] | None = None,
|
||||
mask: Image | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
|
@ -1786,10 +1786,14 @@ class Image:
|
|||
:param mask: An optional mask image.
|
||||
"""
|
||||
|
||||
if isImageType(box) and mask is None:
|
||||
if isImageType(box):
|
||||
if mask is not None:
|
||||
msg = "If using second argument as mask, third argument must be None"
|
||||
raise ValueError(msg)
|
||||
# abbreviated paste(im, mask) syntax
|
||||
mask = box
|
||||
box = None
|
||||
assert not isinstance(box, Image)
|
||||
|
||||
if box is None:
|
||||
box = (0, 0)
|
||||
|
@ -1995,7 +1999,10 @@ class Image:
|
|||
self.im.putband(alpha.im, band)
|
||||
|
||||
def putdata(
|
||||
self, data: Sequence[float], scale: float = 1.0, offset: float = 0.0
|
||||
self,
|
||||
data: Sequence[float] | Sequence[Sequence[int]],
|
||||
scale: float = 1.0,
|
||||
offset: float = 0.0,
|
||||
) -> None:
|
||||
"""
|
||||
Copies pixel data from a flattened sequence object into the image. The
|
||||
|
@ -2656,7 +2663,7 @@ class Image:
|
|||
self,
|
||||
size: tuple[float, float],
|
||||
resample: Resampling = Resampling.BICUBIC,
|
||||
reducing_gap: float = 2.0,
|
||||
reducing_gap: float | None = 2.0,
|
||||
) -> None:
|
||||
"""
|
||||
Make this image into a thumbnail. This method modifies the
|
||||
|
@ -2717,11 +2724,12 @@ class Image:
|
|||
return x, y
|
||||
|
||||
box = None
|
||||
final_size: tuple[int, int]
|
||||
if reducing_gap is not None:
|
||||
preserved_size = preserve_aspect_ratio()
|
||||
if preserved_size is None:
|
||||
return
|
||||
size = preserved_size
|
||||
final_size = preserved_size
|
||||
|
||||
res = self.draft(
|
||||
None, (int(size[0] * reducing_gap), int(size[1] * reducing_gap))
|
||||
|
@ -2735,13 +2743,13 @@ class Image:
|
|||
preserved_size = preserve_aspect_ratio()
|
||||
if preserved_size is None:
|
||||
return
|
||||
size = preserved_size
|
||||
final_size = preserved_size
|
||||
|
||||
if self.size != size:
|
||||
im = self.resize(size, resample, box=box, reducing_gap=reducing_gap)
|
||||
if self.size != final_size:
|
||||
im = self.resize(final_size, resample, box=box, reducing_gap=reducing_gap)
|
||||
|
||||
self.im = im.im
|
||||
self._size = size
|
||||
self._size = final_size
|
||||
self._mode = self.im.mode
|
||||
|
||||
self.readonly = 0
|
||||
|
|
|
@ -62,7 +62,9 @@ directly.
|
|||
|
||||
|
||||
class ImageDraw:
|
||||
font = None
|
||||
font: (
|
||||
ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont | None
|
||||
) = None
|
||||
|
||||
def __init__(self, im: Image.Image, mode: str | None = None) -> None:
|
||||
"""
|
||||
|
|
|
@ -33,11 +33,12 @@ import sys
|
|||
import warnings
|
||||
from enum import IntEnum
|
||||
from io import BytesIO
|
||||
from types import ModuleType
|
||||
from typing import IO, TYPE_CHECKING, Any, BinaryIO
|
||||
|
||||
from . import Image
|
||||
from ._typing import StrOrBytesPath
|
||||
from ._util import is_path
|
||||
from ._util import DeferredError, is_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ImageFile
|
||||
|
@ -53,11 +54,10 @@ class Layout(IntEnum):
|
|||
MAX_STRING_LENGTH = 1_000_000
|
||||
|
||||
|
||||
core: ModuleType | DeferredError
|
||||
try:
|
||||
from . import _imagingft as core
|
||||
except ImportError as ex:
|
||||
from ._util import DeferredError
|
||||
|
||||
core = DeferredError.new(ex)
|
||||
|
||||
|
||||
|
@ -199,6 +199,7 @@ class FreeTypeFont:
|
|||
"""FreeType font wrapper (requires _imagingft service)"""
|
||||
|
||||
font: Font
|
||||
font_bytes: bytes
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -210,6 +211,9 @@ class FreeTypeFont:
|
|||
) -> None:
|
||||
# FIXME: use service provider instead
|
||||
|
||||
if isinstance(core, DeferredError):
|
||||
raise core.ex
|
||||
|
||||
if size <= 0:
|
||||
msg = "font size must be greater than 0"
|
||||
raise ValueError(msg)
|
||||
|
@ -917,7 +921,7 @@ def load_default(size: float | None = None) -> FreeTypeFont | ImageFont:
|
|||
:return: A font object.
|
||||
"""
|
||||
f: FreeTypeFont | ImageFont
|
||||
if core.__class__.__name__ == "module" or size is not None:
|
||||
if isinstance(core, ModuleType) or size is not None:
|
||||
f = truetype(
|
||||
BytesIO(
|
||||
base64.b64decode(
|
||||
|
|
|
@ -196,7 +196,7 @@ if qt_is_installed:
|
|||
self.setColorTable(im_data["colortable"])
|
||||
|
||||
|
||||
def toqimage(im):
|
||||
def toqimage(im) -> ImageQt:
|
||||
return ImageQt(im)
|
||||
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ class Transform(Image.ImageTransformHandler):
|
|||
|
||||
method: Image.Transform
|
||||
|
||||
def __init__(self, data: Sequence[int]) -> None:
|
||||
def __init__(self, data: Sequence[Any]) -> None:
|
||||
self.data = data
|
||||
|
||||
def getdata(self) -> tuple[Image.Transform, Sequence[int]]:
|
||||
|
|
|
@ -428,7 +428,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
return s
|
||||
|
||||
def draft(
|
||||
self, mode: str | None, size: tuple[int, int]
|
||||
self, mode: str | None, size: tuple[int, int] | None
|
||||
) -> tuple[str, tuple[int, int, float, float]] | None:
|
||||
if len(self.tile) != 1:
|
||||
return None
|
||||
|
|
|
@ -89,7 +89,7 @@ DOUBLE = 12
|
|||
IFD = 13
|
||||
LONG8 = 16
|
||||
|
||||
TAGS_V2 = {
|
||||
_tags_v2 = {
|
||||
254: ("NewSubfileType", LONG, 1),
|
||||
255: ("SubfileType", SHORT, 1),
|
||||
256: ("ImageWidth", LONG, 1),
|
||||
|
@ -425,9 +425,11 @@ TAGS = {
|
|||
50784: "Alias Layer Metadata",
|
||||
}
|
||||
|
||||
TAGS_V2: dict[int, TagInfo] = {}
|
||||
|
||||
|
||||
def _populate():
|
||||
for k, v in TAGS_V2.items():
|
||||
for k, v in _tags_v2.items():
|
||||
# Populate legacy structure.
|
||||
TAGS[k] = v[0]
|
||||
if len(v) == 4:
|
||||
|
|
Loading…
Reference in New Issue
Block a user