Coverage for tests/test_serialization_reader.py: 97%
143 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-01 09:41 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-01 09:41 +0000
1# This file is part of lsst-images.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
11from __future__ import annotations
13import builtins
14import contextlib
15import os
16from pathlib import Path
18import pytest
20from lsst.images import VisitImage
21from lsst.images import fits as images_fits
22from lsst.images import json as images_json
23from lsst.images.fits import FitsInputArchive
24from lsst.images.json import JsonInputArchive
25from lsst.images.serialization import ArchiveTree, read_archive
26from lsst.images.tests import current_fixture_path
28try:
29 import h5py # noqa: F401
31 from lsst.images import ndf as images_ndf
32 from lsst.images.ndf import NdfInputArchive
34 HAVE_H5PY = True
35except ImportError:
36 HAVE_H5PY = False
38skip_no_h5py = pytest.mark.skipif(not HAVE_H5PY, reason="h5py is not installed")
40FIXTURE_DIR = Path(__file__).parent / "data" / "schemas"
43@pytest.fixture(scope="session")
44def visit_image() -> VisitImage:
45 """Return a VisitImage loaded once from the committed JSON fixture."""
46 path = current_fixture_path(FIXTURE_DIR, "visit_image")
47 return read_archive(path) # type: ignore[return-value]
50@contextlib.contextmanager
51def count_opens(path: Path | str):
52 """Return a one-element list that counts how many times ``path`` is
53 opened.
54 """
55 count = [0]
56 real_open = builtins.open
58 def counting_open(file, *args, **kwargs):
59 if isinstance(file, (str, bytes, os.PathLike)) and os.fspath(file) == os.fspath(path): 59 ↛ 61line 59 didn't jump to line 61 because the condition on line 59 was always true
60 count[0] += 1
61 return real_open(file, *args, **kwargs)
63 builtins.open = counting_open
64 try:
65 yield count
66 finally:
67 builtins.open = real_open
70def test_fits_open_tree_yields_archive_tree_and_info(visit_image: VisitImage, tmp_path: Path) -> None:
71 """Verify FitsInputArchive.open_tree yields a live archive/tree/info
72 triple.
73 """
74 path = tmp_path / "v.fits"
75 images_fits.write(visit_image, path)
76 with FitsInputArchive.open_tree(path) as (archive, tree, info):
77 assert isinstance(tree, ArchiveTree)
78 assert info.schema_name == "visit_image"
79 proj = tree.deserialize_component("sky_projection", archive)
80 assert proj is not None
83def test_fits_read_still_works(visit_image: VisitImage, tmp_path: Path) -> None:
84 """Verify read_archive() returns the deserialized object directly
85 from a FITS file.
86 """
87 path = tmp_path / "v.fits"
88 images_fits.write(visit_image, path)
89 result = read_archive(path)
90 assert type(result).__name__ == "VisitImage"
93@skip_no_h5py
94def test_ndf_open_tree_yields_archive_tree_and_info(visit_image: VisitImage, tmp_path: Path) -> None:
95 """Verify NdfInputArchive.open_tree yields a live archive/tree/info
96 triple.
97 """
98 path = tmp_path / "v.sdf"
99 images_ndf.write(visit_image, path)
100 with NdfInputArchive.open_tree(path) as (archive, tree, info):
101 assert isinstance(tree, ArchiveTree)
102 assert info.schema_name == "visit_image"
103 assert tree.deserialize_component("obs_info", archive) is not None
106@skip_no_h5py
107def test_ndf_read_still_works(visit_image: VisitImage, tmp_path: Path) -> None:
108 """Verify read_archive() returns the deserialized object directly
109 from an NDF file.
110 """
111 path = tmp_path / "v.sdf"
112 images_ndf.write(visit_image, path)
113 assert type(read_archive(path)).__name__ == "VisitImage"
116def test_json_open_tree_yields_archive_tree_and_info(visit_image: VisitImage, tmp_path: Path) -> None:
117 """Verify JsonInputArchive.open_tree yields a live archive/tree/info
118 triple.
119 """
120 path = tmp_path / "v.json"
121 images_json.write(visit_image, path)
122 with JsonInputArchive.open_tree(path) as (archive, tree, info):
123 assert isinstance(tree, ArchiveTree)
124 assert info.schema_name == "visit_image"
125 assert tree.deserialize_component("sky_projection", archive) is not None
128def test_json_read_still_works(visit_image: VisitImage, tmp_path: Path) -> None:
129 """Verify read_archive() returns the deserialized object directly
130 from a JSON file.
131 """
132 path = tmp_path / "v.json"
133 images_json.write(visit_image, path)
134 assert type(read_archive(path)).__name__ == "VisitImage"
137def _check_components_and_read(path: Path | str) -> None:
138 """Assert that serialization.open_archive() exposes components and a
139 full read on ``path``.
140 """
141 import lsst.images.serialization as ser
143 with ser.open_archive(path) as reader:
144 assert reader.get_component("sky_projection") is not None
145 assert reader.get_component("obs_info") is not None
146 full = reader.read()
147 assert type(full).__name__ == "VisitImage"
150def test_reader_api_components_and_read_fits(visit_image: VisitImage, tmp_path: Path) -> None:
151 """Verify the Reader API exposes components and a full read for FITS."""
152 path = tmp_path / "v.fits"
153 images_fits.write(visit_image, path)
154 _check_components_and_read(path)
157def test_reader_api_components_and_read_json(visit_image: VisitImage, tmp_path: Path) -> None:
158 """Verify the Reader API exposes components and a full read for JSON."""
159 path = tmp_path / "v.json"
160 images_json.write(visit_image, path)
161 _check_components_and_read(path)
164@skip_no_h5py
165def test_reader_api_components_and_read_ndf(visit_image: VisitImage, tmp_path: Path) -> None:
166 """Verify the Reader API exposes components and a full read for NDF."""
167 path = tmp_path / "v.sdf"
168 images_ndf.write(visit_image, path)
169 _check_components_and_read(path)
172def test_reader_api_info(visit_image: VisitImage, tmp_path: Path) -> None:
173 """Verify the Reader API exposes correct schema info for a FITS file."""
174 import lsst.images.serialization as ser
176 path = tmp_path / "v.fits"
177 images_fits.write(visit_image, path)
178 with ser.open_archive(path) as reader:
179 assert reader.info.schema_name == "visit_image"
180 assert reader.info.schema_version == "1.0.0"
181 assert isinstance(reader.metadata, dict)
184def test_reader_api_cls_match(visit_image: VisitImage, tmp_path: Path) -> None:
185 """Verify the Reader API accepts cls= when the type matches."""
186 import lsst.images.serialization as ser
187 from lsst.images import VisitImage
189 path = tmp_path / "v.fits"
190 images_fits.write(visit_image, path)
191 with ser.open_archive(path, cls=VisitImage) as reader:
192 assert isinstance(reader.read(), VisitImage)
195def test_reader_api_cls_mismatch_raises(visit_image: VisitImage, tmp_path: Path) -> None:
196 """Verify the Reader API raises TypeError when cls= does not match the
197 schema.
198 """
199 import lsst.images.serialization as ser
200 from lsst.images import Mask
202 path = tmp_path / "v.fits"
203 images_fits.write(visit_image, path)
204 with pytest.raises(TypeError):
205 with ser.open_archive(path, cls=Mask):
206 pass
209def test_reader_api_unknown_component(visit_image: VisitImage, tmp_path: Path) -> None:
210 """Verify the Reader API raises InvalidComponentError for an unknown
211 component name.
212 """
213 import lsst.images.serialization as ser
214 from lsst.images.serialization import InvalidComponentError
216 path = tmp_path / "v.fits"
217 images_fits.write(visit_image, path)
218 with ser.open_archive(path) as reader:
219 with pytest.raises(InvalidComponentError):
220 reader.get_component("does_not_exist")
223def test_reader_api_use_after_close_raises(visit_image: VisitImage, tmp_path: Path) -> None:
224 """Verify the Reader API raises RuntimeError when used after closing."""
225 import lsst.images.serialization as ser
227 path = tmp_path / "v.fits"
228 images_fits.write(visit_image, path)
229 with ser.open_archive(path) as reader:
230 pass
231 with pytest.raises(RuntimeError):
232 reader.get_component("sky_projection")
235def test_fits_open_reads_file_once(visit_image: VisitImage, tmp_path: Path) -> None:
236 """Verify serialization.open_archive() opens a FITS file exactly once
237 regardless of component reads.
238 """
239 import lsst.images.serialization as ser
241 path = tmp_path / "v.fits"
242 images_fits.write(visit_image, path)
243 with count_opens(path) as count:
244 with ser.open_archive(path) as reader:
245 reader.get_component("sky_projection")
246 reader.get_component("obs_info")
247 assert count[0] == 1