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__ = ( 

25 "TopologicalSpace", 

26 "TopologicalFamily", 

27 "TopologicalRelationshipEndpoint", 

28 "TopologicalExtentDatabaseRepresentation", 

29) 

30 

31from abc import ABC, abstractmethod 

32import enum 

33from typing import ( 

34 Any, 

35 Mapping, 

36 Optional, 

37 Type, 

38 TypeVar, 

39) 

40 

41import sqlalchemy 

42 

43from .named import NamedValueAbstractSet 

44from .utils import immutable 

45 

46 

47@enum.unique 

48class TopologicalSpace(enum.Enum): 

49 """Enumeration of the different categories of continuous-variable 

50 relationships supported by the dimensions system. 

51 

52 Most dimension relationships are discrete, in that they are regular foreign 

53 key relationships between tables. Those connected to a 

54 `TopologicalSpace` are not - a row in a table instead occupies some 

55 region in a continuous-variable space, and topological operators like 

56 "overlaps" between regions in that space define the relationships between 

57 rows. 

58 """ 

59 

60 SPATIAL = enum.auto() 

61 """The (spherical) sky, using `lsst.sphgeom.Region` objects to represent 

62 those regions in memory. 

63 """ 

64 

65 TEMPORAL = enum.auto() 

66 """Time, using `Timespan` instances (with TAI endpoints) to represent 

67 intervals in memory. 

68 """ 

69 

70 

71@immutable 

72class TopologicalFamily(ABC): 

73 """A grouping of `TopologicalRelationshipEndpoint` objects whose regions 

74 form a hierarchy in which one endpoint's rows always contain another's in a 

75 predefined way. 

76 

77 This hierarchy means that endpoints in the same family do not generally 

78 have to be have to be related using (e.g.) overlaps; instead, the regions 

79 from one "best" endpoint from each family are related to the best endpoint 

80 from each other family in a query. 

81 

82 Parameters 

83 ---------- 

84 name : `str` 

85 Unique string identifier for this family. 

86 category : `TopologicalSpace` 

87 Space in which the regions of this family live. 

88 """ 

89 def __init__( 

90 self, 

91 name: str, 

92 space: TopologicalSpace, 

93 ): 

94 self.name = name 

95 self.space = space 

96 

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

98 if isinstance(other, TopologicalFamily): 

99 return self.space == other.space and self.name == other.name 

100 return False 

101 

102 def __hash__(self) -> int: 

103 return hash(self.name) 

104 

105 def __contains__(self, other: TopologicalRelationshipEndpoint) -> bool: 

106 return other.topology.get(self.space) == self 

107 

108 @abstractmethod 

109 def choose(self, endpoints: NamedValueAbstractSet[TopologicalRelationshipEndpoint] 

110 ) -> TopologicalRelationshipEndpoint: 

111 """Select the best member of this family to use in a query join or 

112 data ID when more than one is present. 

113 

114 Usually this should correspond to the most fine-grained region. 

115 

116 Parameters 

117 ---------- 

118 endpoints : `NamedValueAbstractSet` [`TopologicalRelationshipEndpoint`] 

119 Endpoints to choose from. May include endpoints that are not 

120 members of this family (which should be ignored). 

121 

122 Returns 

123 ------- 

124 best : `TopologicalRelationshipEndpoint` 

125 The best endpoint that is both a member of ``self`` and in 

126 ``endpoints``. 

127 """ 

128 raise NotImplementedError() 

129 

130 name: str 

131 """Unique string identifier for this family (`str`). 

132 """ 

133 

134 space: TopologicalSpace 

135 """Space in which the regions of this family live (`TopologicalSpace`). 

136 """ 

137 

138 

139@immutable 

140class TopologicalRelationshipEndpoint(ABC): 

141 """An abstract base class whose instances represent a logical table that 

142 may participate in overlap joins defined by a `TopologicalSpace`. 

143 """ 

144 

145 @property 

146 @abstractmethod 

147 def name(self) -> str: 

148 """Unique string identifier for this endpoint (`str`). 

149 """ 

150 raise NotImplementedError() 

151 

152 @property 

153 @abstractmethod 

154 def topology(self) -> Mapping[TopologicalSpace, TopologicalFamily]: 

155 """The relationship families to which this endpoint belongs, keyed 

156 by the category for that family. 

157 """ 

158 raise NotImplementedError() 

159 

160 @property 

161 def spatial(self) -> Optional[TopologicalFamily]: 

162 """This endpoint's `~TopologicalSpace.SPATIAL` family. 

163 """ 

164 return self.topology.get(TopologicalSpace.SPATIAL) 

165 

166 @property 

167 def temporal(self) -> Optional[TopologicalFamily]: 

168 """This endpoint's `~TopologicalSpace.TEMPORAL` family. 

169 """ 

170 return self.topology.get(TopologicalSpace.TEMPORAL) 

171 

172 

173_S = TypeVar("_S", bound="TopologicalExtentDatabaseRepresentation") 

174 

175 

176class TopologicalExtentDatabaseRepresentation(ABC): 

177 """An abstract base class whose subclasses provide a mapping from the 

178 in-memory representation of a `TopologicalSpace` region to a 

179 database-storage representation, and whose instances act like a 

180 SQLAlchemy-based column expression. 

181 """ 

182 

183 @classmethod 

184 @abstractmethod 

185 def fromSelectable(cls: Type[_S], selectable: sqlalchemy.sql.FromClause) -> _S: 

186 """Construct an instance that represents a logical column (which may 

187 actually be backed by multiple columns) in the given table or subquery. 

188 

189 Parameters 

190 ---------- 

191 selectable : `sqlalchemy.sql.FromClause` 

192 SQLAlchemy object representing a table or subquery. 

193 

194 Returns 

195 ------- 

196 representation : `TopologicalExtentDatabaseRepresentation` 

197 Object representing a logical column. 

198 """ 

199 raise NotImplementedError() 

200 

201 @abstractmethod 

202 def overlaps(self: _S, other: _S) -> sqlalchemy.sql.ColumnElement: 

203 """Return a boolean SQLAlchemy column expression representing an 

204 overlap test between this logical column and another of the same type. 

205 

206 Parameters 

207 ---------- 

208 other : ``type(self)`` 

209 Another instance of the exact same type as ``self``. 

210 

211 Returns 

212 ------- 

213 expression : `sqlalchemy.sql.ColumnElement` 

214 A boolean SQLAlchemy expression. 

215 """ 

216 raise NotImplementedError()