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

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

59 statements  

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__ = ( 

25 "GovernorDimension", 

26) 

27 

28from types import MappingProxyType 

29from typing import ( 

30 AbstractSet, 

31 Iterable, 

32 Mapping, 

33 Optional, 

34 TYPE_CHECKING, 

35) 

36 

37from lsst.utils import doImportType 

38 

39from .. import ddl 

40from ..named import NamedValueAbstractSet, NamedValueSet 

41from .._topology import TopologicalFamily, TopologicalSpace 

42 

43from ._elements import Dimension 

44from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor 

45 

46if TYPE_CHECKING: 46 ↛ 47line 46 didn't jump to line 47, because the condition on line 46 was never true

47 from ...registry.interfaces import ( 

48 Database, 

49 GovernorDimensionRecordStorage, 

50 StaticTablesContext, 

51 ) 

52 

53 

54class GovernorDimension(Dimension): 

55 """Governor dimension. 

56 

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

58 used to group the dimensions that depend on it. 

59 

60 Parameters 

61 ---------- 

62 name : `str` 

63 Name of the dimension. 

64 storage : `dict` 

65 Fully qualified name of the `GovernorDimensionRecordStorage` subclass 

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

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

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

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

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

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

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

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

74 to define unique constraints. 

75 

76 Notes 

77 ----- 

78 Most dimensions have exactly one governor dimension as a required 

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

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

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

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

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

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

85 results instead of straightforward error messages. 

86 

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

88 extent. 

89 

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

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

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

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

94 governor dimension records (instantiating them from information in the 

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

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

97 relationships would need to be reordered first. 

98 """ 

99 

100 def __init__( 

101 self, 

102 name: str, 

103 storage: dict, *, 

104 metadata: NamedValueAbstractSet[ddl.FieldSpec], 

105 uniqueKeys: NamedValueAbstractSet[ddl.FieldSpec], 

106 ): 

107 self._name = name 

108 self._storage = storage 

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

110 self._metadata = metadata 

111 self._uniqueKeys = uniqueKeys 

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

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

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

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

116 raise TypeError(f"Governor dimension '{name}' must have a string primary key with length <= " 

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

118 

119 MAX_KEY_LENGTH = 128 

120 

121 @property 

122 def name(self) -> str: 

123 # Docstring inherited from TopologicalRelationshipEndpoint. 

124 return self._name 

125 

126 @property 

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

128 # Docstring inherited from DimensionElement. 

129 return self._required 

130 

131 @property 

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

133 # Docstring inherited from DimensionElement. 

134 return NamedValueSet().freeze() 

135 

136 @property 

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

138 # Docstring inherited from TopologicalRelationshipEndpoint 

139 return MappingProxyType({}) 

140 

141 @property 

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

143 # Docstring inherited from DimensionElement. 

144 return self._metadata 

145 

146 @property 

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

148 # Docstring inherited from Dimension. 

149 return self._uniqueKeys 

150 

151 def makeStorage( 

152 self, 

153 db: Database, *, 

154 context: Optional[StaticTablesContext] = 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 cls = doImportType(self._storage["cls"]) 

176 assert issubclass(cls, GovernorDimensionRecordStorage) 

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

178 

179 

180class GovernorDimensionConstructionVisitor(DimensionConstructionVisitor): 

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

182 

183 Parameters 

184 ---------- 

185 name : `str` 

186 Name of the dimension. 

187 storage : `dict` 

188 Fully qualified name of the `GovernorDimensionRecordStorage` subclass 

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

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

191 metadata : `Iterable` [ `ddl.FieldSpec` ] 

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

193 uniqueKeys : `Iterable` [ `ddl.FieldSpec` ] 

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

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

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

197 to define unique constraints. 

198 """ 

199 

200 def __init__( 

201 self, 

202 name: str, 

203 storage: dict, *, 

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

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

206 ): 

207 super().__init__(name) 

208 self._storage = storage 

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

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

211 

212 def hasDependenciesIn(self, others: AbstractSet[str]) -> bool: 

213 # Docstring inherited from DimensionConstructionVisitor. 

214 return False 

215 

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

217 # Docstring inherited from DimensionConstructionVisitor. 

218 # Special handling for creating Dimension instances. 

219 dimension = GovernorDimension( 

220 self.name, 

221 storage=self._storage, 

222 metadata=self._metadata, 

223 uniqueKeys=self._uniqueKeys, 

224 ) 

225 builder.dimensions.add(dimension) 

226 builder.elements.add(dimension)