Hide keyboard shortcuts

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/>. 

21 

22from __future__ import annotations 

23 

24__all__ = ("DimensionRecord",) 

25 

26from typing import ( 

27 Any, 

28 ClassVar, 

29 Dict, 

30 Mapping, 

31 TYPE_CHECKING, 

32 Type, 

33) 

34 

35from ..timespan import Timespan 

36from .coordinate import DataCoordinate 

37from .elements import Dimension 

38 

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 

42 

43 

44def _reconstructDimensionRecord(definition: DimensionElement, *args: Any) -> DimensionRecord: 

45 """Unpickle implementation for `DimensionRecord` subclasses. 

46 

47 For internal use by `DimensionRecord`. 

48 """ 

49 return definition.RecordClass(*args) 

50 

51 

52def _makeTimespanFromRecord(record: DimensionRecord) -> Timespan[astropy.time.Time]: 

53 """Extract a `Timespan` object from the appropriate endpoint attributes. 

54 

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 ) 

62 

63 

64def _subclassDimensionRecord(definition: DimensionElement) -> Type[DimensionRecord]: 

65 """Create a dynamic subclass of `DimensionRecord` for the given 

66 `DimensionElement`. 

67 

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) 

78 

79 

80class DimensionRecord: 

81 """Base class for the Python representation of database records for 

82 a `DimensionElement`. 

83 

84 Parameters 

85 ---------- 

86 args 

87 Field values for this record, ordered to match ``__slots__``. 

88 

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. 

96 

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: 

100 

101 - ``definition`` is a class attribute that holds the `DimensionElement`; 

102 

103 - ``timespan`` is a property that returns a `Timespan`, present only 

104 on record classes that correspond to temporal elements. 

105 

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. 

109 

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. 

113 

114 `DimensionRecord` instances are immutable. 

115 """ 

116 

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",) 

121 

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) 

130 

131 @classmethod 

132 def fromDict(cls, mapping: Mapping[str, Any]) -> DimensionRecord: 

133 """Construct a `DimensionRecord` subclass instance from a mapping 

134 of field values. 

135 

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. 

144 

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) 

163 

164 def __reduce__(self) -> tuple: 

165 args = tuple(getattr(self, name) for name in self.__slots__) 

166 return (_reconstructDimensionRecord, (self.definition,) + args) 

167 

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__} 

172 

173 # Class attributes below are shadowed by instance attributes, and are 

174 # present just to hold the docstrings for those instance attributes. 

175 

176 dataId: DataCoordinate 

177 """A dict-like identifier for this record's primary keys 

178 (`DataCoordinate`). 

179 """ 

180 

181 definition: ClassVar[DimensionElement]