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

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