Coverage for python/lsst/verify/yamlpersistance.py: 42%

64 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-01-10 03:00 -0800

1# This file is part of verify. 

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# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22"""Shared code for persisting verify objects to YAML. 

23 

24The YAML code is centralized in one module to simplify registration code. 

25""" 

26 

27__all__ = [] # code defined by this module only called indirectly 

28 

29 

30import astropy.units as u 

31import yaml 

32 

33from .measurement import Datum, Blob, Measurement 

34 

35 

36def _getValidLoaders(): 

37 """Return a list of supported YAML loaders. 

38 

39 For YAML >= 5.1 need a different Loader for the constructor 

40 

41 Returns 

42 ------- 

43 loaderList : `sequence` 

44 A list of loaders that are supported by the current PyYAML version. 

45 """ 

46 loaderList = [yaml.Loader, yaml.CLoader] 

47 try: 

48 loaderList.append(yaml.FullLoader) 

49 except AttributeError: 

50 pass 

51 try: 

52 loaderList.append(yaml.UnsafeLoader) 

53 except AttributeError: 

54 pass 

55 try: 

56 loaderList.append(yaml.SafeLoader) 

57 except AttributeError: 

58 pass 

59 return loaderList 

60 

61 

62def _registerTypes(): 

63 yaml.add_representer(Datum, datum_representer) 

64 yaml.add_representer(Blob, blob_representer) 

65 yaml.add_representer(Measurement, measurement_representer) 

66 

67 for loader in _getValidLoaders(): 

68 yaml.add_constructor( 

69 "lsst.verify.Datum", datum_constructor, Loader=loader) 

70 yaml.add_constructor( 

71 "lsst.verify.Blob", blob_constructor, Loader=loader) 

72 yaml.add_constructor( 

73 "lsst.verify.Measurement", measurement_constructor, Loader=loader) 

74 

75 

76# Based on Measurement.json, but provides self-contained representation 

77def measurement_representer(dumper, measurement): 

78 """Persist a Measurement as a mapping. 

79 """ 

80 if measurement.quantity is None: 

81 normalized_value = None 

82 normalized_unit_str = None 

83 elif measurement.metric is not None: 

84 # ensure metrics are normalized to metric definition's units 

85 metric = measurement.metric 

86 normalized_value = float(measurement.quantity.to(metric.unit).value) 

87 normalized_unit_str = metric.unit_str 

88 else: 

89 normalized_value = float(measurement.quantity.value) 

90 normalized_unit_str = str(measurement.quantity.unit) 

91 

92 return dumper.represent_mapping( 

93 "lsst.verify.Measurement", 

94 {"metric": str(measurement.metric_name), 

95 "identifier": measurement.identifier, 

96 "value": normalized_value, 

97 "unit": normalized_unit_str, 

98 "notes": dict(measurement.notes), 

99 # extras included in blobs 

100 "blobs": list(measurement.blobs.values()), 

101 }, 

102 ) 

103 

104 

105# Based on Measurement.deserialize 

106def measurement_constructor(loader, node): 

107 state = loader.construct_mapping(node, deep=True) 

108 

109 quantity = u.Quantity(state["value"], u.Unit(state["unit"])) 

110 

111 instance = Measurement( 

112 state["metric"], 

113 quantity=quantity, 

114 notes=state["notes"], 

115 blobs=state["blobs"], 

116 ) 

117 instance._id = state["identifier"] # re-wire id from serialization 

118 return instance 

119 

120 

121# Port of Blob.json to yaml 

122def blob_representer(dumper, blob): 

123 """Persist a Blob as a mapping. 

124 """ 

125 return dumper.represent_mapping( 

126 "lsst.verify.Blob", 

127 {"identifier": blob.identifier, 

128 "name": blob.name, 

129 "data": blob._datums, 

130 } 

131 ) 

132 

133 

134# Port of Blob.deserialize to yaml 

135def blob_constructor(loader, node): 

136 state = loader.construct_mapping(node, deep=True) 

137 

138 data = state["data"] if state["data"] is not None else {} 

139 instance = Blob(state["name"], **data) 

140 instance._id = state["identifier"] # re-wire id from serialization 

141 return instance 

142 

143 

144# Port of Datum.json to yaml 

145def datum_representer(dumper, datum): 

146 """Persist a Datum as a mapping. 

147 """ 

148 if datum._is_non_quantity_type(datum.quantity): 

149 v = datum.quantity 

150 elif len(datum.quantity.shape) > 0: 

151 v = datum.quantity.value.tolist() 

152 else: 

153 # Some versions of astropy return numpy scalars, 

154 # which pyyaml can't handle safely 

155 v = float(datum.quantity.value) 

156 

157 return dumper.represent_mapping( 

158 "lsst.verify.Datum", 

159 {"value": v, 

160 "unit": datum.unit_str, 

161 "label": datum.label, 

162 "description": datum.description, 

163 } 

164 ) 

165 

166 

167# Port of Datum.deserialize to yaml 

168def datum_constructor(loader, node): 

169 state = loader.construct_mapping(node, deep=True) 

170 

171 return Datum(quantity=state["value"], 

172 unit=state["unit"], 

173 label=state["label"], 

174 description=state["description"], 

175 ) 

176 

177 

178_registerTypes()