Coverage for python/lsst/daf/butler/core/dimensions/construction.py: 45%

57 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-04 02:04 -0800

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 

24from abc import ABC, abstractmethod 

25from typing import TYPE_CHECKING, AbstractSet, Dict, Iterable, Optional 

26 

27from .._topology import TopologicalFamily, TopologicalSpace 

28from ..named import NamedValueSet 

29 

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

31 from ._config import DimensionConfig 

32 from ._elements import Dimension, DimensionElement 

33 from ._packer import DimensionPackerFactory 

34 

35 

36class DimensionConstructionVisitor(ABC): 

37 """For adding entities to a builder class. 

38 

39 An abstract base class for adding one or more entities to a 

40 `DimensionConstructionBuilder`. 

41 

42 Parameters 

43 ---------- 

44 name : `str` 

45 Name of an entity being added. This must be unique across all 

46 entities, which include `DimensionElement`, `TopologicalFamily`, and 

47 `DimensionPackerFactory` objects. The visitor may add other entities 

48 as well, as long as only the named entity is referenced by other 

49 entities in the universe. 

50 """ 

51 

52 def __init__(self, name: str): 

53 self.name = name 

54 

55 def __str__(self) -> str: 

56 return self.name 

57 

58 @abstractmethod 

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

60 """Test if dependencies have already been constructed. 

61 

62 Tests whether other entities this visitor depends on have already 

63 been constructed. 

64 

65 Parameters 

66 ---------- 

67 others : `AbstractSet` [ `str` ] 

68 The names of other visitors that have not yet been invoked. 

69 

70 Returns 

71 ------- 

72 blocked : `bool` 

73 If `True`, this visitor has dependencies on other visitors that 

74 must be invoked before this one can be. If `False`, `visit` may 

75 be called. 

76 """ 

77 raise NotImplementedError() 

78 

79 @abstractmethod 

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

81 """Modify the given builder object to include responsible entities. 

82 

83 Parameters 

84 ---------- 

85 builder : `DimensionConstructionBuilder` 

86 Builder to modify in-place and from which dependencies can be 

87 obtained. 

88 

89 Notes 

90 ----- 

91 Subclasses may assume (and callers must guarantee) that 

92 `hasDependenciesIn` would return `False` prior to `visit` being 

93 called. 

94 """ 

95 raise NotImplementedError() 

96 

97 

98class DimensionConstructionBuilder: 

99 """A builder object for constructing `DimensionUniverse` instances. 

100 

101 `DimensionConstructionVisitor` objects can be added to a 

102 `DimensionConstructionBuilder` object in any order, and are invoked 

103 in a deterministic order consistent with their dependency relationships 

104 by a single call (by the `DimensionUniverse`) to the `finish` method. 

105 

106 Parameters 

107 ---------- 

108 version : `int` 

109 Version for the `DimensionUniverse`. 

110 commonSkyPixName : `str` 

111 Name of the "common" skypix dimension that is used to relate all other 

112 spatial `TopologicalRelationshipEndpoint` objects. 

113 namespace : `str`, optional 

114 The namespace to assign to this universe. 

115 visitors : `Iterable` [ `DimensionConstructionVisitor` ] 

116 Visitor instances to include from the start. 

117 """ 

118 

119 def __init__( 

120 self, 

121 version: int, 

122 commonSkyPixName: str, 

123 config: DimensionConfig, 

124 *, 

125 namespace: Optional[str] = None, 

126 visitors: Iterable[DimensionConstructionVisitor] = (), 

127 ) -> None: 

128 self.dimensions = NamedValueSet() 

129 self.elements = NamedValueSet() 

130 self.topology = {space: NamedValueSet() for space in TopologicalSpace.__members__.values()} 

131 self.packers = {} 

132 self.version = version 

133 self.namespace = namespace 

134 self.config = config 

135 self.commonSkyPixName = commonSkyPixName 

136 self._todo: Dict[str, DimensionConstructionVisitor] = {v.name: v for v in visitors} 

137 

138 def add(self, visitor: DimensionConstructionVisitor) -> None: 

139 """Add a single visitor to the builder. 

140 

141 Parameters 

142 ---------- 

143 visitor : `DimensionConstructionVisitor` 

144 Visitor instance to add. 

145 """ 

146 self._todo[visitor.name] = visitor 

147 

148 def update(self, visitors: Iterable[DimensionConstructionVisitor]) -> None: 

149 """Add multiple visitors to the builder. 

150 

151 Parameters 

152 ---------- 

153 visitors : `Iterable` [ `DimensionConstructionVisitor` ] 

154 Visitor instances to add. 

155 """ 

156 self._todo.update((v.name, v) for v in visitors) 

157 

158 def finish(self) -> None: 

159 """Complete construction of the builder. 

160 

161 This method invokes all visitors in an order consistent with their 

162 dependencies, fully populating all public attributes. It should be 

163 called only once, by `DimensionUniverse` itself. 

164 """ 

165 while self._todo: 

166 unblocked = [ 

167 name 

168 for name, visitor in self._todo.items() 

169 if not visitor.hasDependenciesIn(self._todo.keys()) 

170 ] 

171 unblocked.sort() # Break ties lexicographically. 

172 if not unblocked: 

173 raise RuntimeError(f"Cycle or unmet dependency in dimension elements: {self._todo.keys()}.") 

174 for name in unblocked: 

175 self._todo.pop(name).visit(self) 

176 

177 version: int 

178 """Version number for the `DimensionUniverse` (`int`). 

179 

180 Populated at builder construction. 

181 """ 

182 

183 namespace: Optional[str] 

184 """Namespace for the `DimensionUniverse` (`str`) 

185 

186 Populated at builder construction. 

187 """ 

188 

189 commonSkyPixName: str 

190 """Name of the common skypix dimension used to connect other spatial 

191 `TopologicalRelationshipEndpoint` objects (`str`). 

192 

193 Populated at builder construction. 

194 """ 

195 

196 dimensions: NamedValueSet[Dimension] 

197 """Set of all `Dimension` objects (`NamedValueSet` [ `Dimension` ]). 

198 

199 Populated by `finish`. 

200 """ 

201 

202 elements: NamedValueSet[DimensionElement] 

203 """Set of all `DimensionElement` objects 

204 (`NamedValueSet` [ `DimensionElement` ]). 

205 

206 Populated by `finish`. `DimensionConstructionVisitor` classes that 

207 construct `Dimension` objects are responsible for adding them to this 

208 set as well as `dimensions`. 

209 """ 

210 

211 topology: Dict[TopologicalSpace, NamedValueSet[TopologicalFamily]] 

212 """Dictionary containing all `TopologicalFamily` objects 

213 (`dict` [ `TopologicalSpace`, `NamedValueSet` [ `TopologicalFamily` ] ] ). 

214 """ 

215 

216 packers: Dict[str, DimensionPackerFactory] 

217 """Dictionary containing all `DimensionPackerFactory` objects 

218 (`dict` [ `str`, `DimensionPackerFactory` ] ). 

219 """