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