Coverage for python/lsst/daf/butler/dimensions/_governor.py: 60%

58 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-16 10:44 +0000

1# This file is part of daf_butler. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://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 software is dual licensed under the GNU General Public License and also 

10# under a 3-clause BSD license. Recipients may choose which of these licenses 

11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, 

12# respectively. If you choose the GPL option then the following text applies 

13# (but note that there is still no warranty even if you opt for BSD instead): 

14# 

15# This program is free software: you can redistribute it and/or modify 

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

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

18# (at your option) any later version. 

19# 

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

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

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

23# GNU General Public License for more details. 

24# 

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

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

27 

28from __future__ import annotations 

29 

30__all__ = ("GovernorDimension",) 

31 

32from collections.abc import Iterable, Mapping, Set 

33from types import MappingProxyType 

34 

35from .._named import NamedValueAbstractSet, NamedValueSet 

36from .._topology import TopologicalFamily, TopologicalSpace 

37from ._elements import Dimension, KeyColumnSpec, MetadataColumnSpec 

38from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor 

39 

40 

41class GovernorDimension(Dimension): 

42 """Governor dimension. 

43 

44 A special `Dimension` with no dependencies and a small number of rows, 

45 used to group the dimensions that depend on it. 

46 

47 Parameters 

48 ---------- 

49 name : `str` 

50 Name of the dimension. 

51 storage : `dict` 

52 Fully qualified name of the `GovernorDimensionRecordStorage` subclass 

53 that will back this element in the registry (in a "cls" key) along 

54 with any other construction keyword arguments (in other keys). 

55 metadata_columns : `NamedValueAbstractSet` [ `MetadataColumnSpec` ] 

56 Field specifications for all non-key fields in this dimension's table. 

57 unique_keys : `NamedValueAbstractSet` [ `KeyColumnSpec` ] 

58 Fields that can each be used to uniquely identify this dimension (given 

59 values for all required dimensions). The first of these is used as 

60 (part of) this dimension's table's primary key, while others are used 

61 to define unique constraints. 

62 doc : `str` 

63 Extended description of this element. 

64 

65 Notes 

66 ----- 

67 Most dimensions have exactly one governor dimension as a required 

68 dependency, and queries that involve those dimensions are always expected 

69 to explicitly identify the governor dimension value(s), rather than 

70 retrieve all matches from the database. Because governor values are thus 

71 almost always known at query-generation time, they can be used there to 

72 simplify queries, provide sensible defaults, or check in advance for common 

73 mistakes that might otherwise yield confusing (albeit formally correct) 

74 results instead of straightforward error messages. 

75 

76 Governor dimensions may not be associated with any kind of topological 

77 extent. 

78 

79 Governor dimension rows are often affiliated with a Python class or 

80 instance (e.g. `lsst.obs.base.Instrument`) that is capable of generating 

81 the rows of at least some dependent dimensions or providing other related 

82 functionality. In the future, we hope to attach these instances to 

83 governor dimension records (instantiating them from information in the 

84 database row when it is fetched), and use those objects to add additional 

85 functionality to governor dimensions, but a number of (code) dependency 

86 relationships would need to be reordered first. 

87 """ 

88 

89 def __init__( 

90 self, 

91 name: str, 

92 storage: dict, 

93 *, 

94 metadata_columns: NamedValueAbstractSet[MetadataColumnSpec], 

95 unique_keys: NamedValueAbstractSet[KeyColumnSpec], 

96 doc: str, 

97 ): 

98 self._name = name 

99 self._storage = storage 

100 self._required = NamedValueSet({self}).freeze() 

101 self._metadata_columns = metadata_columns 

102 self._unique_keys = unique_keys 

103 self._doc = doc 

104 if self.primaryKey.getPythonType() is not str: 

105 raise TypeError( 

106 f"Governor dimension '{name}' must have a string primary key (configured type " 

107 f"is {self.primaryKey.dtype.__name__})." 

108 ) 

