Coverage for python/lsst/daf/butler/core/dimensions/_governor.py: 58%
58 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-14 19:21 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-14 19:21 +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/>.
22from __future__ import annotations
24__all__ = ("GovernorDimension",)
26from collections.abc import Iterable, Mapping, Set
27from types import MappingProxyType
28from typing import TYPE_CHECKING
30from lsst.utils import doImportType
32from .. import ddl
33from .._topology import TopologicalFamily, TopologicalSpace
34from ..named import NamedValueAbstractSet, NamedValueSet
35from ._elements import Dimension
36from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor
38if TYPE_CHECKING:
39 from ...registry.interfaces import Database, GovernorDimensionRecordStorage, StaticTablesContext
42class GovernorDimension(Dimension):
43 """Governor dimension.
45 A special `Dimension` with no dependencies and a small number of rows,
46 used to group the dimensions that depend on it.
48 Parameters
49 ----------
50 name : `str`
51 Name of the dimension.
52 storage : `dict`
53 Fully qualified name of the `GovernorDimensionRecordStorage` subclass
54 that will back this element in the registry (in a "cls" key) along
55 with any other construction keyword arguments (in other keys).
56 metadata : `NamedValueAbstractSet` [ `ddl.FieldSpec` ]
57 Field specifications for all non-key fields in this dimension's table.
58 uniqueKeys : `NamedValueAbstractSet` [ `ddl.FieldSpec` ]
59 Fields that can each be used to uniquely identify this dimension (given
60 values for all required dimensions). The first of these is used as
61 (part of) this dimension's table's primary key, while others are used
62 to define unique constraints.
64 Notes
65 -----
66 Most dimensions have exactly one governor dimension as a required
67 dependency, and queries that involve those dimensions are always expected
68 to explicitly identify the governor dimension value(s), rather than
69 retrieve all matches from the database. Because governor values are thus
70 almost always known at query-generation time, they can be used there to
71 simplify queries, provide sensible defaults, or check in advance for common
72 mistakes that might otherwise yield confusing (albeit formally correct)
73 results instead of straightforward error messages.
75 Governor dimensions may not be associated with any kind of topological
76 extent.
78 Governor dimension rows are often affiliated with a Python class or
79 instance (e.g. `lsst.obs.base.Instrument`) that is capable of generating
80 the rows of at least some dependent dimensions or providing other related
81 functionality. In the future, we hope to attach these instances to
82 governor dimension records (instantiating them from information in the
83 database row when it is fetched), and use those objects to add additional
84 functionality to governor dimensions, but a number of (code) dependency
85 relationships would need to be reordered first.
86 """
88 def __init__(
89 self,
90 name: str,
91 storage: dict,
92 *,
93 metadata: NamedValueAbstractSet[ddl.FieldSpec],
94 uniqueKeys: NamedValueAbstractSet[ddl.FieldSpec],
95 ):
96 self._name = name
97 self._storage = storage
98 self._required = NamedValueSet({self}).freeze()
99 self._metadata = metadata
100 self._uniqueKeys = uniqueKeys
101 if self.primaryKey.getPythonType() is not str:
102 raise TypeError(
103 f"Governor dimension '{name}' must have a string primary key (configured type "
104 f"is {self.primaryKey.dtype.__name__})."
105 )
106 if self.primaryKey.length is not None and self.primaryKey.length > self.MAX_KEY_LENGTH:
107 raise TypeError(
108 f"Governor dimension '{name}' must have a string primary key with length <= "
109 f"{self.MAX_KEY_LENGTH} (configured value is {self.primaryKey.length})."
110 )
112 MAX_KEY_LENGTH = 128
114 @property
115 def name(self) -> str:
116 # Docstring inherited from TopologicalRelationshipEndpoint.
117 return self._name
119 @property
120 def required(self) -> NamedValueAbstractSet[Dimension]:
121 # Docstring inherited from DimensionElement.
122 return self._required
124 @property
125 def implied(self) -> NamedValueAbstractSet[Dimension]:
126 # Docstring inherited from DimensionElement.
127 return NamedValueSet().freeze()
129 @property
130 def topology(self) -> Mapping[TopologicalSpace, TopologicalFamily]:
131 # Docstring inherited from TopologicalRelationshipEndpoint
132 return MappingProxyType({})
134 @property
135 def metadata(self) -> NamedValueAbstractSet[ddl.FieldSpec]:
136 # Docstring inherited from DimensionElement.
137 return self._metadata
139 @property
140 def uniqueKeys(self) -> NamedValueAbstractSet[ddl.FieldSpec]:
141 # Docstring inherited from Dimension.
142 return self._uniqueKeys
144 def makeStorage(
145 self,
146 db: Database,
147 *,
148 context: StaticTablesContext | None = None,
149 ) -> GovernorDimensionRecordStorage:
150 """Make storage record.
152 Constructs the `DimensionRecordStorage` instance that should
153 be used to back this element in a registry.
155 Parameters
156 ----------
157 db : `Database`
158 Interface to the underlying database engine and namespace.
159 context : `StaticTablesContext`, optional
160 If provided, an object to use to create any new tables. If not
161 provided, ``db.ensureTableExists`` should be used instead.
163 Returns
164 -------
165 storage : `GovernorDimensionRecordStorage`
166 Storage object that should back this element in a registry.
167 """
168 from ...registry.interfaces import GovernorDimensionRecordStorage
170 cls = doImportType(self._storage["cls"])
171 assert issubclass(cls, GovernorDimensionRecordStorage)
172 return cls.initialize(db, self, context=context, config=self._storage)
175class GovernorDimensionConstructionVisitor(DimensionConstructionVisitor):
176 """A construction visitor for `GovernorDimension`.
178 Parameters
179 ----------
180 name : `str`
181 Name of the dimension.
182 storage : `dict`
183 Fully qualified name of the `GovernorDimensionRecordStorage` subclass
184 that will back this element in the registry (in a "cls" key) along
185 with any other construction keyword arguments (in other keys).
186 metadata : `~collections.abc.Iterable` [ `ddl.FieldSpec` ]
187 Field specifications for all non-key fields in this element's table.
188 uniqueKeys : `~collections.abc.Iterable` [ `ddl.FieldSpec` ]
189 Fields that can each be used to uniquely identify this dimension (given
190 values for all required dimensions). The first of these is used as
191 (part of) this dimension's table's primary key, while others are used
192 to define unique constraints.
193 """
195 def __init__(
196 self,
197 name: str,
198 storage: dict,
199 *,
200 metadata: Iterable[ddl.FieldSpec] = (),
201 uniqueKeys: Iterable[ddl.FieldSpec] = (),
202 ):
203 super().__init__(name)
204 self._storage = storage
205 self._metadata = NamedValueSet(metadata).freeze()
206 self._uniqueKeys = NamedValueSet(uniqueKeys).freeze()
208 def hasDependenciesIn(self, others: Set[str]) -> bool:
209 # Docstring inherited from DimensionConstructionVisitor.
210 return False
212 def visit(self, builder: DimensionConstructionBuilder) -> None:
213 # Docstring inherited from DimensionConstructionVisitor.
214 # Special handling for creating Dimension instances.
215 dimension = GovernorDimension(
216 self.name,
217 storage=self._storage,
218 metadata=self._metadata,
219 uniqueKeys=self._uniqueKeys,
220 )
221 builder.dimensions.add(dimension)
222 builder.elements.add(dimension)