diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 1f5df85..a9200b1 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -19,28 +19,28 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest] # Add more os? - python-version: [3.8, 3.9] + python-version: ['3.8', '3.9'] astropy-version: ['<5.0', '<5.1'] numpy-version: ['<1.20', '<1.21', '<1.22'] scipy-version: ['<1.6', '<1.7'] + # matplotlib >= 3.3 brings in a dependency on Pillow, which requires numpy >= 1.21 unless it's pinned to Pillow<10.4. matplotlib-version: ['<3.4', '<3.5'] steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install Python dependencies run: | python -m pip install --upgrade pip wheel setuptools - python -m pip install pytest pytest-astropy pyyaml - # python -m pip install -r requirements.txt + python -m pip install pytest pytest-astropy pyyaml Pillow\<10.4 python -m pip install 'scipy${{ matrix.scipy-version }}' python -m pip install 'matplotlib${{ matrix.matplotlib-version }}' python -m pip install 'astropy${{ matrix.astropy-version }}' @@ -59,20 +59,20 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install Python dependencies run: | python -m pip install --upgrade pip wheel setuptools - python -m pip install pytest pytest-astropy coveralls - python -m pip install pyyaml numpy\<1.21 scipy\<1.6 matplotlib\<3.3 astropy\<5.0 + python -m pip install pytest pytest-astropy pyyaml coveralls + python -m pip install Pillow\<10.4 numpy\<1.21 scipy\<1.6 matplotlib\<3.4 astropy\<5.0 - name: Run the test with coverage run: pytest --cov @@ -90,24 +90,23 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: [3.9] + python-version: ['3.8'] steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install Python dependencies run: | - python -m pip install --upgrade pip wheel setuptools docutils\<0.18 Sphinx - python -m pip install sphinx-astropy - python -m pip install speclite + python -m pip install --upgrade pip wheel setuptools Sphinx\<8 + python -m pip install -e .[docs] - name: Test the documentation run: sphinx-build -W --keep-going -b html docs docs/_build/html @@ -119,16 +118,16 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: [3.9] + python-version: ['3.10'] steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -136,7 +135,7 @@ jobs: run: | python -m pip install --upgrade pip wheel setuptools flake8 - - name: Test the style; failures are allowed - # This is equivalent to an allowed falure. - continue-on-error: true + - name: Test the style + # If allowed failures are needed, uncomment continue-on-error. + # continue-on-error: true run: flake8 speclite --count --max-line-length=100 diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..53f2bbe --- /dev/null +++ b/conftest.py @@ -0,0 +1,52 @@ +# this contains imports plugins that configure py.test for astropy tests. +# by importing them here in conftest.py they are discoverable by py.test +# no matter how it is invoked within the source tree. + +from astropy.version import version as astropy_version +if astropy_version < '3.0': + # With older versions of Astropy, we actually need to import the pytest + # plugins themselves in order to make them discoverable by pytest. + from astropy.tests.pytest_plugins import * +else: + # As of Astropy 3.0, the pytest plugins provided by Astropy are + # automatically made available when Astropy is installed. This means it's + # not necessary to import them here, but we still need to import global + # variables that are used for configuration. + from pytest_astropy_header.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS + +from astropy.tests.helper import enable_deprecations_as_exceptions + +## Uncomment the following line to treat all DeprecationWarnings as +## exceptions +# enable_deprecations_as_exceptions() + +## Uncomment and customize the following lines to add/remove entries from +## the list of packages for which version numbers are displayed when running +## the tests. Making it pass for KeyError is essential in some cases when +## the package uses other astropy affiliated packages. +try: + PYTEST_HEADER_MODULES['Astropy'] = 'astropy' + PYTEST_HEADER_MODULES['Pillow'] = 'PIL' + PYTEST_HEADER_MODULES['PyYAML'] = 'yaml' + del PYTEST_HEADER_MODULES['h5py'] + del PYTEST_HEADER_MODULES['Pandas'] +except (NameError, KeyError): # NameError is needed to support Astropy < 1.0 + pass + +## Uncomment the following lines to display the version number of the +## package rather than the version number of Astropy in the top line when +## running the tests. +# import os +# +## This is to figure out the affiliated package version, rather than +## using Astropy's +# try: +# from .version import version +# except ImportError: +# version = 'dev' +# +# try: +# packagename = os.path.basename(os.path.dirname(__file__)) +# TESTED_VERSIONS[packagename] = version +# except NameError: # Needed to support Astropy <= 1.0.0 +# pass diff --git a/docs/api.rst b/docs/api.rst index 3f785b1..6d9556a 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -20,7 +20,7 @@ Operations with Filters .. automodapi:: speclite.filters :no-inheritance-diagram: - :skip: get_path_of_data_file + :skip: get_path_of_data_file, trapz, simps Other Functions =============== diff --git a/docs/conf.py b/docs/conf.py index 445fee2..7e2f9c7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,6 +28,7 @@ import datetime import os import sys +from importlib import import_module try: import astropy_helpers @@ -84,7 +85,7 @@ # |version| and |release|, also used in various other places throughout the # built documents. -__import__(setup_cfg['package_name']) +import_module(setup_cfg['package_name']) package = sys.modules[setup_cfg['package_name']] # The short X.Y version. diff --git a/setup.cfg b/setup.cfg index 1110016..909577c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,7 +25,7 @@ license = BSD url = https://speclite.readthedocs.io/ edit_on_github = True github_project = desihub/speclite -install_requires = astropy scipy pyyaml pytest_astropy_header +install_requires = astropy scipy pyyaml # version should be PEP440 compatible, e.g. 0.8 or 0.8.dev (http://www.python.org/dev/peps/pep-0440) version = 0.20.dev @@ -36,6 +36,8 @@ version = 0.20.dev speclite_benchmark = speclite.benchmark:main [options.extras_require] +test = + pytest-astropy docs = sphinx-astropy diff --git a/speclite/conftest.py b/speclite/conftest.py index 782d3ed..53f2bbe 100644 --- a/speclite/conftest.py +++ b/speclite/conftest.py @@ -24,12 +24,14 @@ ## the list of packages for which version numbers are displayed when running ## the tests. Making it pass for KeyError is essential in some cases when ## the package uses other astropy affiliated packages. -# try: -# PYTEST_HEADER_MODULES['Astropy'] = 'astropy' -# PYTEST_HEADER_MODULES['scikit-image'] = 'skimage' -# del PYTEST_HEADER_MODULES['h5py'] -# except (NameError, KeyError): # NameError is needed to support Astropy < 1.0 -# pass +try: + PYTEST_HEADER_MODULES['Astropy'] = 'astropy' + PYTEST_HEADER_MODULES['Pillow'] = 'PIL' + PYTEST_HEADER_MODULES['PyYAML'] = 'yaml' + del PYTEST_HEADER_MODULES['h5py'] + del PYTEST_HEADER_MODULES['Pandas'] +except (NameError, KeyError): # NameError is needed to support Astropy < 1.0 + pass ## Uncomment the following lines to display the version number of the ## package rather than the version number of Astropy in the top line when diff --git a/speclite/filters.py b/speclite/filters.py index 8928203..75e30a9 100644 --- a/speclite/filters.py +++ b/speclite/filters.py @@ -237,7 +237,11 @@ import numpy as np import scipy.interpolate -import scipy.integrate + +try: + from scipy.integrate import trapezoid as trapz, simpson as simps +except ImportError: + from scipy.integrate import trapz, simps import astropy.table import astropy.units @@ -267,9 +271,7 @@ _photon_weighted_unit = default_wavelength_unit**2 / _hc_constant.unit # Map names to integration methods allowed by the convolution methods below. -_filter_integration_methods = dict( - trapz= scipy.integrate.trapz, - simps= scipy.integrate.simps) +_filter_integration_methods = dict(trapz=trapz, simps=simps) # Group and band names must be valid python identifiers. Although a leading # underscore is probably not a good idea, it is simpler to stick with a @@ -1806,7 +1808,7 @@ def load_filters(*names): """ # Replace any group wildcards with the corresponding canonical names. - + filters_path = get_path_of_data_file('filters/') @@ -1945,7 +1947,7 @@ def load_filter(name, load_from_cache=True, verbose=False): def plot_filters(responses, wavelength_unit=None, wavelength_limits=None, wavelength_scale='linear', - legend_loc='upper right', legend_ncols=1, + legend_loc='upper right', legend_ncols=1, response_limits=None, cmap='nipy_spectral'): """Plot one or more filter response curves. diff --git a/speclite/resample.py b/speclite/resample.py index b77fa28..bb722ba 100644 --- a/speclite/resample.py +++ b/speclite/resample.py @@ -1,14 +1,12 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst """Resample spectra using interpolation. """ -from __future__ import print_function, division - import numpy as np import numpy.ma as ma import scipy.interpolate -import pkg_resources as pkgr +from packaging import version -if pkgr.parse_version(np.__version__) >= pkgr.parse_version('1.16'): +if version.parse(np.__version__) >= version.parse('1.16'): import numpy.lib.recfunctions as rfn def resample(data_in, x_in, x_out, y, data_out=None, kind='linear'): @@ -168,7 +166,7 @@ def resample(data_in, x_in, x_out, y, data_out=None, kind='linear'): for i,y in enumerate(y_names): y_in[:,i] = data_in[y].filled(np.nan) else: - if pkgr.parse_version(np.__version__) >= pkgr.parse_version('1.16'): + if version.parse(np.__version__) >= version.parse('1.16'): # The slicing does not work in numpy 1.16 and above # we use structured_to_unstructured to get the slice that we care about y_in = rfn.structured_to_unstructured( @@ -178,7 +176,7 @@ def resample(data_in, x_in, x_out, y, data_out=None, kind='linear'): # View the structured 1D array as a 2D unstructured array (without # copying any memory). y_in = y_in.view(y_type).reshape(data_in.shape + y_shape) - + # interp1d will only propagate NaNs correctly for certain values of `kind`. # With numpy = 1.6 or 1.7, only 'nearest' and 'linear' work. # With numpy = 1.8 or 1.9, 'slinear' and kind = 0 or 1 also work. diff --git a/speclite/tests/test_accumulate.py b/speclite/tests/test_accumulate.py index b5be488..816511c 100644 --- a/speclite/tests/test_accumulate.py +++ b/speclite/tests/test_accumulate.py @@ -1,6 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import print_function, division - from astropy.tests.helper import pytest from ..accumulate import accumulate import numpy as np diff --git a/speclite/tests/test_filters.py b/speclite/tests/test_filters.py index 4a5187a..5be7916 100644 --- a/speclite/tests/test_filters.py +++ b/speclite/tests/test_filters.py @@ -1,6 +1,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst -from __future__ import print_function, division - from astropy.tests.helper import pytest from ..filters import * @@ -261,7 +259,7 @@ def test_wavelength_property(): wlen = [1, 2, 3] * u.Angstrom meta = dict(group_name='g', band_name='b') r = FilterResponse(wlen, [0,1,0], meta) - assert np.allclose(r.wavelength, r._wavelength) + assert np.allclose(r.wavelength, r._wavelength) assert np.allclose(r.wavelength, validate_wavelength_array(wlen)) def test_mag_flux_units(): diff --git a/speclite/tests/test_utils.py b/speclite/tests/test_utils.py new file mode 100644 index 0000000..4e234e5 --- /dev/null +++ b/speclite/tests/test_utils.py @@ -0,0 +1,22 @@ +import os +from ..utils import package_data + + +def test_get_path_of_data_file(): + data_file = package_data.get_path_of_data_file('filters/twomass-Ks.ecsv') + assert os.path.exists(data_file) + + +def test_get_path_of_data_dir(): + data_dir = package_data.get_path_of_data_dir() + assert os.path.isdir(data_dir) + + +def test_get_path_of_data_dir_no_importlib(monkeypatch): + data_dir = package_data.get_path_of_data_dir() + def mock_resource(foo, bar): + return data_dir + monkeypatch.setattr(package_data, '_has_importlib', False) + monkeypatch.setattr(package_data, 'resource_filename', mock_resource) + data_dir2 = package_data.get_path_of_data_dir() + assert data_dir2 == data_dir diff --git a/speclite/utils/package_data.py b/speclite/utils/package_data.py index 59f753a..c661563 100644 --- a/speclite/utils/package_data.py +++ b/speclite/utils/package_data.py @@ -1,21 +1,24 @@ import os -import pkg_resources +_has_importlib = True +try: + from importlib.resources import files + resource_filename = None +except ImportError: + from pkg_resources import resource_filename + _has_importlib = False # TODO: should make these Path objects -def get_path_of_data_file(data_file) -> str: +def get_path_of_data_file(data_file): """convenience wrapper to return location of data file """ - file_path = pkg_resources.resource_filename( - "speclite", os.path.join("data", f"{data_file}")) + return os.path.join(get_path_of_data_dir(), data_file) - return file_path - -def get_path_of_data_dir() -> str: +def get_path_of_data_dir(): """convenience wrapper to return location of data directory """ - file_path = pkg_resources.resource_filename("speclite", "data") - - return file_path + if _has_importlib: + return str(files('speclite') / 'data') + return resource_filename('speclite', 'data')