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

# 

# LSST Data Management System 

# 

# This product includes software developed by the 

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

# 

# See COPYRIGHT file at the top of the source tree. 

# 

# 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 LSST License Statement and 

# the GNU General Public License along with this program. If not, 

# see <https://www.lsstcorp.org/LegalNotices/>. 

# 

from __future__ import print_function 

 

__all__ = ['Metadata'] 

 

# Get ChainMap backport 

from future.standard_library import install_aliases 

install_aliases() # noqa: E402 

 

try: 

from collections import ChainMap 

except ImportError: 

# future 0.16.0 doesn't do the import right; this will be fixed in 0.16.1 

# https://github.com/PythonCharmers/python-future/issues/226 

from future.backports.misc import ChainMap 

import json 

import re 

 

from .jsonmixin import JsonSerializationMixin 

 

 

class Metadata(JsonSerializationMixin): 

"""Container for verification framework job metadata. 

 

Metadata are key-value terms. Both keys and values should be 

JSON-serializable. 

 

Parameters 

---------- 

measurement_set : `lsst.verify.MeasurementSet`, optional 

When provided, metadata with keys prefixed by metric names are 

deferred to `Metadata` instances attached to measurements 

(`lsst.verify.Measurement.notes`). 

data : `dict`, optional 

Dictionary to seed metadata. 

""" 

 

# Pattern for detecting metric name prefixes in names 

_prefix_pattern = re.compile(r'^(\S+\.\S+)\.') 

 

def __init__(self, measurement_set, data=None): 

 

# Dict of job metadata not stored with a mesaurement 

self._data = {} 

 

# Measurement set to get measurement annotations from 

self._meas_set = measurement_set 

 

# Initialize the ChainMap. The first item in the chain map is the 

# Metadata object's own _data. This is generic metadata. Additional 

# items in the chain are Measurement.notes annotations for all 

# measurements in the measurement_set. 

self._chain = ChainMap(self._data) 

self._cached_prefixes = set() 

self._refresh_chainmap() 

 

if data is not None: 

self.update(data) 

 

def _refresh_chainmap(self): 

prefixes = set([str(name) for name in self._meas_set]) 

 

if self._cached_prefixes != prefixes: 

self._cached_prefixes = prefixes 

 

self._chain = ChainMap(self._data) 

for _, measurement in self._meas_set.items(): 

# Get the dict instance directly so we don't use 

# the MeasurementNotes's key auto-prefixing. 

self._chain.maps.append(measurement.notes._data) 

 

@staticmethod 

def _get_prefix(key): 

"""Get the prefix of a measurement not, if it exists. 

 

Examples 

-------- 

>>> Metadata._get_prefix('note') is None 

True 

>>> Metadata._get_prefix('validate_drp.PA1.note') 

'validate_drp.PA1.' 

 

To get the metric name: 

 

>>> prefix = Metadata._get_prefix('validate_drp.PA1.note') 

>>> prefix.rstrip('.') 

'validate_drp.PA1' 

""" 

match = Metadata._prefix_pattern.match(key) 

if match is not None: 

return match.group(0) 

else: 

return None 

 

def __getitem__(self, key): 

self._refresh_chainmap() 

return self._chain[key] 

 

def __setitem__(self, key, value): 

prefix = Metadata._get_prefix(key) 

if prefix is not None: 

metric_name = prefix.rstrip('.') 

if metric_name in self._meas_set: 

# turn prefix into a metric name 

self._meas_set[metric_name].notes[key] = value 

return 

 

# No matching measurement; insert into general metadata 

self._data[key] = value 

 

def __delitem__(self, key): 

prefix = Metadata._get_prefix(key) 

if prefix is not None: 

metric_name = prefix.rstrip('.') 

if metric_name in self._meas_set: 

del self._meas_set[metric_name].notes[key] 

return 

 

# No matching measurement; delete from general metadata 

del self._data[key] 

 

def __contains__(self, key): 

self._refresh_chainmap() 

return key in self._chain 

 

def __len__(self): 

self._refresh_chainmap() 

return len(self._chain) 

 

def __iter__(self): 

self._refresh_chainmap() 

for key in self._chain: 

yield key 

 

def __eq__(self, other): 

# No explicit chain refresh because __len__ already does it 

if len(self) != len(other): 

return False 

 

for key, value in other.items(): 

if key not in self: 

return False 

if value != self[key]: 

return False 

 

return True 

 

def __ne__(self, other): 

return not self.__eq__(other) 

 

def __str__(self): 

json_data = self.json 

return json.dumps(json_data, sort_keys=True, indent=4) 

 

def __repr__(self): 

return repr(self._chain) 

 

def _repr_html_(self): 

return self.__str__() 

 

def keys(self): 

"""Get a `list` of metadata keys. 

 

Returns 

------- 

keys : `list` of `str` 

These keys keys can be used to access metadata values (like a 

`dict`). 

""" 

return [key for key in self] 

 

def items(self): 

"""Iterate over key-value metadata pairs. 

 

Yields 

------ 

item : `tuple` 

A metadata item is a tuple of: 

 

- Key (`str`). 

- Value (object). 

""" 

self._refresh_chainmap() 

for item in self._chain.items(): 

yield item 

 

def update(self, data): 

"""Update metadata with key-value pairs from a `dict`-like object. 

 

Parameters 

---------- 

data : `dict`-like 

The ``data`` object needs to provide an ``items`` method to 

iterate over its key-value pairs. If this ``Metadata`` instance 

already has a key, the value will be overwritten with the value 

from ``data``. 

""" 

for key, value in data.items(): 

self[key] = value 

 

@property 

def json(self): 

"""A `dict` that can be serialized as semantic SQUASH JSON. 

 

Keys in the `dict` are metadata keys (see `Metadata.keys`). Values 

are the associated metadata values as JSON-serializable objects. 

""" 

self._refresh_chainmap() 

return self.jsonify_dict(self._chain)