Skip to content
Merged
18 changes: 16 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
Expand All @@ -49,8 +49,22 @@ jobs:
cache-dependency-path: "pyproject.toml"
- run: sudo apt-get install -y libbz2-dev # required to build fitsio
- run: pip install -c .github/test-constraints.txt '.[test]'
- run: pytest --cov=heracles --cov-report=lcov
- run: pytest --cov=heracles --cov-report=xml
- uses: coverallsapp/github-action@v2
with:
parallel: true
flag-name: run-${{ matrix.python-version }}

finish:
name: Finish
runs-on: ubuntu-latest
needs: test
if: always()
steps:
- uses: coverallsapp/github-action@v2
with:
parallel-finished: true
carryforward: "run-3.9,run-3.10,run-3.11,run-3.12,run-3.13"

build:
name: Build
Expand Down
2 changes: 2 additions & 0 deletions heracles/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,8 @@ def __getitem__(self, key):
data = self._cache.get(ext)
if data is None:
with self.fits as fits:
if ext not in fits:
raise KeyError(ext)
data = self.reader(fits[ext])
self._cache[ext] = data
return data
Expand Down
72 changes: 67 additions & 5 deletions tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,30 @@ def zbins():
return {0: (0.0, 0.8), 1: (1.0, 1.2)}


@pytest.fixture
def zbins2():
return {2: (0.0, 0.8), 3: (1.0, 1.2)}


@pytest.fixture
def mock_alms(rng, zbins):
return generate_mock_alms(rng, zbins)


@pytest.fixture
def mock_alms2(rng, zbins2):
return generate_mock_alms(rng, zbins2)


def generate_mock_alms(rng, zbins):
import numpy as np

lmax = 32

Nlm = (lmax + 1) * (lmax + 2) // 2

# names and spins
fields = {"P": 0, "G": 2}

alms = {}

for n, s in fields.items():
shape = (Nlm, 2) if s == 0 else (2, Nlm, 2)
for i in zbins:
Expand Down Expand Up @@ -258,17 +270,67 @@ def test_write_read_alms(mock_alms, tmp_path):
import numpy as np

path = tmp_path / "alms.fits"

heracles.write_alms(path, mock_alms)
assert path.exists()
alms = heracles.read_alms(path)

alms = heracles.read_alms(path)
assert alms.keys() == mock_alms.keys()

for key in mock_alms:
np.testing.assert_array_equal(mock_alms[key], alms[key])
assert mock_alms[key].dtype.metadata == alms[key].dtype.metadata


def test_fits_dict_keyerror(mock_alms, tmp_path):
from heracles.io import FitsDict
from pathlib import Path

tmp_path = Path(tmp_path)
path1 = tmp_path / "alms1.fits"

heracles.write_alms(path1, mock_alms)

assert path1.exists()

alms1 = FitsDict(path1, clobber=False)

# Ensure bad key raises a key error
with pytest.raises(KeyError):
_ = alms1["badkey"]

# Ensure an existing key does NOT raise an error
_ = alms1[("P", 0)]


def test_chain_almfits(mock_alms, mock_alms2, tmp_path):
from heracles.io import AlmFits
from collections import ChainMap
import numpy as np

path1 = tmp_path / "alms1.fits"
path2 = tmp_path / "alms2.fits"

heracles.write_alms(path1, mock_alms)
heracles.write_alms(path2, mock_alms2)

assert path1.exists()
assert path2.exists()

alms1 = AlmFits(path1, clobber=False)
alms2 = AlmFits(path2, clobber=False)
chained_alms = ChainMap()

chained_alms.maps.append(alms1)
chained_alms.maps.append(alms2)

for key in mock_alms:
np.testing.assert_array_equal(mock_alms[key], chained_alms[key])
assert mock_alms[key].dtype.metadata == chained_alms[key].dtype.metadata
for key in mock_alms2:
np.testing.assert_array_equal(mock_alms2[key], chained_alms[key])
assert mock_alms2[key].dtype.metadata == chained_alms[key].dtype.metadata


def test_write_read_cls(mock_cls, tmp_path):
import numpy as np

Expand Down
2 changes: 1 addition & 1 deletion tests/test_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def test_result_2d(rng):
@pytest.mark.parametrize("weight", [None, "l(l+1)", "2l+1", "<rand>"])
@pytest.mark.parametrize("ndim,axis", [(1, 0), (2, 0), (3, 1)])
def test_binned(ndim, axis, weight, rng):
shape = rng.integers(0, 100, ndim)
shape = rng.integers(1, 100, ndim)
lmax = shape[axis] - 1

data = heracles.Result(rng.standard_normal(shape), axis=axis)
Expand Down