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

59 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-24 23:50 -0700

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 types import MappingProxyType 

27from typing import TYPE_CHECKING, AbstractSet, Iterable, Mapping, Optional 

28 

29from lsst.utils import doImportType 

30 

31from .. import ddl 

32from .._topology import TopologicalFamily, TopologicalSpace 

33from ..named import NamedValueAbstractSet, NamedValueSet 

34from ._elements import Dimension 

35from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor 

36 

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

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

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 : `NamedValueAbstractSet` [ `ddl.FieldSpec` ] 

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

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

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 

63 Notes 

64 ----- 

65 Most dimensions have exactly one governor dimension as a required 

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

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

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

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

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

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

72 results instead of straightforward error messages. 

73 

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

75 extent. 

76 

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

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

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

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

81 governor dimension records (instantiating them from information in the 

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

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

84 relationships would need to be reordered first. 

85 """ 

86 

87 def __init__( 

88 self, 

89 name: str, 

90 storage: dict, 

91 *, 

92 metadata: NamedValueAbstractSet[ddl.FieldSpec], 

93 uniqueKeys: NamedValueAbstractSet[ddl.FieldSpec], 

94 ): 

95 self._name = name 

96 self._storage = storage 

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

98 self._metadata = metadata 

99 self._uniqueKeys = uniqueKeys 

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

101 raise TypeError( 

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

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

104 ) 

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

106 raise TypeError( 

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

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

109 ) 

110 

111 MAX_KEY_LENGTH = 128 

112 

113 @property 

114 def name(self) -> str: 

115 # Docstring inherited from TopologicalRelationshipEndpoint. 

116 return self._name 

117 

118 @property 

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

120 # Docstring inherited from DimensionElement. 

121 return self._required 

122 

123 @property 

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

125 # Docstring inherited from DimensionElement. 

126 return NamedValueSet().freeze() 

127 

128 @property 

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

130 # Docstring inherited from TopologicalRelationshipEndpoint 

131 return MappingProxyType({}) 

132 

133 @property 

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

135 # Docstring inherited from DimensionElement. 

136 return self._metadata 

137 

138 @property 

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

140 # Docstring inherited from Dimension. 

141 return self._uniqueKeys 

142 

143 def makeStorage( 

144 self, 

145 db: Database, 

146 *, 

147 context: Optional[StaticTablesContext] = None, 

148 ) -> GovernorDimensionRecordStorage: 

149 """Make storage record. 

150 

151 Constructs the `DimensionRecordStorage` instance that should 

152 be used to back this element in a registry. 

153 

154 Parameters 

155 ---------- 

156 db : `Database` 

157 Interface to the underlying database engine and namespace. 

158 context : `StaticTablesContext`, optional 

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

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

161 

162 Returns 

163 ------- 

164 storage : `GovernorDimensionRecordStorage` 

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

166 """ 

167 from ...registry.interfaces import GovernorDimensionRecordStorage 

168 

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

170 assert issubclass(cls, GovernorDimensionRecordStorage) 

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

172 

173 

174class GovernorDimensionConstructionVisitor(DimensionConstructionVisitor): 

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

176 

177 Parameters 

178 ---------- 

179 name : `str` 

180 Name of the dimension. 

181 storage : `dict` 

182 Fully qualified name of the `GovernorDimensionRecordStorage` subclass 

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

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

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

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

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

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

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

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

191 to define unique constraints. 

192 """ 

193 

194 def __init__( 

195 self, 

196 name: str, 

197 storage: dict, 

198 *, 

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

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

201 ): 

202 super().__init__(name) 

203 self._storage = storage 

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

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

206 

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

208 # Docstring inherited from DimensionConstructionVisitor. 

209 return False 

210 

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

212 # Docstring inherited from DimensionConstructionVisitor. 

213 # Special handling for creating Dimension instances. 

214 dimension = GovernorDimension( 

215 self.name, 

216 storage=self._storage, 

217 metadata=self._metadata, 

218 uniqueKeys=self._uniqueKeys, 

219 ) 

220 builder.dimensions.add(dimension) 

221 builder.elements.add(dimension)