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

58 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-08-05 01:26 +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 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 <http://www.gnu.org/licenses/>. 

21 

22from __future__ import annotations 

23 

24__all__ = ("GovernorDimension",) 

25 

26from collections.abc import Iterable, Mapping, Set 

27from types import MappingProxyType 

28from typing import TYPE_CHECKING 

29 

30from lsst.utils import doImportType 

31 

32from .. import ddl 

33from .._topology import TopologicalFamily, TopologicalSpace 

34from ..named import NamedValueAbstractSet, NamedValueSet 

35from ._elements import Dimension 

36from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor 

37 

38if TYPE_CHECKING: 

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

40 

41 

42class GovernorDimension(Dimension): 

43 """Governor dimension. 

44 

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

46 used to group the dimensions that depend on it. 

47 

48 Parameters 

49 ---------- 

50 name : `str` 

51 Name of the dimension. 

52 storage : `dict` 

53 Fully qualified name of the `GovernorDimensionRecordStorage` subclass 

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

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

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

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

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

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

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

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

62 to define unique constraints. 

63 

64 Notes 

65 ----- 

66 Most dimensions have exactly one governor dimension as a required 

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

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

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

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

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

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

73 results instead of straightforward error messages. 

74 

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

76 extent. 

77 

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

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

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

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

82 governor dimension records (instantiating them from information in the 

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

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

85 relationships would need to be reordered first. 

86 """ 

87 

88 def __init__( 

89 self, 

90 name: str, 

91 storage: dict, 

92 *, 

93 metadata: NamedValueAbstractSet[ddl.FieldSpec], 

94 uniqueKeys: NamedValueAbstractSet[ddl.FieldSpec], 

95 ): 

96 self._name = name 

97 self._storage = storage 

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

99 self._metadata = metadata 

100 self._uniqueKeys = uniqueKeys 

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

102 raise TypeError( 

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

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

105 ) 

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

107 raise TypeError( 

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

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

110 ) 

111 

112 MAX_KEY_LENGTH = 128 

113 

114 @property 

115 def name(self) -> str: 

116 # Docstring inherited from TopologicalRelationshipEndpoint. 

117 return self._name 

118 

119 @property 

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

121 # Docstring inherited from DimensionElement. 

122 return self._required 

123 

124 @property 

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

126 # Docstring inherited from DimensionElement. 

127 return NamedValueSet().freeze() 

128 

129 @property 

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

131 # Docstring inherited from TopologicalRelationshipEndpoint 

132 return MappingProxyType({}) 

133 

134 @property 

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

136 # Docstring inherited from DimensionElement. 

137 return self._metadata 

138 

139 @property 

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

141 # Docstring inherited from Dimension. 

142 return self._uniqueKeys 

143 

144 def makeStorage( 

145 self, 

146 db: Database, 

147 *, 

148 context: StaticTablesContext | None = None, 

149 ) -> GovernorDimensionRecordStorage: 

150 """Make storage record. 

151 

152 Constructs the `DimensionRecordStorage` instance that should 

153 be used to back this element in a registry. 

154 

155 Parameters 

156 ---------- 

157 db : `Database` 

158 Interface to the underlying database engine and namespace. 

159 context : `StaticTablesContext`, optional 

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

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

162 

163 Returns 

164 ------- 

165 storage : `GovernorDimensionRecordStorage` 

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

167 """ 

168 from ...registry.interfaces import GovernorDimensionRecordStorage 

169 

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

171 assert issubclass(cls, GovernorDimensionRecordStorage) 

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

173 

174 

175class GovernorDimensionConstructionVisitor(DimensionConstructionVisitor): 

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

177 

178 Parameters 

179 ---------- 

180 name : `str` 

181 Name of the dimension. 

182 storage : `dict` 

183 Fully qualified name of the `GovernorDimensionRecordStorage` subclass 

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

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

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

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

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

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

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

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

192 to define unique constraints. 

193 """ 

194 

195 def __init__( 

196 self, 

197 name: str, 

198 storage: dict, 

199 *, 

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

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

202 ): 

203 super().__init__(name) 

204 self._storage = storage 

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

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

207 

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

209 # Docstring inherited from DimensionConstructionVisitor. 

210 return False 

211 

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

213 # Docstring inherited from DimensionConstructionVisitor. 

214 # Special handling for creating Dimension instances. 

215 dimension = GovernorDimension( 

216 self.name, 

217 storage=self._storage, 

218 metadata=self._metadata, 

219 uniqueKeys=self._uniqueKeys, 

220 ) 

221 builder.dimensions.add(dimension) 

222 builder.elements.add(dimension)