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/>. 

21from __future__ import annotations 

22 

23__all__ = ( 

24 "CollectionSummary", 

25 "GovernorDimensionRestriction", 

26) 

27 

28from dataclasses import dataclass 

29import itertools 

30from typing import ( 

31 AbstractSet, 

32 Any, 

33 ItemsView, 

34 Iterable, 

35 Iterator, 

36 Mapping, 

37 Set, 

38 Union, 

39 ValuesView, 

40) 

41 

42from ..core import ( 

43 DataCoordinate, 

44 DatasetType, 

45 DimensionUniverse, 

46 GovernorDimension, 

47 NamedKeyDict, 

48 NamedKeyMapping, 

49 NamedValueAbstractSet, 

50 NamedValueSet, 

51) 

52from ..core.utils import iterable 

53 

54 

55class GovernorDimensionRestriction(NamedKeyMapping[GovernorDimension, AbstractSet[str]]): 

56 """A custom mapping that represents a restriction on the values one or 

57 more governor dimensions may take in some context. 

58 

59 Parameters 

60 ---------- 

61 mapping : `NamedKeyDict` [ `GovernorDimension`, `Set` [ `str` ]] 

62 Mapping from governor dimension to the values it may take. Dimensions 

63 not present in the mapping are not constrained at all. 

64 """ 

65 def __init__(self, mapping: NamedKeyDict[GovernorDimension, Set[str]]): 

66 self._mapping = mapping 

67 

68 @classmethod 

69 def makeEmpty(cls, universe: DimensionUniverse) -> GovernorDimensionRestriction: 

70 """Construct a `GovernorDimensionRestriction` that allows no values 

71 for any governor dimension in the given `DimensionUniverse`. 

72 

73 Parameters 

74 ---------- 

75 universe : `DimensionUniverse` 

76 Object that manages all dimensions. 

77 

78 Returns 

79 ------- 

80 restriction : `GovernorDimensionRestriction` 

81 Restriction instance that maps all governor dimensions to an empty 

82 set. 

83 """ 

84 return cls(NamedKeyDict((k, set()) for k in universe.getGovernorDimensions())) 

85 

86 @classmethod 

87 def makeFull(cls) -> GovernorDimensionRestriction: 

88 """Construct a `GovernorDimensionRestriction` that allows any value 

89 for any governor dimension. 

90 

91 Returns 

92 ------- 

93 restriction : `GovernorDimensionRestriction` 

94 Restriction instance that contains no keys, and hence contains 

95 allows any value for any governor dimension. 

96 """ 

97 return cls(NamedKeyDict()) 

98 

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

100 if not isinstance(other, GovernorDimensionRestriction): 

101 return False 

102 return self._mapping == other._mapping 

103 

104 def __str__(self) -> str: 

105 return "({})".format( 

106 ", ".join(f"{dimension.name}: {values}" for dimension, values in self._mapping.items()) 

107 ) 

108 

109 def __repr__(self) -> str: 

110 return "GovernorDimensionRestriction({})".format( 

111 ", ".join(f"{dimension.name}={values}" for dimension, values in self._mapping.items()) 

112 ) 

113 

114 def __iter__(self) -> Iterator[GovernorDimension]: 

115 return iter(self._mapping) 

116 

117 def __len__(self) -> int: 

118 return len(self._mapping) 

119 

120 @property 

121 def names(self) -> AbstractSet[str]: 

122 # Docstring inherited. 

123 return self._mapping.names 

124 

125 def keys(self) -> NamedValueAbstractSet[GovernorDimension]: 

126 return self._mapping.keys() 

127 

128 def values(self) -> ValuesView[AbstractSet[str]]: 

129 return self._mapping.values() 

130 

131 def items(self) -> ItemsView[GovernorDimension, AbstractSet[str]]: 

132 return self._mapping.items() 

133 

134 def __getitem__(self, key: Union[str, GovernorDimension]) -> AbstractSet[str]: 

135 return self._mapping[key] 

136 

137 def copy(self) -> GovernorDimensionRestriction: 

138 """Return a deep copy of this object. 

139 

140 Returns 

141 ------- 

142 copy : `GovernorDimensionRestriction` 

143 A copy of ``self`` that can be modified without modifying ``self`` 

144 at all. 

145 """ 

146 return GovernorDimensionRestriction(NamedKeyDict((k, set(v)) for k, v in self.items())) 

147 

148 def add(self, dimension: GovernorDimension, value: str) -> None: 

149 """Add a single dimension value to the restriction. 

150 

151 Parameters 

152 ---------- 

153 dimension : `GovernorDimension` 

154 Dimension to update. 

155 value : `str` 

156 Value to allow for this dimension. 

157 """ 

158 current = self._mapping.get(dimension) 

159 if current is not None: 

160 current.add(value) 

161 

162 def update(self, other: Mapping[GovernorDimension, Union[str, Iterable[str]]]) -> None: 

163 """Update ``self`` to include all dimension values in either ``self`` 

164 or ``other``. 

165 

166 Parameters 

167 ---------- 

168 other : `Mapping` [ `Dimension`, `str` or `Iterable` [ `str` ] ] 

169 Mapping to union into ``self``. This may be another 

170 `GovernorDimensionRestriction` or any other mapping from dimension 

171 to `str` or iterable of `str`. 

172 """ 

173 for dimension in (self.keys() - other.keys()): 

174 self._mapping.pop(dimension, None) 

175 for dimension in (self.keys() & other.keys()): 

176 self._mapping[dimension].update(iterable(other[dimension])) 

177 # Dimensions that are in 'other' but not in 'self' are ignored, because 

