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

58 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-27 09: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 

34from typing import TYPE_CHECKING 

35 

36from lsst.utils import doImportType 

37 

38from .. import ddl 

39from .._named import NamedValueAbstractSet, NamedValueSet 

40from .._topology import TopologicalFamily, TopologicalSpace 

41from ._elements import Dimension 

42from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor 

43 

44if TYPE_CHECKING: 

45 from ..registry.interfaces import Database, GovernorDimensionRecordStorage, StaticTablesContext 

46 

47 

48class GovernorDimension(Dimension): 

49 """Governor dimension. 

50 

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

52 used to group the dimensions that depend on it. 

53 

54 Parameters 

55 ---------- 

56 name : `str` 

57 Name of the dimension. 

58 storage : `dict` 

59 Fully qualified name of the `GovernorDimensionRecordStorage` subclass 

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

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

62 metadata : `NamedValueAbstractSet` [ `ddl.FieldSpec` ] 

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

64 uniqueKeys : `NamedValueAbstractSet` [ `ddl.FieldSpec` ] 

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

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

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

68 to define unique constraints. 

69 

70 Notes 

71 ----- 

72 Most dimensions have exactly one governor dimension as a required 

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

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

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

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

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

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

79 results instead of straightforward error messages. 

80 

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

82 extent. 

83 

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

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

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

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

88 governor dimension records (instantiating them from information in the 

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

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

91 relationships would need to be reordered first. 

92 """ 

93 

94 def __init__( 

95 self, 

96 name: str, 

97 storage: dict, 

98 *, 

99 metadata: NamedValueAbstractSet[ddl.FieldSpec], 

100 uniqueKeys: NamedValueAbstractSet[ddl.FieldSpec], 

101 ): 

102 self._name = name 

103 self._storage = storage 

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

105 self._metadata = metadata 

106 self._uniqueKeys = uniqueKeys 

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

108 raise TypeError( 

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

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

111 ) 

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

113 raise TypeError( 

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

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

116 ) 

117 

118 MAX_KEY_LENGTH = 128 

119 

120 @property 

121 def name(self) -> str: 

122 # Docstring inherited from TopologicalRelationshipEndpoint. 

123 return self._name 

124 

125 @property 

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

127 # Docstring inherited from DimensionElement. 

128 return self._required 

129 

130 @property 

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

132 # Docstring inherited from DimensionElement. 

133 return NamedValueSet().freeze() 

134 

135 @property 

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

137 # Docstring inherited from TopologicalRelationshipEndpoint 

138 return MappingProxyType({}) 

139 

140 @property 

141 def metadata(self) -> NamedValueAbstractSet[ddl.FieldSpec]: 

142 # Docstring inherited from DimensionElement. 

143 return self._metadata 

144 

145 @property 

146 def uniqueKeys(self) -> NamedValueAbstractSet[ddl.FieldSpec]: 

147 # Docstring inherited from Dimension. 

148 return self._uniqueKeys 

149 

150 def makeStorage( 

151 self, 

152 db: Database, 

153 *, 

154 context: StaticTablesContext | None = None, 

155 ) -> GovernorDimensionRecordStorage: 

156 """Make storage record. 

157 

158 Constructs the `DimensionRecordStorage` instance that should 

159 be used to back this element in a registry. 

160 

161 Parameters 

162 ---------- 

163 db : `Database` 

164 Interface to the underlying database engine and namespace. 

165 context : `StaticTablesContext`, optional 

166 If provided, an object to use to create any new tables. If not 

167 provided, ``db.ensureTableExists`` should be used instead. 

168 

169 Returns 

170 ------- 

171 storage : `GovernorDimensionRecordStorage` 

172 Storage object that should back this element in a registry. 

173 """ 

174 from ..registry.interfaces import GovernorDimensionRecordStorage 

175 

176 cls = doImportType(self._storage["cls"]) 

177 assert issubclass(cls, GovernorDimensionRecordStorage) 

178 return cls.initialize(db, self, context=context, config=self._storage) 

179 

180 

181class GovernorDimensionConstructionVisitor(DimensionConstructionVisitor): 

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

183 

184 Parameters 

185 ---------- 

186 name : `str` 

187 Name of the dimension. 

188 storage : `dict` 

189 Fully qualified name of the `GovernorDimensionRecordStorage` subclass 

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

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

192 metadata : `~collections.abc.Iterable` [ `ddl.FieldSpec` ] 

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

194 uniqueKeys : `~collections.abc.Iterable` [ `ddl.FieldSpec` ] 

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

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

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

198 to define unique constraints. 

199 """ 

200 

201 def __init__( 

202 self, 

203 name: str, 

204 storage: dict, 

205 *, 

206 metadata: Iterable[ddl.FieldSpec] = (), 

207 uniqueKeys: Iterable[ddl.FieldSpec] = (), 

208 ): 

209 super().__init__(name) 

210 self._storage = storage 

211 self._metadata = NamedValueSet(metadata).freeze() 

212 self._uniqueKeys = NamedValueSet(uniqueKeys).freeze() 

213 

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

215 # Docstring inherited from DimensionConstructionVisitor. 

216 return False 

217 

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

219 # Docstring inherited from DimensionConstructionVisitor. 

220 # Special handling for creating Dimension instances. 

221 dimension = GovernorDimension( 

222 self.name, 

223 storage=self._storage, 

224 metadata=self._metadata, 

225 uniqueKeys=self._uniqueKeys, 

226 ) 

227 builder.dimensions.add(dimension) 

228 builder.elements.add(dimension)