Coverage for python/lsst/daf/butler/dimensions/_governor.py: 60%
58 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
30__all__ = ("GovernorDimension",)
32from collections.abc import Iterable, Mapping, Set
33from types import MappingProxyType
35from .._named import NamedValueAbstractSet, NamedValueSet
36from .._topology import TopologicalFamily, TopologicalSpace
37from ._elements import Dimension, KeyColumnSpec, MetadataColumnSpec
38from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor
41class GovernorDimension(Dimension):
42 """Governor dimension.
44 A special `Dimension` with no dependencies and a small number of rows,
45 used to group the dimensions that depend on it.
47 Parameters
48 ----------
49 name : `str`
50 Name of the dimension.
51 storage : `dict`
52 Fully qualified name of the `GovernorDimensionRecordStorage` subclass
53 that will back this element in the registry (in a "cls" key) along
54 with any other construction keyword arguments (in other keys).
55 metadata_columns : `NamedValueAbstractSet` [ `MetadataColumnSpec` ]
56 Field specifications for all non-key fields in this dimension's table.
57 unique_keys : `NamedValueAbstractSet` [ `KeyColumnSpec` ]
58 Fields that can each be used to uniquely identify this dimension (given
59 values for all required dimensions). The first of these is used as
60 (part of) this dimension's table's primary key, while others are used
61 to define unique constraints.
62 doc : `str`
63 Extended description of this element.
65 Notes
66 -----
67 Most dimensions have exactly one governor dimension as a required
68 dependency, and queries that involve those dimensions are always expected
69 to explicitly identify the governor dimension value(s), rather than
70 retrieve all matches from the database. Because governor values are thus
71 almost always known at query-generation time, they can be used there to
72 simplify queries, provide sensible defaults, or check in advance for common
73 mistakes that might otherwise yield confusing (albeit formally correct)
74 results instead of straightforward error messages.
76 Governor dimensions may not be associated with any kind of topological
77 extent.
79 Governor dimension rows are often affiliated with a Python class or
80 instance (e.g. `lsst.obs.base.Instrument`) that is capable of generating
81 the rows of at least some dependent dimensions or providing other related
82 functionality. In the future, we hope to attach these instances to
83 governor dimension records (instantiating them from information in the
84 database row when it is fetched), and use those objects to add additional
85 functionality to governor dimensions, but a number of (code) dependency
86 relationships would need to be reordered first.
87 """
89 def __init__(
90 self,
91 name: str,
92 storage: dict,
93 *,
94 metadata_columns: NamedValueAbstractSet[MetadataColumnSpec],
95 unique_keys: NamedValueAbstractSet[KeyColumnSpec],
96 doc: str,
97 ):
98 self._name = name
99 self._storage = storage
100 self._required = NamedValueSet({self}).freeze()
101 self._metadata_columns = metadata_columns
102 self._unique_keys = unique_keys
103 self._doc = doc
104 if self.primaryKey.getPythonType() is not str:
105 raise TypeError(
106 f"Governor dimension '{name}' must have a string primary key (configured type "
107 f"is {self.primaryKey.dtype.__name__})."
108 )
109 if self.primaryKey.length is not None and self.primaryKey.length > self.MAX_KEY_LENGTH:
110 raise TypeError(
111 f"Governor dimension '{name}' must have a string primary key with length <= "
112 f"{self.MAX_KEY_LENGTH} (configured value is {self.primaryKey.length})."
113 )
115 MAX_KEY_LENGTH = 128
117 @property
118 def name(self) -> str:
119 # Docstring inherited from TopologicalRelationshipEndpoint.
120 return self._name
122 @property
123 def required(self) -> NamedValueAbstractSet[Dimension]:
124 # Docstring inherited from DimensionElement.
125 return self._required
127 @property
128 def implied(self) -> NamedValueAbstractSet[Dimension]:
129 # Docstring inherited from DimensionElement.
130 return NamedValueSet().freeze()
132 @property
133 def topology(self) -> Mapping[TopologicalSpace, TopologicalFamily]:
134 # Docstring inherited from TopologicalRelationshipEndpoint
135 return MappingProxyType({})
137 @property
138 def metadata_columns(self) -> NamedValueAbstractSet[MetadataColumnSpec]:
139 # Docstring inherited from DimensionElement.
140 return self._metadata_columns
142 @property
143 def unique_keys(self) -> NamedValueAbstractSet[KeyColumnSpec]:
144 # Docstring inherited from Dimension.
145 return self._unique_keys
147 @property
148 def is_cached(self) -> bool:
149 # Docstring inherited.
150 return True
152 @property
153 def documentation(self) -> str:
154 # Docstring inherited from DimensionElement.
155 return self._doc
158class GovernorDimensionConstructionVisitor(DimensionConstructionVisitor):
159 """A construction visitor for `GovernorDimension`.
161 Parameters
162 ----------
163 name : `str`
164 Name of the dimension.
165 storage : `dict`
166 Fully qualified name of the `GovernorDimensionRecordStorage` subclass
167 that will back this element in the registry (in a "cls" key) along
168 with any other construction keyword arguments (in other keys).
169 metadata_columns : `~collections.abc.Iterable` [ `MetadataColumnSpec` ]
170 Field specifications for all non-key fields in this element's table.
171 unique_keys : `~collections.abc.Iterable` [ `KeyColumnSpec` ]
172 Fields that can each be used to uniquely identify this dimension (given
173 values for all required dimensions). The first of these is used as
174 (part of) this dimension's table's primary key, while others are used
175 to define unique constraints.
176 doc : `str`
177 Extended description of this element.
178 """
180 def __init__(
181 self,
182 name: str,
183 storage: dict,
184 *,
185 metadata_columns: Iterable[MetadataColumnSpec] = (),
186 unique_keys: Iterable[KeyColumnSpec] = (),
187 doc: str,
188 ):
189 super().__init__(name)
190 self._storage = storage
191 self._metadata_columns = NamedValueSet(metadata_columns).freeze()
192 self._unique_keys = NamedValueSet(unique_keys).freeze()
193 self._doc = doc
195 def hasDependenciesIn(self, others: Set[str]) -> bool:
196 # Docstring inherited from DimensionConstructionVisitor.
197 return False
199 def visit(self, builder: DimensionConstructionBuilder) -> None:
200 # Docstring inherited from DimensionConstructionVisitor.
201 # Special handling for creating Dimension instances.
202 dimension = GovernorDimension(
203 self.name,
204 storage=self._storage,
205 metadata_columns=self._metadata_columns,
206 unique_keys=self._unique_keys,
207 doc=self._doc,
208 )
209 builder.dimensions.add(dimension)
210 builder.elements.add(dimension)