diff --git a/regions/_utils/optional_deps.py b/regions/_utils/optional_deps.py index 22438364..d79c8a02 100644 --- a/regions/_utils/optional_deps.py +++ b/regions/_utils/optional_deps.py @@ -1,13 +1,25 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst +""" +Checks for optional dependencies using lazy import from `PEP 562 +`_. +""" -try: - import matplotlib - HAS_MATPLOTLIB = True - MPL_VERSION = getattr(matplotlib, '__version__', None) - if MPL_VERSION is None: - MPL_VERSION = matplotlib._version.version - MPL_VERSION = MPL_VERSION.split('.') - MPL_VERSION = 10 * int(MPL_VERSION[0]) + int(MPL_VERSION[1]) -except ImportError: - HAS_MATPLOTLIB = False - MPL_VERSION = 0 +import importlib + +# This list is a duplicate of the dependencies in pyproject.toml "all". +# Note that in some cases the package names are different from the +# pip-install name (e.g.k scikit-image -> skimage). +optional_deps = ['matplotlib', 'shapely'] +deps = {key.upper(): key for key in optional_deps} +__all__ = [f'HAS_{pkg}' for pkg in deps] + + +def __getattr__(name): + if name in __all__: + try: + importlib.import_module(deps[name[4:]]) + except ImportError: + return False + return True + + raise AttributeError(f'Module {__name__!r} has no attribute {name!r}.') diff --git a/regions/shapes/ellipse.py b/regions/shapes/ellipse.py index 37860fa6..056182f2 100644 --- a/regions/shapes/ellipse.py +++ b/regions/shapes/ellipse.py @@ -265,8 +265,6 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, """ from matplotlib.widgets import EllipseSelector - from regions._utils.optional_deps import MPL_VERSION - if hasattr(self, '_mpl_selector'): raise AttributeError('Cannot attach more than one selector to a region.') @@ -284,11 +282,7 @@ def sync_callback(*args, **kwargs): 'linewidth': self.visual.get('linewidth', 1), 'linestyle': self.visual.get('linestyle', 'solid')} rectprops.update(kwargs.pop('props', dict())) - # `rectprops` renamed `props` in mpl 3.5 and deprecated for 3.7. - if MPL_VERSION < 35: - kwargs.update({'rectprops': rectprops}) - else: - kwargs.update({'props': rectprops}) + kwargs.update({'props': rectprops}) self._mpl_selector = EllipseSelector(ax, sync_callback, interactive=True, **kwargs) diff --git a/regions/shapes/rectangle.py b/regions/shapes/rectangle.py index 2803aed7..10ad2880 100644 --- a/regions/shapes/rectangle.py +++ b/regions/shapes/rectangle.py @@ -261,8 +261,6 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, """ from matplotlib.widgets import RectangleSelector - from regions._utils.optional_deps import MPL_VERSION - if hasattr(self, '_mpl_selector'): raise AttributeError('Cannot attach more than one selector to a region.') @@ -281,11 +279,7 @@ def sync_callback(*args, **kwargs): 'linewidth': self.visual.get('linewidth', 1), 'linestyle': self.visual.get('linestyle', 'solid')} rectprops.update(kwargs.pop('props', dict())) - # `rectprops` renamed `props` in mpl 3.5 and deprecated for 3.7. - if MPL_VERSION < 35: - kwargs.update({'rectprops': rectprops}) - else: - kwargs.update({'props': rectprops}) + kwargs.update({'props': rectprops}) self._mpl_selector = RectangleSelector(ax, sync_callback, interactive=True, **kwargs) diff --git a/regions/shapes/tests/test_ellipse.py b/regions/shapes/tests/test_ellipse.py index 5d1c781c..30e5256b 100644 --- a/regions/shapes/tests/test_ellipse.py +++ b/regions/shapes/tests/test_ellipse.py @@ -10,7 +10,7 @@ from astropy.wcs import WCS from numpy.testing import assert_allclose, assert_equal -from regions._utils.optional_deps import HAS_MATPLOTLIB, MPL_VERSION +from regions._utils.optional_deps import HAS_MATPLOTLIB from regions.core import PixCoord, RegionMeta, RegionVisual from regions.shapes.ellipse import EllipsePixelRegion, EllipseSkyRegion from regions.shapes.tests.test_common import (BaseTestPixelRegion, @@ -108,7 +108,6 @@ def test_region_bbox_zero_size(self): EllipsePixelRegion(PixCoord(50, 50), width=10, height=0, angle=0. * u.deg) - @pytest.mark.skipif(MPL_VERSION < 33, reason='requires `do_event`') # temporarily disable sync=True test due to random failures # @pytest.mark.parametrize('sync', (False, True)) @pytest.mark.parametrize('sync', (False,)) @@ -167,7 +166,6 @@ def update_mask(reg): with pytest.raises(AttributeError, match=('Cannot attach more than one selector to a reg')): region.as_mpl_selector(ax) - @pytest.mark.skipif(MPL_VERSION < 33, reason='requires `do_event`') @pytest.mark.parametrize('anywhere', (False, True)) def test_mpl_selector_drag(self, anywhere): """Test dragging of entire region from central handle and anywhere.""" @@ -186,14 +184,10 @@ def update_mask(reg): region = self.reg.copy(angle=0 * u.deg) - if anywhere and MPL_VERSION < 35: - pytest.skip('Requires `drag_from_anywhere` kwarg') - elif MPL_VERSION < 35: - selector = region.as_mpl_selector(ax, callback=update_mask) - else: - selector = region.as_mpl_selector(ax, callback=update_mask, drag_from_anywhere=anywhere) - assert selector.drag_from_anywhere is anywhere - assert region._mpl_selector.drag_from_anywhere is anywhere + selector = region.as_mpl_selector(ax, callback=update_mask, + drag_from_anywhere=anywhere) + assert selector.drag_from_anywhere is anywhere + assert region._mpl_selector.drag_from_anywhere is anywhere # click_and_drag(selector, start=(3, 4), end=(3.5, 4.5)) do_event(selector, 'press', xdata=3, ydata=4, button=1) @@ -247,9 +241,6 @@ def update_mask(reg): region = self.reg.copy(angle=0 * u.deg) region.visual = {'color': 'red'} - if MPL_VERSION < 35 and 'grab_range' in userargs: - userargs['maxdist'] = userargs.pop('grab_range') - if 'twit' in userargs: with pytest.raises(TypeError, match=(r'__init__.. got an unexpected keyword argument')): selector = region.as_mpl_selector(ax, callback=update_mask, **userargs) diff --git a/regions/shapes/tests/test_rectangle.py b/regions/shapes/tests/test_rectangle.py index dbe848c7..a9d7939f 100644 --- a/regions/shapes/tests/test_rectangle.py +++ b/regions/shapes/tests/test_rectangle.py @@ -10,7 +10,7 @@ from astropy.wcs import WCS from numpy.testing import assert_allclose, assert_equal -from regions._utils.optional_deps import HAS_MATPLOTLIB, MPL_VERSION +from regions._utils.optional_deps import HAS_MATPLOTLIB from regions.core import PixCoord, RegionMeta, RegionVisual from regions.shapes.rectangle import RectanglePixelRegion, RectangleSkyRegion from regions.shapes.tests.test_common import (BaseTestPixelRegion, @@ -115,7 +115,6 @@ def test_eq(self): reg.angle = 35 * u.deg assert reg != self.reg - @pytest.mark.skipif(MPL_VERSION < 33, reason='requires `do_event`') # temporarily disable sync=True test due to random failures # @pytest.mark.parametrize('sync', (False, True)) @pytest.mark.parametrize('sync', (False,)) @@ -171,7 +170,6 @@ def update_mask(reg): with pytest.raises(AttributeError, match=('Cannot attach more than one selector to a reg')): region.as_mpl_selector(ax) - @pytest.mark.skipif(MPL_VERSION < 33, reason='requires `do_event`') @pytest.mark.parametrize('anywhere', (False, True)) def test_mpl_selector_drag(self, anywhere): """Test dragging of entire region from central handle and anywhere.""" @@ -191,14 +189,10 @@ def update_mask(reg): region = self.reg.copy(angle=0 * u.deg) - if anywhere and MPL_VERSION < 35: - pytest.skip('Requires `drag_from_anywhere` kwarg') - elif MPL_VERSION < 35: - selector = region.as_mpl_selector(ax, callback=update_mask) - else: - selector = region.as_mpl_selector(ax, callback=update_mask, drag_from_anywhere=anywhere) - assert selector.drag_from_anywhere is anywhere - assert region._mpl_selector.drag_from_anywhere is anywhere + selector = region.as_mpl_selector(ax, callback=update_mask, + drag_from_anywhere=anywhere) + assert selector.drag_from_anywhere is anywhere + assert region._mpl_selector.drag_from_anywhere is anywhere # click_and_drag(selector, start=(3, 4), end=(3.5, 4.5)) do_event(selector, 'press', xdata=3, ydata=4, button=1) @@ -251,9 +245,6 @@ def update_mask(reg): region = self.reg.copy(angle=0 * u.deg) - if MPL_VERSION < 35 and 'grab_range' in userargs: - userargs['maxdist'] = userargs.pop('grab_range') - if 'twit' in userargs: with pytest.raises(TypeError, match=(r'__init__.. got an unexpected keyword argument')): selector = region.as_mpl_selector(ax, callback=update_mask, **userargs)