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 Tuple, 

32 TYPE_CHECKING, 

33 Type, 

34) 

35 

36from ..timespan import Timespan 

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 from .coordinate import DataCoordinate 

43 

44 

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

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

47 

48 For internal use by `DimensionRecord`. 

49 """ 

50 return definition.RecordClass(*args) 

51 

52 

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

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

55 

56 For internal use by `DimensionRecord`. 

57 """ 

58 from ..timespan import TIMESPAN_FIELD_SPECS 

59 return Timespan( 

60 begin=getattr(record, TIMESPAN_FIELD_SPECS.begin.name), 

61 end=getattr(record, TIMESPAN_FIELD_SPECS.end.name), 

62 ) 

63 

64 

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

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

67 `DimensionElement`. 

68 

69 For internal use by `DimensionRecord`. 

70 """ 

71 from .schema import makeDimensionElementTableSpec 

72 fields = tuple(makeDimensionElementTableSpec(definition).fields.names) 

73 d = { 

74 "definition": definition, 

75 "__slots__": fields, 

76 "fields": fields, 

77 } 

78 if definition.temporal: 

79 d["timespan"] = property(_makeTimespanFromRecord) 

80 return type(definition.name + ".RecordClass", (DimensionRecord,), d) 

81 

82 

83class DimensionRecord: 

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

85 a `DimensionElement`. 

86 

87 Parameters 

88 ---------- 

89 args 

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

91 

92 Notes 

93 ----- 

94 `DimensionRecord` subclasses are created dynamically for each 

95 `DimensionElement` in a `DimensionUniverse`, and are accessible via the 

96 `DimensionElement.RecordClass` attribute. The `DimensionRecord` base class 

97 itself is pure abstract, but does not use the `abc` module to indicate this 

98 because it does not have overridable methods. 

99 

100 Record classes have attributes that correspond exactly to the fields in the 

101 related database table, a few additional methods inherited from the 

102 `DimensionRecord` base class, and two additional injected attributes: 

103 

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

105 

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

107 on record classes that correspond to temporal elements. 

108 

109 The field attributes are defined via the ``__slots__`` mechanism, and the 

110 ``__slots__`` tuple itself is considered the public interface for obtaining 

111 the list of fields. 

112 

113 Instances are usually obtained from a `Registry`, but in the rare cases 

114 where they are constructed directly in Python (usually for insertion into 

115 a `Registry`), the `fromDict` method should generally be used. 

116 

117 `DimensionRecord` instances are immutable. 

118 """ 

119 

120 # Derived classes are required to define __slots__ as well, and it's those 

121 # derived-class slots that other methods on the base class expect to see 

122 # when they access self.__slots__. 

123 __slots__ = ("dataId",) 

124 

125 def __init__(self, *args: Any): 

126 for attrName, value in zip(self.__slots__, args): 

127 object.__setattr__(self, attrName, value) 

128 from .coordinate import DataCoordinate 

129 if self.definition.required.names == self.definition.graph.required.names: 

130 dataId = DataCoordinate.fromRequiredValues( 

131 self.definition.graph, 

132 args[:len(self.definition.required.names)] 

133 ) 

134 else: 

135 assert not isinstance(self.definition, Dimension) 

136 dataId = DataCoordinate.fromRequiredValues( 

137 self.definition.graph, 

138 tuple(getattr(self, name) for name in self.definition.required.names) 

139 ) 

140 object.__setattr__(self, "dataId", dataId) 

141 

142 @classmethod 

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

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

145 of field values. 

146 

147 Parameters 

148 ---------- 

149 mapping : `~collections.abc.Mapping` 

150 Field values, keyed by name. The keys must match those in 

151 ``__slots__``, with the exception that a dimension name 

152 may be used in place of the primary key name - for example, 

153 "tract" may be used instead of "id" for the "id" primary key 

154 field of the "tract" dimension. 

155 

156 Returns 

157 ------- 

158 record : `DimensionRecord` 

159 An instance of this subclass of `DimensionRecord`. 

160 """ 

161 # If the name of the dimension is present in the given dict, use it 

162 # as the primary key value instead of expecting the field name. 

163 # For example, allow {"instrument": "HSC", ...} instead of 

164 # {"name": "HSC", ...} when building a record for instrument dimension. 

165 primaryKey = mapping.get(cls.definition.name) 

166 d: Mapping[str, Any] 

167 if primaryKey is not None and isinstance(cls.definition, Dimension): 

168 d = dict(mapping) 

169 d[cls.definition.primaryKey.name] = primaryKey 

170 else: 

171 d = mapping 

172 values = tuple(d.get(k) for k in cls.__slots__) 

173 return cls(*values) 

174 

175 def __eq__(self, other: Any) -> bool: 

176 if type(other) != type(self): 

177 return False 

178 return self.dataId == other.dataId 

179 

180 def __hash__(self) -> int: 

181 return hash(self.dataId) 

182 

183 def __str__(self) -> str: 

184 lines = [f"{self.definition.name}:"] 

185 lines.extend(f" {field}: {getattr(self, field)!r}" for field in self.fields) 

186 return "\n".join(lines) 

187 

188 def __repr__(self) -> str: 

189 return "{}.RecordClass({})".format( 

190 self.definition.name, 

191 ", ".join(repr(getattr(self, field)) for field in self.fields) 

192 ) 

193 

194 def __reduce__(self) -> tuple: 

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

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

197 

198 def toDict(self) -> Dict[str, Any]: 

199 """Return a vanilla `dict` representation of this record. 

200 """ 

201 return {name: getattr(self, name) for name in self.__slots__} 

202 

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

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

205 

206 dataId: DataCoordinate 

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

208 (`DataCoordinate`). 

209 """ 

210 

211 definition: ClassVar[DimensionElement] 

212 """The `DimensionElement` whose records this class represents 

213 (`DimensionElement`). 

214 """ 

215 

216 fields: ClassVar[Tuple[str, ...]] 

217 """The names of all fields in this class (`tuple` [ `str` ]). 

218 """