Coverage for python/lsst/daf/butler/dimensions/construction.py: 51%
49 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-16 10:44 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-16 10:44 +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/>.
28from __future__ import annotations
30from abc import ABC, abstractmethod
31from collections.abc import Iterable, Set
32from typing import TYPE_CHECKING
34from .._named import NamedValueSet
35from .._topology import TopologicalFamily, TopologicalSpace
37if TYPE_CHECKING:
38 from ._config import DimensionConfig
39 from ._elements import Dimension, DimensionElement
42class DimensionConstructionVisitor(ABC):
43 """For adding entities to a builder class.
45 An abstract base class for adding one or more entities to a
46 `DimensionConstructionBuilder`.
48 Parameters
49 ----------
50 name : `str`
51 Name of an entity being added. This must be unique across all
52 entities, which include `DimensionElement`, `TopologicalFamily`, and
53 `DimensionPackerFactory` objects. The visitor may add other entities
54 as well, as long as only the named entity is referenced by other
55 entities in the universe.
56 """
58 def __init__(self, name: str):
59 self.name = name
61 def __str__(self) -> str:
62 return self.name
64 @abstractmethod
65 def hasDependenciesIn(self, others: Set[str]) -> bool:
66 """Test if dependencies have already been constructed.
68 Tests whether other entities this visitor depends on have already
69 been constructed.
71 Parameters
72 ----------
73 others : `~collections.abc.Set` [ `str` ]
74 The names of other visitors that have not yet been invoked.
76 Returns
77 -------
78 blocked : `bool`
79 If `True`, this visitor has dependencies on other visitors that
80 must be invoked before this one can be. If `False`, `visit` may
81 be called.
82 """
83 raise NotImplementedError()
85 @abstractmethod
86 def visit(self, builder: DimensionConstructionBuilder) -> None:
87 """Modify the given builder object to include responsible entities.
89 Parameters
90 ----------
91 builder : `DimensionConstructionBuilder`
92 Builder to modify in-place and from which dependencies can be
93 obtained.
95 Notes
96 -----
97 Subclasses may assume (and callers must guarantee) that
98 `hasDependenciesIn` would return `False` prior to `visit` being
99 called.
100 """
101 raise NotImplementedError()
104class DimensionConstructionBuilder:
105 """A builder object for constructing `DimensionUniverse` instances.
107 `DimensionConstructionVisitor` objects can be added to a
108 `DimensionConstructionBuilder` object in any order, and are invoked
109 in a deterministic order consistent with their dependency relationships
110 by a single call (by the `DimensionUniverse`) to the `finish` method.
112 Parameters
113 ----------
114 version : `int`
115 Version for the `DimensionUniverse`.
116 commonSkyPixName : `str`
117 Name of the "common" skypix dimension that is used to relate all other
118 spatial `TopologicalRelationshipEndpoint` objects.
119 config : `DimensionConfig`
120 The dimension universe to be used.
121 namespace : `str`, optional
122 The namespace to assign to this universe.
123 visitors : `~collections.abc.Iterable` [ `DimensionConstructionVisitor` ]
124 Visitor instances to include from the start.
125 """
127 def __init__(
128 self,
129 version: int,
130 commonSkyPixName: str,
131 config: DimensionConfig,
132 *,
133 namespace: str | None = None,
134 visitors: Iterable[DimensionConstructionVisitor] = (),
135 ) -> None:
136 self.dimensions = NamedValueSet()
137 self.elements = NamedValueSet()
138 self.topology = {space: NamedValueSet() for space in TopologicalSpace.__members__.values()}
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}
145 def add(self, visitor: DimensionConstructionVisitor) -> None:
146 """Add a single visitor to the builder.
148 Parameters
149 ----------
150 visitor : `DimensionConstructionVisitor`
151 Visitor instance to add.
152 """
153 self._todo[visitor.name] = visitor
155 def update(self, visitors: Iterable[DimensionConstructionVisitor]) -> None:
156 """Add multiple visitors to the builder.
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)
166 def finish(self) -> None:
167 """Complete construction of the builder.
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)
185 version: int
186 """Version number for the `DimensionUniverse` (`int`).
188 Populated at builder construction.
189 """
191 namespace: str | None
192 """Namespace for the `DimensionUniverse` (`str`)
194 Populated at builder construction.
195 """
197 commonSkyPixName: str
198 """Name of the common skypix dimension used to connect other spatial
199 `TopologicalRelationshipEndpoint` objects (`str`).
201 Populated at builder construction.
202 """
204 dimensions: NamedValueSet[Dimension]
205 """Set of all `Dimension` objects (`NamedValueSet` [ `Dimension` ]).
207 Populated by `finish`.
208 """
210 elements: NamedValueSet[DimensionElement]
211 """Set of all `DimensionElement` objects
212 (`NamedValueSet` [ `DimensionElement` ]).
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 """
219 topology: dict[TopologicalSpace, NamedValueSet[TopologicalFamily]]
220 """Dictionary containing all `TopologicalFamily` objects
221 (`dict` [ `TopologicalSpace`, `NamedValueSet` [ `TopologicalFamily` ] ] ).
222 """