178 # 'self' says they are already unconstrained. 

179 

180 def union(self, *others: Mapping[GovernorDimension, Union[str, Iterable[str]]] 

181 ) -> GovernorDimensionRestriction: 

182 """Construct a restriction that permits any values permitted by any of 

183 the input restrictions. 

184 

185 Parameters 

186 ---------- 

187 *others : `Mapping` [ `Dimension`, `str` or `Iterable` [ `str` ] ] 

188 Mappings to union into ``self``. These may be other 

189 `GovernorDimensionRestriction` instances or any other kind of 

190 mapping from dimension to `str` or iterable of `str`. 

191 

192 Returns 

193 ------- 

194 unioned : `GovernorDimensionRestriction` 

195 New restriction object that represents the union of ``self`` with 

196 ``others``. 

197 """ 

198 result = self.copy() 

199 for other in others: 

200 result.update(other) 

201 return result 

202 

203 def intersection_update(self, other: Mapping[GovernorDimension, Union[str, Iterable[str]]]) -> None: 

204 """Update ``self`` to include only dimension values in both ``self`` 

205 and ``other``. 

206 

207 Parameters 

208 ---------- 

209 other : `Mapping` [ `Dimension`, `str` or `Iterable` [ `str` ] ] 

210 Mapping to intersect into ``self``. This may be another 

211 `GovernorDimensionRestriction` or any other mapping from dimension 

212 to `str` or iterable of `str`. 

213 """ 

214 for dimension, values in other.items(): 

215 self._mapping.setdefault(dimension, set()).intersection_update(iterable(values)) 

216 

217 def intersection(self, *others: Mapping[GovernorDimension, Union[str, Iterable[str]]] 

218 ) -> GovernorDimensionRestriction: 

219 """Construct a restriction that permits only values permitted by all of 

220 the input restrictions. 

221 

222 Parameters 

223 ---------- 

224 *others : `Mapping` [ `Dimension`, `str` or `Iterable` [ `str` ] ] 

225 Mappings to intersect with ``self``. These may be other 

226 `GovernorDimensionRestriction` instances or any other kind of 

227 mapping from dimension to `str` or iterable of `str`. 

228 Returns 

229 ------- 

230 intersection : `GovernorDimensionRestriction` 

231 New restriction object that represents the intersection of ``self`` 

232 with ``others``. 

233 """ 

234 result = self.copy() 

235 for other in others: 

236 result.intersection_update(other) 

237 return result 

238 

239 def update_extract(self, data_id: DataCoordinate) -> None: 

240 """Update ``self`` to include all governor dimension values in the 

241 given data ID (in addition to those already in ``self``). 

242 

243 Parameters 

244 ---------- 

245 data_id : `DataCoordinate` 

246 Data ID from which governor dimension values should be extracted. 

247 Values for non-governor dimensions are ignored. 

248 """ 

249 for dimension in data_id.graph.governors: 

250 current = self._mapping.get(dimension) 

251 if current is not None: 

252 current.add(data_id[dimension]) 

253 

254 

255@dataclass 

256class CollectionSummary: 

257 """A summary of the datasets that can be found in a collection. 

258 """ 

259 

260 @classmethod 

261 def makeEmpty(cls, universe: DimensionUniverse) -> CollectionSummary: 

262 """Construct a `CollectionSummary` for a collection with no 

263 datasets. 

264 

265 Parameters 

266 ---------- 

267 universe : `DimensionUniverse` 

268 Object that manages all dimensions. 

269 

270 Returns 

271 ------- 

272 summary : `CollectionSummary` 

273 Summary object with no dataset types and no governor dimension 

274 values. 

275 """ 

276 return cls( 

277 datasetTypes=NamedValueSet(), 

278 dimensions=GovernorDimensionRestriction.makeEmpty(universe), 

279 ) 

280 

281 def copy(self) -> CollectionSummary: 

282 """Return a deep copy of this object. 

283 

284 Returns 

285 ------- 

286 copy : `CollectionSummary` 

287 A copy of ``self`` that can be modified without modifying ``self`` 

288 at all. 

289 """ 

290 return CollectionSummary(datasetTypes=self.datasetTypes.copy(), dimensions=self.dimensions.copy()) 

291 

292 def union(self, *others: CollectionSummary) -> CollectionSummary: 

293 """Construct a summary that contains all dataset types and governor 

294 dimension values in any of the inputs. 

295 

296 Parameters 

297 ---------- 

298 *others : `CollectionSummary` 

299 Restrictions to combine with ``self``. 

300 

301 Returns 

302 ------- 

303 unioned : `CollectionSummary` 

304 New summary object that represents the union of ``self`` with 

305 ``others``. 

306 """ 

307 if not others: 

308 return self 

309 datasetTypes = NamedValueSet(self.datasetTypes) 

310 datasetTypes.update(itertools.chain.from_iterable(o.datasetTypes for o in others)) 

311 dimensions = self.dimensions.union(*[o.dimensions for o in others]) 

312 return CollectionSummary(datasetTypes, dimensions) 

313 

314 datasetTypes: NamedValueSet[DatasetType] 

315 """Dataset types that may be present in the collection 

316 (`NamedValueSet` [ `DatasetType` ]). 

317 

318 A dataset type not in this set is definitely not in the collection, but 

319 the converse is not necessarily true. 

320 """ 

321 

322 dimensions: GovernorDimensionRestriction 

323 """Governor dimension values that may be present in the collection 

324 (`GovernorDimensionRestriction`). 

325 

326 A dimension value not in this restriction is definitely not in the 

327 collection, but the converse is not necessarily true. 

328 """