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

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

# This file is part of daf_butler. 

# 

# Developed for the LSST Data Management System. 

# This product includes software developed by the LSST Project 

# (http://www.lsst.org). 

# See the COPYRIGHT file at the top-level directory of this distribution 

# for details of code ownership. 

# 

# This program is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 3 of the License, or 

# (at your option) any later version. 

# 

# This program is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the GNU General Public License 

# along with this program. If not, see <http://www.gnu.org/licenses/>. 

 

__all__ = ("DimensionElement", "Dimension", "DimensionJoin") 

 

from ..utils import PrivateConstructorMeta 

 

 

class DimensionElement(metaclass=PrivateConstructorMeta): 

"""Base class for elements in the dimension schema. 

 

`DimensionElement` has exactly two subclasses: `Dimension` and 

`DimensionJoin`. 

 

`DimensionElement` objects are not directly constructable; they can only 

be obtained (possibly indirectly) from a special "universe" 

`DimensionGraph` loaded from configuration. 

""" 

 

# 

# Constructor is private, so its docs are just comments. 

# 

# Parameters 

# ---------- 

# universe : `DimensionGraph` 

# Ultimate-parent `DimensionGraph` that constructed this element. 

# name : `str` 

# Name of the element. Also the name of any SQL table or view 

# associated with it. 

# hasRegion : `bool` 

# Whether entries for this dimension are associated with a region 

# on the sky. 

# links : iterable of `str` 

# The names of primary key fields used by this element but not any of 

# its dependencies. 

# required : iterable of `str` 

# The names of the `Dimension`\ s whose primary keys are a subset of 

# this element's primary key. 

# implied : iterable of `str` 

# The names of the `Dimension`\ s whose primary keys are specified by 

# foreign keys in this element. 

# doc : `str` 

# Documentation string for this element. 

# 

def __init__(self, universe, name, hasRegion, links, required, implied, doc): 

from .sets import DimensionSet 

self._universe = universe 

self._name = name 

self._hasRegion = hasRegion 

self._localLinks = frozenset(links) 

self._doc = doc 

self._requiredDependencies = DimensionSet(universe, required, expand=True, implied=False) 

self._impliedDependencies = DimensionSet(universe, implied, expand=False) 

# Compute _primaryKeys dict, used to back primaryKeys property. 

expandedLinks = set(self._localLinks) 

for dimension in self.dependencies(implied=False): 

expandedLinks |= dimension.links(expand=True) 

self._expandedLinks = frozenset(expandedLinks) 

 

def __eq__(self, other): 

try: 

return self.universe is other.universe and self.name == other.name 

except AttributeError: 

return NotImplemented 

 

def __hash__(self): 

return hash(self.name) 

 

@property 

def universe(self): 

"""The graph of all dimensions compatible with self (`DimensionGraph`). 

""" 

return self._universe 

 

@property 

def name(self): 

"""Name of this dimension (`str`, read-only). 

 

Also assumed to be the name of any associated table or view. 

""" 

return self._name 

 

@property 

def hasRegion(self): 

"""Whether this dimension is associated with a region on the sky 

(`bool`). 

""" 

return self._hasRegion 

 

@property 

def doc(self): 

"""Documentation for this dimension (`str`). 

""" 

return self._doc 

 

def links(self, expand=True): 

"""Return the names of fields that uniquely identify this dimension in 

a data ID dict. 

 

Parameters 

---------- 

expand: `bool` 

If `True` (default) include links associated with required 

dependencies. 

 

Returns 

------- 

links : `frozenset` of `str` 

Set of field names. 

""" 

if expand: 

return self._expandedLinks 

else: 

return self._localLinks 

 

def dependencies(self, required=True, implied=False): 

"""Return the set of dimensions this dimension depends on. 

 

Parameters 

---------- 

required : `bool` 

If `True` (default), include required dependencies. Required 

dependences are always expanded recursively. 

implied : `bool` 

If `True`, return implied dependencies. 

 

Returns 

------- 

dependencies : `DimensionSet` 

""" 

if required: 

if implied: 

return self._requiredDependencies | self._impliedDependencies 

else: 

return self._requiredDependencies 

elif implied: 

return self._impliedDependencies 

