Coverage for tests/test_serialization_reader.py: 97%
137 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-09 09:35 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-09 09:35 +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
16import tempfile
17import unittest
19from lsst.images import fits as images_fits
20from lsst.images import json as images_json
21from lsst.images.fits import FitsInputArchive
22from lsst.images.json import JsonInputArchive
23from lsst.images.serialization import ArchiveTree, read_archive
26@contextlib.contextmanager
27def count_opens(path: str):
28 """Count how many times ``path`` is physically opened for reading.
30 Yields a one-element list whose single entry is the running open count;
31 read it after the ``with`` block.
32 """
33 count = [0]
34 real_open = builtins.open
36 def counting_open(file, *args, **kwargs):
37 if isinstance(file, (str, bytes, os.PathLike)) and os.fspath(file) == path: 37 ↛ 39line 37 didn't jump to line 39 because the condition on line 37 was always true
38 count[0] += 1
39 return real_open(file, *args, **kwargs)
41 builtins.open = counting_open
42 try:
43 yield count
44 finally:
45 builtins.open = real_open
48DATA_DIR = os.path.join(os.path.dirname(__file__), "data", "schema_v1")
51def _visit_image():
52 """Load a VisitImage from the committed JSON fixture."""
53 return read_archive(os.path.join(DATA_DIR, "visit_image.json"))
56class FitsOpenTreeTestCase(unittest.TestCase):
57 """InputArchive.open_tree yields a live (archive, tree) pair."""
59 def setUp(self) -> None:
60 tmp = tempfile.TemporaryDirectory()
61 self.addCleanup(tmp.cleanup)
62 self.path = os.path.join(tmp.name, "v.fits")
63 images_fits.write(_visit_image(), self.path)
65 def test_open_tree_yields_archive_tree_and_info(self) -> None:
66 with FitsInputArchive.open_tree(self.path) as (archive, tree, info):
67 self.assertIsInstance(tree, ArchiveTree)
68 self.assertEqual(info.schema_name, "visit_image")
69 proj = tree.deserialize_component("sky_projection", archive)
70 self.assertIsNotNone(proj)
72 def test_read_still_works(self) -> None:
73 # read_archive() returns the deserialized object directly, via
74 # open_archive().
75 result = read_archive(self.path)
76 self.assertEqual(type(result).__name__, "VisitImage")
79try:
80 import h5py # noqa: F401
82 from lsst.images import ndf as images_ndf
83 from lsst.images.ndf import NdfInputArchive
85 HAVE_H5PY = True
86except ImportError:
87 HAVE_H5PY = False
90@unittest.skipUnless(HAVE_H5PY, "h5py is not available.")
91class NdfOpenTreeTestCase(unittest.TestCase):
92 """open_tree works for the NDF backend."""
94 def setUp(self) -> None:
95 tmp = tempfile.TemporaryDirectory()
96 self.addCleanup(tmp.cleanup)
97 self.path = os.path.join(tmp.name, "v.sdf")
98 images_ndf.write(_visit_image(), self.path)
100 def test_open_tree_yields_archive_tree_and_info(self) -> None:
101 with NdfInputArchive.open_tree(self.path) as (archive, tree, info):
102 self.assertIsInstance(tree, ArchiveTree)
103 self.assertEqual(info.schema_name, "visit_image")
104 self.assertIsNotNone(tree.deserialize_component("obs_info", archive))
106 def test_read_still_works(self) -> None:
107 self.assertEqual(type(read_archive(self.path)).__name__, "VisitImage")
110class JsonOpenTreeTestCase(unittest.TestCase):
111 """open_tree works for the JSON backend."""
113 def setUp(self) -> None:
114 tmp = tempfile.TemporaryDirectory()
115 self.addCleanup(tmp.cleanup)
116 self.path = os.path.join(tmp.name, "v.json")
117 images_json.write(_visit_image(), self.path)
119 def test_open_tree_yields_archive_tree_and_info(self) -> None:
120 with JsonInputArchive.open_tree(self.path) as (archive, tree, info):
121 self.assertIsInstance(tree, ArchiveTree)
122 self.assertEqual(info.schema_name, "visit_image")
123 self.assertIsNotNone(tree.deserialize_component("sky_projection", archive))
125 def test_read_still_works(self) -> None:
126 self.assertEqual(type(read_archive(self.path)).__name__, "VisitImage")
129class ReaderApiTestCase(unittest.TestCase):
130 """The user-facing serialization.open_archive() / Reader interface."""
132 def setUp(self) -> None:
133 tmp = tempfile.TemporaryDirectory()
134 self.addCleanup(tmp.cleanup)
135 self.tmp = tmp.name
136 self.vi = _visit_image()
137 self.fits = os.path.join(self.tmp, "v.fits")
138 images_fits.write(self.vi, self.fits)
140 def _check_components_and_read(self, path: str) -> None:
141 import lsst.images.serialization as ser
143 with ser.open_archive(path) as reader:
144 self.assertIsNotNone(reader.get_component("sky_projection"))
145 self.assertIsNotNone(reader.get_component("obs_info"))
146 full = reader.read()
147 self.assertEqual(type(full).__name__, "VisitImage")
149 def test_components_and_read_fits(self) -> None:
150 self._check_components_and_read(self.fits)
152 def test_components_and_read_json(self) -> None:
153 path = os.path.join(self.tmp, "v.json")
154 images_json.write(self.vi, path)
155 self._check_components_and_read(path)
157 @unittest.skipUnless(HAVE_H5PY, "h5py is not available.")
158 def test_components_and_read_ndf(self) -> None:
159 path = os.path.join(self.tmp, "v.sdf")
160 images_ndf.write(self.vi, path)
161 self._check_components_and_read(path)
163 def test_info(self) -> None:
164 import lsst.images.serialization as ser
166 with ser.open_archive(self.fits) as reader:
167 self.assertEqual(reader.info.schema_name, "visit_image")
168 self.assertEqual(reader.info.schema_version, "1.0.0")
169 self.assertIsInstance(reader.metadata, dict)
171 def test_cls_match(self) -> None:
172 import lsst.images.serialization as ser
173 from lsst.images import VisitImage
175 with ser.open_archive(self.fits, cls=VisitImage) as reader:
176 self.assertIsInstance(reader.read(), VisitImage)
178 def test_cls_mismatch_raises(self) -> None:
179 import lsst.images.serialization as ser
180 from lsst.images import Mask
182 with self.assertRaises(TypeError):
183 with ser.open_archive(self.fits, cls=Mask):
184 pass
186 def test_unknown_component(self) -> None:
187 import lsst.images.serialization as ser
188 from lsst.images.serialization import InvalidComponentError
190 with ser.open_archive(self.fits) as reader:
191 with self.assertRaises(InvalidComponentError):
192 reader.get_component("does_not_exist")
194 def test_use_after_close_raises(self) -> None:
195 import lsst.images.serialization as ser
197 with ser.open_archive(self.fits) as reader:
198 pass
199 with self.assertRaises(RuntimeError):
200 reader.get_component("sky_projection")
202 def test_fits_open_reads_file_once(self) -> None:
203 # open() must not open the file a second time just to read the schema
204 # from the primary header: the archive it opens already parses that
205 # header, so the schema is identified from a single open.
206 import lsst.images.serialization as ser
208 with count_opens(self.fits) as count:
209 with ser.open_archive(self.fits) as reader:
210 reader.get_component("sky_projection")
211 reader.get_component("obs_info")
212 self.assertEqual(count[0], 1)
215if __name__ == "__main__":
216 unittest.main()