Skip to content

Commit 89bd836

Browse files
committed
lowlevel: add attribute indicating whether a function is available
Add `available` attribute on lowlevel functions to indicate whether OpenSlide supports the function, without the need to call it. Update tests to use this; drop maybe_supported decorator. Signed-off-by: Benjamin Gilbert <[email protected]>
1 parent 9e37358 commit 89bd836

File tree

5 files changed

+44
-27
lines changed

5 files changed

+44
-27
lines changed

openslide/lowlevel.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,14 +263,28 @@ def _func(name, restype, argtypes, errcheck=_check_error, minimum_version=None):
263263
def function_unavailable(*_args):
264264
raise OpenSlideVersionError(minimum_version)
265265

266+
# allow checking for availability without calling the function
267+
function_unavailable.available = False
268+
266269
return function_unavailable
267270
func.argtypes = argtypes
268271
func.restype = restype
269272
if errcheck is not None:
270273
func.errcheck = errcheck
274+
func.available = True
271275
return func
272276

273277

278+
def _wraps_funcs(wrapped):
279+
def decorator(f):
280+
f.available = True
281+
for w in wrapped:
282+
f.available = f.available and w.available
283+
return f
284+
285+
return decorator
286+
287+
274288
try:
275289
detect_vendor = _func('openslide_detect_vendor', c_char_p, [_utf8_p], _check_string)
276290
except AttributeError:
@@ -289,6 +303,7 @@ def function_unavailable(*_args):
289303
)
290304

291305

306+
@_wraps_funcs([_get_level_dimensions])
292307
def get_level_dimensions(slide, level):
293308
w, h = c_int64(), c_int64()
294309
_get_level_dimensions(slide, level, byref(w), byref(h))
@@ -310,6 +325,7 @@ def get_level_dimensions(slide, level):
310325
)
311326

312327

328+
@_wraps_funcs([_read_region])
313329
def read_region(slide, x, y, level, w, h):
314330
if w < 0 or h < 0:
315331
# OpenSlide would catch this, but not before we tried to allocate
@@ -349,6 +365,7 @@ def read_region(slide, x, y, level, w, h):
349365
)
350366

351367

368+
@_wraps_funcs([_get_associated_image_dimensions])
352369
def get_associated_image_dimensions(slide, name):
353370
w, h = c_int64(), c_int64()
354371
_get_associated_image_dimensions(slide, name, byref(w), byref(h))
@@ -360,6 +377,7 @@ def get_associated_image_dimensions(slide, name):
360377
)
361378

362379

380+
@_wraps_funcs([get_associated_image_dimensions, _read_associated_image])
363381
def read_associated_image(slide, name):
364382
w, h = get_associated_image_dimensions(slide, name)
365383
buf = (w * h * c_uint32)()

tests/common.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1818
#
1919

20-
from functools import wraps
2120
import os
2221
from pathlib import Path
2322

@@ -41,22 +40,6 @@
4140

4241
os.environ['PATH'] = _orig_path
4342

44-
from openslide import OpenSlideVersionError
45-
4643

4744
def file_path(name):
4845
return Path(__file__).parent / 'fixtures' / name
49-
50-
51-
def maybe_supported(f):
52-
'''Decorator to ignore test failures caused by an OpenSlide version that
53-
doesn't support the tested functionality.'''
54-
55-
@wraps(f)
56-
def wrapper(*args, **kwargs):
57-
try:
58-
return f(*args, **kwargs)
59-
except OpenSlideVersionError:
60-
pass
61-
62-
return wrapper

tests/test_base.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#
22
# openslide-python - Python bindings for the OpenSlide library
33
#
4-
# Copyright (c) 2016 Benjamin Gilbert
4+
# Copyright (c) 2016-2023 Benjamin Gilbert
55
#
66
# This library is free software; you can redistribute it and/or modify it
77
# under the terms of version 2.1 of the GNU Lesser General Public License
@@ -17,11 +17,12 @@
1717
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
1818
#
1919

20+
import ctypes
2021
import unittest
2122

2223
from common import file_path
2324

