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

52 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-10-02 08:00 +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 software is dual licensed under the GNU General Public License and also 

10# under a 3-clause BSD license. Recipients may choose which of these licenses 

11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, 

12# respectively. If you choose the GPL option then the following text applies 

13# (but note that there is still no warranty even if you opt for BSD instead): 

14# 

15# This program is free software: you can redistribute it and/or modify 

16# it under the terms of the GNU General Public License as published by 

17# the Free Software Foundation, either version 3 of the License, or 

18# (at your option) any later version. 

19# 

20# This program is distributed in the hope that it will be useful, 

21# but WITHOUT ANY WARRANTY; without even the implied warranty of 

22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

23# GNU General Public License for more details. 

24# 

25# You should have received a copy of the GNU General Public License 

26# along with this program. If not, see <http://www.gnu.org/licenses/>. 

27 

28from __future__ import annotations 

29 

30from abc import ABC, abstractmethod 

31from collections.abc import Iterable, Set 

32from typing import TYPE_CHECKING 

33 

34from .._topology import TopologicalFamily, TopologicalSpace 

35from ..named import NamedValueSet 

36 

37if TYPE_CHECKING: 

38 from ._config import DimensionConfig 

39 from ._elements import Dimension, DimensionElement 

40 from ._packer import DimensionPackerFactory 

41 

42 

43class DimensionConstructionVisitor(ABC): 

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

45 

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

47 `DimensionConstructionBuilder`. 

48 

49 Parameters 

50 ---------- 

51 name : `str` 

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

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

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

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

56 entities in the universe. 

57 """ 

58 

59 def __init__(self, name: str): 

60 self.name = name 

61 

62 def __str__(self) -> str: 

63 return self.name 

64 

65 @abstractmethod 

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

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

68 

69 Tests whether other entities this visitor depends on have already 

70 been constructed. 

71 

72 Parameters 

73 ---------- 

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

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

76 

77 Returns 

78 ------- 

79 blocked : `bool` 

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

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

82 be called. 

83 """ 

84 raise NotImplementedError() 

85 

86 @abstractmethod 

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

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

89 

90 Parameters 

91 ---------- 

92 builder : `DimensionConstructionBuilder` 

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

94 obtained. 

95 

96 Notes 

97 ----- 

98 Subclasses may assume (and callers must guarantee) that 

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

100 called. 

101 """ 

102 raise NotImplementedError() 

103 

104 

105class DimensionConstructionBuilder: 

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

107 

108 `DimensionConstructionVisitor` objects can be added to a 

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

110 in a deterministic order consistent with their dependency relationships 

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

112 

113 Parameters 

114 ---------- 

115 version : `int` 

116 Version for the `DimensionUniverse`. 

117 commonSkyPixName : `str` 

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

119 spatial `TopologicalRelationshipEndpoint` objects. 

120 namespace : `str`, optional 

121 The namespace to assign to this universe. 

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

123 Visitor instances to include from the start. 

124 """ 

125 

126 def __init__( 

127 self, 

128 version: int, 

129 commonSkyPixName: str, 

130 config: DimensionConfig, 

131 *, 

132 namespace: str | None = None, 

133 visitors: Iterable[DimensionConstructionVisitor] = (), 

134 ) -> None: 

135 self.dimensions = NamedValueSet() 

136 self.elements = NamedValueSet() 

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

138 self.packers = {} 

139 self.version = version 

140 self.namespace = namespace 

141 self.config = config 

142 self.commonSkyPixName = commonSkyPixName 

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

144 

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

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

147 

148 Parameters 

149 ---------- 

150 visitor : `DimensionConstructionVisitor` 

151 Visitor instance to add. 

152 """ 

153 self._todo[visitor.name] = visitor 

154 

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

156 """Add multiple visitors to the builder. 

157 

158 Parameters 

159 ---------- 

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

161 [ `DimensionConstructionVisitor` ] 

162 Visitor instances to add. 

163 """ 

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

165 

166 def finish(self) -> None: 

167 """Complete construction of the builder. 

168 

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

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

171 called only once, by `DimensionUniverse` itself. 

172 """ 

173 while self._todo: 

174 unblocked = [ 

175 name 

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

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

178 ] 

179 unblocked.sort() # Break ties lexicographically. 

180 if not unblocked: 

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

182 for name in unblocked: 

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

184 

185 version: int 

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

187 

188 Populated at builder construction. 

189 """ 

190 

191 namespace: str | None 

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

193 

194 Populated at builder construction. 

195 """ 

196 

197 commonSkyPixName: str 

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

199 `TopologicalRelationshipEndpoint` objects (`str`). 

200 

201 Populated at builder construction. 

202 """ 

203 

204 dimensions: NamedValueSet[Dimension] 

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

206 

207 Populated by `finish`. 

208 """ 

209 

210 elements: NamedValueSet[DimensionElement] 

211 """Set of all `DimensionElement` objects 

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

213 

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

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

216 set as well as `dimensions`. 

217 """ 

218 

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

220 """Dictionary containing all `TopologicalFamily` objects 

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

222 """ 

223 

224 packers: dict[str, DimensionPackerFactory] 

225 """Dictionary containing all `DimensionPackerFactory` objects 

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

227 """