Coverage for python / lsst / images / serialization / _input_archive.py: 100%

28 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-17 09:16 +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. 

11 

12from __future__ import annotations 

13 

14__all__ = ("InputArchive",) 

15 

16from abc import ABC, abstractmethod 

17from collections.abc import Callable 

18from types import EllipsisType 

19from typing import TYPE_CHECKING, TypeVar 

20 

21import astropy.io.fits 

22import astropy.table 

23import astropy.units 

24import numpy as np 

25import pydantic 

26 

27from ._asdf_utils import ArrayReferenceModel 

28from ._common import ArchiveTree, OpaqueArchiveMetadata, no_header_updates 

29from ._tables import TableReferenceModel 

30 

31if TYPE_CHECKING: 

32 from .._transforms import FrameSet 

33 

34 

35# This pre-python-3.12 declaration is needed by Sphinx (probably the 

36# autodoc-typehints plugin. 

37P = TypeVar("P", bound=pydantic.BaseModel) 

38 

39 

40class InputArchive[P: pydantic.BaseModel](ABC): 

41 """Abstract interface for reading from a file format. 

42 

43 Notes 

44 ----- 

45 An input archive instance is assumed to be paired with a Pydantic model 

46 that represents a JSON tree, with the archive used to deserialize data that 

47 is not native JSON from data that is (which may just be a reference to 

48 binary data stored elsewhere in the file). The archive doesn't actually 

49 hold that model instance because we'd prefer to avoid making the input 

50 archive generic over the model type. It is expected that most concrete 

51 archive implementations will provide a method to load the paired model from 

52 a file, but this is not part of the base class interface. 

53 """ 

54 

55 @abstractmethod 

56 def deserialize_pointer[U: ArchiveTree, V]( 

57 self, pointer: P, model_type: type[U], deserializer: Callable[[U, InputArchive[P]], V] 

58 ) -> V: 

59 """Deserialize an object that was saved by 

60 `~lsst.serialization.OutputArchive.serialize_pointer`. 

61 

62 Parameters 

63 ---------- 

64 pointer 

65 JSON Pointer model to dereference. 

66 model_type 

67 Pydantic model type that the pointer should dereference to. 

68 deserializer 

69 Callable that takes an instance of ``model_type`` and an input 

70 archive, and returns the deserialized object. 

71 

72 Returns 

73 ------- 

74 V 

75 The deserialized object. 

76 

77 Notes 

78 ----- 

79 Implementations are required to remember previously-deserialized 

80 objects and return them when the same pointer is passed in multiple 

81 times. 

82 

83 There is no ``deserialize_direct`` (to pair with 

84 `~lsst.serialization.OutputArchive.serialize_direct`) because the 

85 caller can just call a deserializer function directly on a sub-model 

86 of its Pydantic tree. 

87 """ 

88 raise NotImplementedError() 

89 

90 @abstractmethod 

91 def get_frame_set(self, ref: P) -> FrameSet: 

92 """Return an already-deserialized frame set from the archive. 

93 

94 Parameters 

95 ---------- 

96 ref 

97 Implementation-specific reference to the frame set. 

98 

99 Returns 

100 ------- 

101 FrameSet 

102 Loaded frame set. 

103 """ 

104 raise NotImplementedError() 

105 

106 @abstractmethod 

107 def get_array( 

108 self, 

109 ref: ArrayReferenceModel, 

110 *, 

111 slices: tuple[slice, ...] | EllipsisType = ..., 

112 strip_header: Callable[[astropy.io.fits.Header], None] = no_header_updates, 

113 ) -> np.ndarray: 

114 """Load an array from the archive. 

115 

116 Parameters 

117 ---------- 

118 ref 

119 A Pydantic model that references the array. 

120 slices 

121 Slices that specify a subset of the original array to read. 

122 strip_header 

123 A callable that strips out any FITS header cards added by the 

124 ``update_header`` argument in the corresponding call to 

125 `~lsst.images.serialization.OutputArchive.add_array`. 

126 """ 

127 raise NotImplementedError() 

128 

129 @abstractmethod 

130 def get_table( 

131 self, 

132 ref: TableReferenceModel, 

133 strip_header: Callable[[astropy.io.fits.Header], None] = no_header_updates, 

134 ) -> astropy.table.Table: 

135 """Load a table from the archive. 

136 

137 Parameters 

138 ---------- 

139 ref 

140 A Pydantic model that references the table. 

141 strip_header 

142 A callable that strips out any FITS header cards added by the 

143 ``update_header`` argument in the corresponding call to 

144 `~lsst.serialization.OutputArchive.add_table`. 

145 

146 Returns 

147 ------- 

148 astropy.table.Table 

149 The loaded table. 

150 """ 

151 raise NotImplementedError() 

152 

153 @abstractmethod 

154 def get_structured_array( 

155 self, 

156 ref: TableReferenceModel, 

157 strip_header: Callable[[astropy.io.fits.Header], None] = no_header_updates, 

158 ) -> np.ndarray: 

159 """Load a table from the archive as a structured array. 

160 

161 Parameters 

162 ---------- 

163 ref 

164 A Pydantic model that references the table. 

165 strip_header 

166 A callable that strips out any FITS header cards added by the 

167 ``update_header`` argument in the corresponding call to 

168 `~lsst.serialization.OutputArchive.add_structured_array`. 

169 

170 Returns 

171 ------- 

172 numpy.ndarray 

173 The loaded table as a structured array. 

174 """ 

175 raise NotImplementedError() 

176 

177 @abstractmethod 

178 def get_opaque_metadata(self) -> OpaqueArchiveMetadata: 

179 """Return opaque metadata loaded from the file that should be saved if 

180 another version of the object is saved to the same file format. 

181 

182 Returns 

183 ------- 

184 OpaqueArchiveMetadata 

185 Opaque metadata specific to this archive type that should be 

186 round-tripped if it is saved in the same format. 

187 """ 

188 raise NotImplementedError()