diff --git a/CHANGES.rst b/CHANGES.rst index da84509f75..cce7d581b8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -103,6 +103,13 @@ svo_fps - Add ``get_filter_metadata`` to allow retrieval of filter metadata. [#3528] - Add ``get_zeropoint`` to allow retrieval of filter zeropoints and allow kwarg passing to ``get_filter_metadata``. [#3545] +hips2fits +^^^^^^^^^ + +- ``Pillow`` is now an optional dependency: importing ``astroquery.hips2fits`` no + longer crashes when Pillow is not installed. A clear ``ImportError`` is raised + only when a jpg/png response is actually decoded. [#3619] + heasarc ^^^^^^^ - Add ``query_constraints`` to allow querying of different catalog columns. [#3403] diff --git a/astroquery/hips2fits/core.py b/astroquery/hips2fits/core.py index c249945430..3bb4e0eb99 100644 --- a/astroquery/hips2fits/core.py +++ b/astroquery/hips2fits/core.py @@ -9,8 +9,6 @@ from astropy.io import fits from astropy.coordinates import Angle import astropy.units as u -from PIL import Image - from astropy.utils.exceptions import AstropyUserWarning from ..query import BaseQuery @@ -325,6 +323,13 @@ def _parse_result(self, response, verbose, format): return hdul else: # jpg/png formats + try: + from PIL import Image + except ImportError: + raise ImportError( + "Pillow is required to decode jpg/png responses from hips2fits. " + "Install it with: pip install Pillow" + ) bytes = io.BytesIO(bytes_str) im = Image.open(bytes) data = np.asarray(im) diff --git a/astroquery/hips2fits/tests/test_hips2fits.py b/astroquery/hips2fits/tests/test_hips2fits.py index 668f60b9b4..a45a4927f0 100644 --- a/astroquery/hips2fits/tests/test_hips2fits.py +++ b/astroquery/hips2fits/tests/test_hips2fits.py @@ -1,4 +1,8 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst +import sys +from unittest.mock import MagicMock, patch + +import pytest from astropy import wcs as astropy_wcs from matplotlib.colors import Colormap from astropy.coordinates import Angle, Longitude, Latitude @@ -7,6 +11,27 @@ from ..core import hips2fits +class TestHips2fits: + + def test_jpg_format_requires_pillow(self): + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.content = b'fake_image_bytes' + + with patch.dict(sys.modules, {'PIL': None, 'PIL.Image': None}): + with pytest.raises(ImportError, match="Pillow is required"): + hips2fits._parse_result(mock_response, verbose=False, format='jpg') + + def test_png_format_requires_pillow(self): + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.content = b'fake_image_bytes' + + with patch.dict(sys.modules, {'PIL': None, 'PIL.Image': None}): + with pytest.raises(ImportError, match="Pillow is required"): + hips2fits._parse_result(mock_response, verbose=False, format='png') + + class TestHips2fitsRemote: # Create a new WCS astropy object diff --git a/pyproject.toml b/pyproject.toml index bbe6c2c6cd..e266b4677e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ all = [ "boto3", "botocore", "regions>=0.5", + "Pillow", ] [build-system]