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

52 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-21 09:55 +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 

24from abc import ABC, abstractmethod 

25from collections.abc import Iterable, Set 

26from typing import TYPE_CHECKING 

27 

28from .._topology import TopologicalFamily, TopologicalSpace 

29from ..named import NamedValueSet 

30 

31if TYPE_CHECKING: 

32 from ._config import DimensionConfig 

33 from ._elements import Dimension, DimensionElement 

34 from ._packer import DimensionPackerFactory 

35 

36 

37class DimensionConstructionVisitor(ABC): 

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

39 

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

41 `DimensionConstructionBuilder`. 

42 

43 Parameters 

44 ---------- 

45 name : `str` 

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

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

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

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

50 entities in the universe. 

51 """ 

52 

53 def __init__(self, name: str): 

54 self.name = name 

55 

56 def __str__(self) -> str: 

57 return self.name 

58 

59 @abstractmethod 

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

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

62 

63 Tests whether other entities this visitor depends on have already 

64 been constructed. 

65 

66 Parameters 

67 ---------- 

68 others : `~collections.abc.Set` [ `str` ] 

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

70 

71 Returns 

72 ------- 

73 blocked : `bool` 

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

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

76 be called. 

77 """ 

78 raise NotImplementedError() 

79 

80 @abstractmethod 

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

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

83 

84 Parameters 

85 ---------- 

86 builder : `DimensionConstructionBuilder` 

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

88 obtained. 

89 

90 Notes 

91 ----- 

92 Subclasses may assume (and callers must guarantee) that 

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

94 called. 

95 """ 

96 raise NotImplementedError() 

97 

98 

99class DimensionConstructionBuilder: 

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

101 

102 `DimensionConstructionVisitor` objects can be added to a 

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

104 in a deterministic order consistent with their dependency relationships 

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

106 

107 Parameters 

108 ---------- 

109 version : `int` 

110 Version for the `DimensionUniverse`. 

111 commonSkyPixName : `str` 

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

113 spatial `TopologicalRelationshipEndpoint` objects. 

114 namespace : `str`, optional 

115 The namespace to assign to this universe. 

116 visitors : `~collections.abc.Iterable` [ `DimensionConstructionVisitor` ] 

117 Visitor instances to include from the start. 

118 """ 

119 

120 def __init__( 

121 self, 

122 version: int, 

123 commonSkyPixName: str, 

124 config: DimensionConfig, 

125 *, 

126 namespace: str | None = None, 

127 visitors: Iterable[DimensionConstructionVisitor] = (), 

128 ) -> None: 

129 self.dimensions = NamedValueSet() 

130 self.elements = NamedValueSet() 

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

132 self.packers = {} 

133 self.version = version 

134 self.namespace = namespace 

135 self.config = config 

136 self.commonSkyPixName = commonSkyPixName 

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

138 

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

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

141 

142 Parameters 

143 ---------- 

144 visitor : `DimensionConstructionVisitor` 

145 Visitor instance to add. 

146 """ 

147 self._todo[visitor.name] = visitor 

148 

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

150 """Add multiple visitors to the builder. 

151 

152 Parameters 

153 ---------- 

154 visitors : `~collections.abc.Iterable` \ 

155 [ `DimensionConstructionVisitor` ] 

156 Visitor instances to add. 

157 """ 

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

159 

160 def finish(self) -> None: 

161 """Complete construction of the builder. 

162 

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

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

165 called only once, by `DimensionUniverse` itself. 

166 """ 

167 while self._todo: 

168 unblocked = [ 

169 name 

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

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

172 ] 

173 unblocked.sort() # Break ties lexicographically. 

174 if not unblocked: 

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

176 for name in unblocked: 

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

178 

179 version: int 

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

181 

182 Populated at builder construction. 

183 """ 

184 

185 namespace: str | None 

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

187 

188 Populated at builder construction. 

189 """ 

190 

191 commonSkyPixName: str 

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

193 `TopologicalRelationshipEndpoint` objects (`str`). 

194 

195 Populated at builder construction. 

196 """ 

197 

198 dimensions: NamedValueSet[Dimension] 

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

200 

201 Populated by `finish`. 

202 """ 

203 

204 elements: NamedValueSet[DimensionElement] 

205 """Set of all `DimensionElement` objects 

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

207 

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

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

210 set as well as `dimensions`. 

211 """ 

212 

213 topology: dict[TopologicalSpace, NamedValueSet[TopologicalFamily]] 

214 """Dictionary containing all `TopologicalFamily` objects 

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

216 """ 

217 

218 packers: dict[str, DimensionPackerFactory] 

219 """Dictionary containing all `DimensionPackerFactory` objects 

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

221 """