109 if self.primaryKey.length is not None and self.primaryKey.length > self.MAX_KEY_LENGTH: 

110 raise TypeError( 

111 f"Governor dimension '{name}' must have a string primary key with length <= " 

112 f"{self.MAX_KEY_LENGTH} (configured value is {self.primaryKey.length})." 

113 ) 

114 

115 MAX_KEY_LENGTH = 128 

116 

117 @property 

118 def name(self) -> str: 

119 # Docstring inherited from TopologicalRelationshipEndpoint. 

120 return self._name 

121 

122 @property 

123 def required(self) -> NamedValueAbstractSet[Dimension]: 

124 # Docstring inherited from DimensionElement. 

125 return self._required 

126 

127 @property 

128 def implied(self) -> NamedValueAbstractSet[Dimension]: 

129 # Docstring inherited from DimensionElement. 

130 return NamedValueSet().freeze() 

131 

132 @property 

133 def topology(self) -> Mapping[TopologicalSpace, TopologicalFamily]: 

134 # Docstring inherited from TopologicalRelationshipEndpoint 

135 return MappingProxyType({}) 

136 

137 @property 

138 def metadata_columns(self) -> NamedValueAbstractSet[MetadataColumnSpec]: 

139 # Docstring inherited from DimensionElement. 

140 return self._metadata_columns 

141 

142 @property 

143 def unique_keys(self) -> NamedValueAbstractSet[KeyColumnSpec]: 

144 # Docstring inherited from Dimension. 

145 return self._unique_keys 

146 

147 @property 

148 def is_cached(self) -> bool: 

149 # Docstring inherited. 

150 return True 

151 

152 @property 

153 def documentation(self) -> str: 

154 # Docstring inherited from DimensionElement. 

155 return self._doc 

156 

157 

158class GovernorDimensionConstructionVisitor(DimensionConstructionVisitor): 

159 """A construction visitor for `GovernorDimension`. 

160 

161 Parameters 

162 ---------- 

163 name : `str` 

164 Name of the dimension. 

165 storage : `dict` 

166 Fully qualified name of the `GovernorDimensionRecordStorage` subclass 

167 that will back this element in the registry (in a "cls" key) along 

168 with any other construction keyword arguments (in other keys). 

169 metadata_columns : `~collections.abc.Iterable` [ `MetadataColumnSpec` ] 

170 Field specifications for all non-key fields in this element's table. 

171 unique_keys : `~collections.abc.Iterable` [ `KeyColumnSpec` ] 

172 Fields that can each be used to uniquely identify this dimension (given 

173 values for all required dimensions). The first of these is used as 

174 (part of) this dimension's table's primary key, while others are used 

175 to define unique constraints. 

176 doc : `str` 

177 Extended description of this element. 

178 """ 

179 

180 def __init__( 

181 self, 

182 name: str, 

183 storage: dict, 

184 *, 

185 metadata_columns: Iterable[MetadataColumnSpec] = (), 

186 unique_keys: Iterable[KeyColumnSpec] = (), 

187 doc: str, 

188 ): 

189 super().__init__(name) 

190 self._storage = storage 

191 self._metadata_columns = NamedValueSet(metadata_columns).freeze() 

192 self._unique_keys = NamedValueSet(unique_keys).freeze() 

193 self._doc = doc 

194 

195 def hasDependenciesIn(self, others: Set[str]) -> bool: 

196 # Docstring inherited from DimensionConstructionVisitor. 

197 return False 

198 

199 def visit(self, builder: DimensionConstructionBuilder) -> None: 

200 # Docstring inherited from DimensionConstructionVisitor. 

201 # Special handling for creating Dimension instances. 

202 dimension = GovernorDimension( 

203 self.name, 

204 storage=self._storage, 

205 metadata_columns=self._metadata_columns, 

206 unique_keys=self._unique_keys, 

207 doc=self._doc, 

208 ) 

209 builder.dimensions.add(dimension) 

210 builder.elements.add(dimension)