Coverage for python/lsst/daf/butler/core/dimensions/records.py : 34%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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__ = ("DimensionRecord",)
26from typing import (
27 Any,
28 ClassVar,
29 Dict,
30 Mapping,
31 TYPE_CHECKING,
32 Type,
33)
35from ..timespan import Timespan
36from .coordinate import DataCoordinate
37from .elements import Dimension
39if TYPE_CHECKING: # Imports needed only for type annotations; may be circular. 39 ↛ 40line 39 didn't jump to line 40, because the condition on line 39 was never true
40 import astropy.time
41 from .elements import DimensionElement
44def _reconstructDimensionRecord(definition: DimensionElement, *args: Any) -> DimensionRecord:
45 """Unpickle implementation for `DimensionRecord` subclasses.
47 For internal use by `DimensionRecord`.
48 """
49 return definition.RecordClass(*args)
52def _makeTimespanFromRecord(record: DimensionRecord) -> Timespan[astropy.time.Time]:
53 """Extract a `Timespan` object from the appropriate endpoint attributes.
55 For internal use by `DimensionRecord`.
56 """
57 from ..timespan import TIMESPAN_FIELD_SPECS
58 return Timespan(
59 begin=getattr(record, TIMESPAN_FIELD_SPECS.begin.name),
60 end=getattr(record, TIMESPAN_FIELD_SPECS.end.name),
61 )
64def _subclassDimensionRecord(definition: DimensionElement) -> Type[DimensionRecord]:
65 """Create a dynamic subclass of `DimensionRecord` for the given
66 `DimensionElement`.
68 For internal use by `DimensionRecord`.
69 """
70 from .schema import makeDimensionElementTableSpec
71 d = {
72 "definition": definition,
73 "__slots__": tuple(makeDimensionElementTableSpec(definition).fields.names)
74 }
75 if definition.temporal:
76 d["timespan"] = property(_makeTimespanFromRecord)
77 return type(definition.name + ".RecordClass", (DimensionRecord,), d)
80class DimensionRecord:
81 """Base class for the Python representation of database records for
82 a `DimensionElement`.
84 Parameters
85 ----------
86 args
87 Field values for this record, ordered to match ``__slots__``.
89 Notes
90 -----
91 `DimensionRecord` subclasses are created dynamically for each
92 `DimensionElement` in a `DimensionUniverse`, and are accessible via the
93 `DimensionElement.RecordClass` attribute. The `DimensionRecord` base class
94 itself is pure abstract, but does not use the `abc` module to indicate this
95 because it does not have overridable methods.
97 Record classes have attributes that correspond exactly to the fields in the
98 related database table, a few additional methods inherited from the
99 `DimensionRecord` base class, and two additional injected attributes:
101 - ``definition`` is a class attribute that holds the `DimensionElement`;
103 - ``timespan`` is a property that returns a `Timespan`, present only
104 on record classes that correspond to temporal elements.
106 The field attributes are defined via the ``__slots__`` mechanism, and the
107 ``__slots__`` tuple itself is considered the public interface for obtaining
108 the list of fields.
110 Instances are usually obtained from a `Registry`, but in the rare cases
111 where they are constructed directly in Python (usually for insertion into
112 a `Registry`), the `fromDict` method should generally be used.
114 `DimensionRecord` instances are immutable.
115 """
117 # Derived classes are required to define __slots__ as well, and it's those
118 # derived-class slots that other methods on the base class expect to see
119 # when they access self.__slots__.
120 __slots__ = ("dataId",)
122 def __init__(self, *args: Any):
123 for attrName, value in zip(self.__slots__, args):
124 object.__setattr__(self, attrName, value)
125 dataId = DataCoordinate(
126 self.definition.graph,
127 args[:len(self.definition.required.names)]
128 )
129 object.__setattr__(self, "dataId", dataId)
131 @classmethod
132 def fromDict(cls, mapping: Mapping[str, Any]) -> DimensionRecord:
133 """Construct a `DimensionRecord` subclass instance from a mapping
134 of field values.
136 Parameters
137 ----------
138 mapping : `~collections.abc.Mapping`
139 Field values, keyed by name. The keys must match those in
140 ``__slots__``, with the exception that a dimension name
141 may be used in place of the primary key name - for example,
142 "tract" may be used instead of "id" for the "id" primary key
143 field of the "tract" dimension.
145 Returns
146 -------
147 record : `DimensionRecord`
148 An instance of this subclass of `DimensionRecord`.
149 """
150 # If the name of the dimension is present in the given dict, use it
151 # as the primary key value instead of expecting the field name.
152 # For example, allow {"instrument": "HSC", ...} instead of
153 # {"name": "HSC", ...} when building a record for instrument dimension.
154 primaryKey = mapping.get(cls.definition.name)
155 d: Mapping[str, Any]
156 if primaryKey is not None and isinstance(cls.definition, Dimension):
157 d = dict(mapping)
158 d[cls.definition.primaryKey.name] = primaryKey
159 else:
160 d = mapping
161 values = tuple(d.get(k) for k in cls.__slots__)
162 return cls(*values)
164 def __reduce__(self) -> tuple:
165 args = tuple(getattr(self, name) for name in self.__slots__)
166 return (_reconstructDimensionRecord, (self.definition,) + args)
168 def toDict(self) -> Dict[str, Any]:
169 """Return a vanilla `dict` representation of this record.
170 """
171 return {name: getattr(self, name) for name in self.__slots__}
173 # Class attributes below are shadowed by instance attributes, and are
174 # present just to hold the docstrings for those instance attributes.
176 dataId: DataCoordinate
177 """A dict-like identifier for this record's primary keys
178 (`DataCoordinate`).
179 """
181 definition: ClassVar[DimensionElement]