raise ValueError("At least one of 'required' and 'implied' must be True.") 

 

def graph(self, implied=False): 

"""Return the minimal `DimensionGraph` that contains ``self``. 

 

Parameters 

---------- 

implied : `bool` 

If `True`, include implied as well as required dependencies. 

 

Returns 

------- 

graph : `DimensionGraph` 

""" 

return self.universe.extract([self], implied=implied) 

 

 

class Dimension(DimensionElement): 

r"""A discrete dimension of data used to organize Datasets and associate 

them with metadata. 

 

`Dimension` instances represent concepts such as "Instrument", 

"Detector", "Visit" and "SkyMap", which are usually associated with 

database tables. A `DatasetType` is associated with a fixed combination 

of `Dimension`\s. 

 

`Dimension` objects are not directly constructable; they can only be 

obtained from a `DimensionGraph`. 

""" 

 

# 

# Constructor is private, so its docs are just comments. 

# 

# Parameters 

# ---------- 

# universe : `DimensionGraph` 

# Ultimate-parent DimensionGraph that constructed this element. 

# name : `str` 

# Name of the element. Also the name of any SQL table or view 

# associated with it. 

# config : `Config` 

# Sub-config corresponding to this `Dimension`. 

# 

def __init__(self, universe, name, config): 

super().__init__(universe, name, hasRegion=config.get("hasRegion", False), links=config["link"], 

required=config.get(".dependencies.required", ()), 

implied=config.get(".dependencies.implied", ()), 

doc=config["doc"]) 

 

def __repr__(self): 

return "Dimension({})".format(self.name) 

 

 

class DimensionJoin(DimensionElement): 

r"""A join that relates two or more `Dimension`\s. 

 

`DimensionJoin`\s usually map to many-to-many join tables or views that 

relate `Dimension` tables. 

 

`DimensionJoin` objects are not directly constructable; they can only be 

obtained from a `DimensionGraph`. 

""" 

 

# 

# Constructor is private, so its docs are just comments. 

# 

# Parameters 

# ---------- 

# universe : `DimensionGraph` 

# Ultimate-parent DimensionGraph that constructed this element. 

# name : `str` 

# Name of the element. Also the name of any SQL table or view 

# associated with it. 

# config : `Config` 

# Sub-config corresponding to this `DimensionJoin`. 

# 

def __init__(self, universe, name, config): 

from .sets import DimensionSet 

 

lhs = list(config["lhs"]) 

rhs = list(config["rhs"]) 

super().__init__(universe, name, hasRegion=config.get("hasRegion", False), links=(), 

required=lhs + rhs, implied=(), doc=config["doc"]) 

self._lhs = DimensionSet(universe, lhs, implied=False) 

self._rhs = DimensionSet(universe, rhs, implied=False) 

# self._summarizes initialized later in DimensionGraph.fromConfig. 

 

@property 

def lhs(self): 

r"""The `Dimension`\s on the left hand side of the join 

(`DimensionSet`). 

 

Left vs. right is completely arbitrary; the terminology simply provides 

an easy way to distinguish between the two sides. 

""" 

return self._lhs 

 

@property 

def rhs(self): 

r"""The `Dimension`\s on the right hand side of the join 

(`DimensionSet`). 

 

Left vs. right is completely arbitrary; the terminology simply provides 

an easy way to distinguish between the two sides. 

""" 

return self._rhs 

 

@property 

def summarizes(self): 

r"""A set of other `DimensionJoin`\s that provide more fine-grained 

relationships than this one (`DimensionJoinSet`). 

 

When a join "summarizes" another, it means the table for that join 

could (at least conceptually) be defined as an aggregate view on the 

summarized join table. For example, "TractSkyPixJoin" summarizes 

"PatchSkyPixJoin", because the set of SkyPix rows associated with a 

Tract row is just the set of SkyPix rows associated with all Patches 

associated with that Tract. Or, in SQL: 

 

.. code-block:: sql 

 

CREATE VIEW TractSkyPixJoin AS 

SELECT DISTINCT skymap, tract, skypix FROM PatchSkyPixJoin; 

""" 

return self._summarizes 

 

def __repr__(self): 

return "DimensionJoin({})".format(self.name)