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 Dict, Any, Type, Mapping, TYPE_CHECKING 

27 

28from ..timespan import Timespan 

29from .coordinate import DataCoordinate 

30 

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 

33 

34 

35def _reconstructDimensionRecord(definition: DimensionElement, *args): 

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

37 

38 For internal use by `DimensionRecord`. 

39 """ 

40 return definition.RecordClass(*args) 

41 

42 

43def _makeTimespanFromRecord(record: DimensionRecord): 

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

45 

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 ) 

53 

54 

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

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

57 `DimensionElement`. 

58 

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) 

69 

70 

71class DimensionRecord: 

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

73 a `DimensionElement`. 

74 

75 Parameters 

76 ---------- 

77 args 

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

79 

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. 

87 

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: 

91 

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

93 

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

95 on record classes that correspond to temporal elements. 

96 

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. 

100 

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. 

104 

105 `DimensionRecord` instances are immutable. 

106 """ 

107 

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

112 

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) 

121 

122 @classmethod 

123 def fromDict(cls, mapping: Mapping[str, Any]): 

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

125 of field values. 

126 

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. 

135 

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) 

153 

154 def __reduce__(self): 

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

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

157 

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

162 

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

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

165 

166 dataId: DataCoordinate 

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

168 (`DataCoordinate`). 

169 """