1111import sysconfig
1212import tempfile
1313from io import BytesIO
14+ from typing import Sequence , Any , Callable
1415
1516import pytest
1617from packaging .version import parse as parse_version
2829
2930 class test_image_results :
3031 @staticmethod
31- def upload (a , b ) :
32+ def upload (a : Image . Image , b : Image . Image ) -> None :
3233 a .show ()
3334 b .show ()
3435
3536elif "GITHUB_ACTIONS" in os .environ :
3637 HAS_UPLOADER = True
3738
38- class test_image_results :
39+ class test_image_results : # type: ignore[no-redef]
3940 @staticmethod
40- def upload (a , b ) :
41+ def upload (a : Image . Image , b : Image . Image ) -> str :
4142 dir_errors = os .path .join (os .path .dirname (__file__ ), "errors" )
4243 os .makedirs (dir_errors , exist_ok = True )
4344 tmpdir = tempfile .mkdtemp (dir = dir_errors )
@@ -47,14 +48,16 @@ def upload(a, b):
4748
4849else :
4950 try :
50- import test_image_results
51+ import test_image_results # type: ignore[import-untyped, no-redef]
5152
5253 HAS_UPLOADER = True
5354 except ImportError :
5455 pass
5556
5657
57- def convert_to_comparable (a , b ):
58+ def convert_to_comparable (
59+ a : Image .Image , b : Image .Image
60+ ) -> tuple [Image .Image , Image .Image ]:
5861 new_a , new_b = a , b
5962 if a .mode == "P" :
6063 new_a = Image .new ("L" , a .size )
@@ -67,14 +70,16 @@ def convert_to_comparable(a, b):
6770 return new_a , new_b
6871
6972
70- def assert_deep_equal (a , b , msg = None ):
73+ def assert_deep_equal (a : Sequence , b : Sequence , msg : str | None = None ) -> None :
7174 try :
7275 assert len (a ) == len (b ), msg or f"got length { len (a )} , expected { len (b )} "
7376 except Exception :
7477 assert a == b , msg
7578
7679
77- def assert_image (im , mode , size , msg = None ):
80+ def assert_image (
81+ im : Image .Image , mode : str , size : tuple [int , int ], msg : str | None = None
82+ ) -> None :
7883 if mode is not None :
7984 assert im .mode == mode , (
8085 msg or f"got mode { repr (im .mode )} , expected { repr (mode )} "
@@ -86,7 +91,7 @@ def assert_image(im, mode, size, msg=None):
8691 )
8792
8893
89- def assert_image_equal (a , b , msg = None ):
94+ def assert_image_equal (a : Image . Image , b : Image . Image , msg : str | None = None ) -> None :
9095 assert a .mode == b .mode , msg or f"got mode { repr (a .mode )} , expected { repr (b .mode )} "
9196 assert a .size == b .size , msg or f"got size { repr (a .size )} , expected { repr (b .size )} "
9297 if a .tobytes () != b .tobytes ():
@@ -100,14 +105,18 @@ def assert_image_equal(a, b, msg=None):
100105 pytest .fail (msg or "got different content" )
101106
102107
103- def assert_image_equal_tofile (a , filename , msg = None , mode = None ):
108+ def assert_image_equal_tofile (
109+ a : Image .Image , filename : str , msg : str | None = None , mode : str | None = None
110+ ) -> None :
104111 with Image .open (filename ) as img :
105112 if mode :
106113 img = img .convert (mode )
107114 assert_image_equal (a , img , msg )
108115
109116
110- def assert_image_similar (a , b , epsilon , msg = None ):
117+ def assert_image_similar (
118+ a : Image .Image , b : Image .Image , epsilon : float , msg : str | None = None
119+ ) -> None :
111120 assert a .mode == b .mode , msg or f"got mode { repr (a .mode )} , expected { repr (b .mode )} "
112121 assert a .size == b .size , msg or f"got size { repr (a .size )} , expected { repr (b .size )} "
113122
@@ -134,24 +143,32 @@ def assert_image_similar(a, b, epsilon, msg=None):
134143 raise e
135144
136145
137- def assert_image_similar_tofile (a , filename , epsilon , msg = None , mode = None ):
146+ def assert_image_similar_tofile (
147+ a : Image .Image ,
148+ filename : str ,
149+ epsilon : float ,
150+ msg : str | None = None ,
151+ mode : str | None = None ,
152+ ) -> None :
138153 with Image .open (filename ) as img :
139154 if mode :
140155 img = img .convert (mode )
141156 assert_image_similar (a , img , epsilon , msg )
142157
143158
144- def assert_all_same (items , msg = None ):
159+ def assert_all_same (items : Sequence [ Any ] , msg : str | None = None ) -> None :
145160 assert items .count (items [0 ]) == len (items ), msg
146161
147162
148- def assert_not_all_same (items , msg = None ):
163+ def assert_not_all_same (items : Sequence [ Any ] , msg : str | None = None ) -> None :
149164 assert items .count (items [0 ]) != len (items ), msg
150165
151166
152- def assert_tuple_approx_equal (actuals , targets , threshold , msg ):
167+ def assert_tuple_approx_equal (
168+ actuals : tuple [int , ...], targets : tuple [int , ...], threshold : int , msg : str
169+ ) -> None :
153170 """Tests if actuals has values within threshold from targets"""
154- value = True
171+ value = 1
155172 for i , target in enumerate (targets ):
156173 value *= target - threshold <= actuals [i ] <= target + threshold
157174
@@ -163,17 +180,24 @@ def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
163180 return pytest .mark .skipif (not features .check (feature ), reason = reason )
164181
165182
166- def skip_unless_feature_version (feature , version_required , reason = None ):
183+ def skip_unless_feature_version (
184+ feature : str , version_required : str , reason : str | None = None
185+ ) -> pytest .MarkDecorator :
167186 if not features .check (feature ):
168187 return pytest .mark .skip (f"{ feature } not available" )
169188 if reason is None :
170189 reason = f"{ feature } is older than { version_required } "
171- version_required = parse_version (version_required )
190+ version_required_ = parse_version (version_required )
172191 version_available = parse_version (features .version (feature ))
173- return pytest .mark .skipif (version_available < version_required , reason = reason )
192+ return pytest .mark .skipif (version_available < version_required_ , reason = reason )
174193
175194
176- def mark_if_feature_version (mark , feature , version_blacklist , reason = None ):
195+ def mark_if_feature_version (
196+ mark : pytest .MarkDecorator ,
197+ feature : str ,
198+ version_blacklist : str ,
199+ reason : str | None = None ,
200+ ) -> pytest .MarkDecorator :
177201 if not features .check (feature ):
178202 return pytest .mark .pil_noop_mark ()
179203 if reason is None :
@@ -194,7 +218,7 @@ class PillowLeakTestCase:
194218 iterations = 100 # count
195219 mem_limit = 512 # k
196220
197- def _get_mem_usage (self ):
221+ def _get_mem_usage (self ) -> float :
198222 """
199223 Gets the RUSAGE memory usage, returns in K. Encapsulates the difference
200224 between macOS and Linux rss reporting
@@ -216,7 +240,7 @@ def _get_mem_usage(self):
216240 # This is the maximum resident set size used (in kilobytes).
217241 return mem # Kb
218242
219- def _test_leak (self , core ) :
243+ def _test_leak (self , core : Callable [[], None ]) -> None :
220244 start_mem = self ._get_mem_usage ()
221245 for cycle in range (self .iterations ):
222246 core ()
@@ -228,17 +252,22 @@ def _test_leak(self, core):
228252# helpers
229253
230254
231- def fromstring (data ) :
255+ def fromstring (data : bytes ) -> Image . Image :
232256 return Image .open (BytesIO (data ))
233257
234258
235- def tostring (im , string_format , ** options ) :
259+ def tostring (im : Image . Image , string_format : str , ** options : Any ) -> bytes :
236260 out = BytesIO ()
237261 im .save (out , string_format , ** options )
238262 return out .getvalue ()
239263
240264
241- def hopper (mode = None , cache = {}):
265+ def hopper (
266+ mode : str | None = None , cache : dict [str , Image .Image ] | None = None
267+ ) -> Image .Image :
268+ if cache is None :
269+ cache = {}
270+
242271 if mode is None :
243272 # Always return fresh not-yet-loaded version of image.
244273 # Operations on not-yet-loaded images is separate class of errors
@@ -259,29 +288,31 @@ def hopper(mode=None, cache={}):
259288 return im .copy ()
260289
261290
262- def djpeg_available ():
291+ def djpeg_available () -> bool :
263292 if shutil .which ("djpeg" ):
264293 try :
265294 subprocess .check_call (["djpeg" , "-version" ])
266295 return True
267296 except subprocess .CalledProcessError : # pragma: no cover
268297 return False
298+ return False
269299
270300
271- def cjpeg_available ():
301+ def cjpeg_available () -> bool :
272302 if shutil .which ("cjpeg" ):
273303 try :
274304 subprocess .check_call (["cjpeg" , "-version" ])
275305 return True
276306 except subprocess .CalledProcessError : # pragma: no cover
277307 return False
308+ return False
278309
279310
280- def netpbm_available ():
311+ def netpbm_available () -> bool :
281312 return bool (shutil .which ("ppmquant" ) and shutil .which ("ppmtogif" ))
282313
283314
284- def magick_command ():
315+ def magick_command () -> list [ str ] | None :
285316 if sys .platform == "win32" :
286317 magickhome = os .environ .get ("MAGICK_HOME" )
287318 if magickhome :
@@ -299,46 +330,48 @@ def magick_command():
299330 if graphicsmagick and shutil .which (graphicsmagick [0 ]):
300331 return graphicsmagick
301332
333+ return None
334+
302335
303- def on_appveyor ():
336+ def on_appveyor () -> bool :
304337 return "APPVEYOR" in os .environ
305338
306339
307- def on_github_actions ():
340+ def on_github_actions () -> bool :
308341 return "GITHUB_ACTIONS" in os .environ
309342
310343
311- def on_ci ():
344+ def on_ci () -> bool :
312345 # GitHub Actions and AppVeyor have "CI"
313346 return "CI" in os .environ
314347
315348
316- def is_big_endian ():
349+ def is_big_endian () -> bool :
317350 return sys .byteorder == "big"
318351
319352
320- def is_ppc64le ():
353+ def is_ppc64le () -> bool :
321354 import platform
322355
323356 return platform .machine () == "ppc64le"
324357
325358
326- def is_win32 ():
359+ def is_win32 () -> bool :
327360 return sys .platform .startswith ("win32" )
328361
329362
330- def is_pypy ():
363+ def is_pypy () -> bool :
331364 return hasattr (sys , "pypy_translation_info" )
332365
333366
334- def is_mingw ():
367+ def is_mingw () -> bool :
335368 return sysconfig .get_platform () == "mingw"
336369
337370
338371class CachedProperty :
339- def __init__ (self , func ) :
372+ def __init__ (self , func : Callable [[ Any ], Any ]) -> None :
340373 self .func = func
341374
342- def __get__ (self , instance , cls = None ):
375+ def __get__ (self , instance : dict [ Any , Callable [[], Any ]], cls : Any = None ) -> Any :
343376 result = instance .__dict__ [self .func .__name__ ] = self .func (instance )
344377 return result
0 commit comments