24-
from openslide import ImageSlide, OpenSlide, open_slide
25+
from openslide import ImageSlide, OpenSlide, lowlevel, open_slide
2526

2627

2728
class TestLibrary(unittest.TestCase):
@@ -30,3 +31,17 @@ def test_open_slide(self):
3031
self.assertTrue(isinstance(osr, OpenSlide))
3132
with open_slide(file_path('boxes.png')) as osr:
3233
self.assertTrue(isinstance(osr, ImageSlide))
34+
35+
def test_lowlevel_available(self):
36+
'''Ensure all exported functions have an 'available' attribute.'''
37+
for name in dir(lowlevel):
38+
# ignore classes and unexported functions
39+
if name.startswith('_') or name[0].isupper():
40+
continue
41+
# ignore random imports
42+
if hasattr(ctypes, name) or name in ('count', 'platform'):
43+
continue
44+
self.assertTrue(
45+
hasattr(getattr(lowlevel, name), 'available'),
46+
f'"{name}" missing "available" attribute',
47+
)

tests/test_imageslide.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#
22
# openslide-python - Python bindings for the OpenSlide library
33
#
4-
# Copyright (c) 2016-2021 Benjamin Gilbert
4+
# Copyright (c) 2016-2023 Benjamin Gilbert
55
#
66
# This library is free software; you can redistribute it and/or modify it
77
# under the terms of version 2.1 of the GNU Lesser General Public License
@@ -20,9 +20,9 @@
2020
import unittest
2121

2222
from PIL import Image
23-
from common import file_path, maybe_supported
23+
from common import file_path
2424

25-
from openslide import ImageSlide, OpenSlideCache, OpenSlideError
25+
from openslide import ImageSlide, OpenSlideCache, OpenSlideError, lowlevel
2626

2727

2828
class TestImageWithoutOpening(unittest.TestCase):
@@ -105,7 +105,7 @@ def test_read_region_bad_size(self):
105105
def test_thumbnail(self):
106106
self.assertEqual(self.osr.get_thumbnail((100, 100)).size, (100, 83))
107107

108-
@maybe_supported
108+
@unittest.skipUnless(lowlevel.cache_create.available, "requires OpenSlide 4.0.0")
109109
def test_set_cache(self):
110110
self.osr.set_cache(OpenSlideCache(64 << 10))
111111
self.assertEqual(self.osr.read_region((0, 0), 0, (400, 400)).size, (400, 400))

tests/test_openslide.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#
22
# openslide-python - Python bindings for the OpenSlide library
33
#
4-
# Copyright (c) 2016-2021 Benjamin Gilbert
4+
# Copyright (c) 2016-2023 Benjamin Gilbert
55
#
66
# This library is free software; you can redistribute it and/or modify it
77
# under the terms of version 2.1 of the GNU Lesser General Public License
@@ -23,18 +23,19 @@
2323
import unittest
2424

2525
from PIL import Image
26-
from common import file_path, maybe_supported
26+
from common import file_path
2727

2828
from openslide import (
2929
OpenSlide,
3030
OpenSlideCache,
3131
OpenSlideError,
3232
OpenSlideUnsupportedFormatError,
33+
lowlevel,
3334
)
3435

3536

3637
class TestCache(unittest.TestCase):
37-
@maybe_supported
38+
@unittest.skipUnless(lowlevel.cache_create.available, "requires OpenSlide 4.0.0")
3839
def test_create_cache(self):
3940
OpenSlideCache(0)
4041
OpenSlideCache(1)
@@ -155,7 +156,7 @@ def _test_read_region_2GB(self):
155156
def test_thumbnail(self):
156157
self.assertEqual(self.osr.get_thumbnail((100, 100)).size, (100, 83))
157158

158-
@maybe_supported
159+
@unittest.skipUnless(lowlevel.cache_create.available, "requires OpenSlide 4.0.0")
159160
def test_set_cache(self):
160161
self.osr.set_cache(OpenSlideCache(64 << 10))
161162
self.assertEqual(self.osr.read_region((0, 0), 0, (400, 400)).size, (400, 400))

0 commit comments

Comments
 (0)