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"""Code relating to constraints based on `DatasetRef`, `DatasetType`, or 

25`StorageClass`.""" 

26 

27__all__ = ("Constraints", "ConstraintsValidationError", "ConstraintsConfig") 

28 

29from typing import ( 

30 TYPE_CHECKING, 

31 Optional, 

32 Set, 

33 Union, 

34) 

35 

36import logging 

37from .config import Config 

38from .configSupport import LookupKey, processLookupConfigList 

39from .exceptions import ValidationError 

40 

41if TYPE_CHECKING: 41 ↛ 42line 41 didn't jump to line 42, because the condition on line 41 was never true

42 from .dimensions import DimensionUniverse 

43 from .storageClass import StorageClass 

44 from .datasets import DatasetRef, DatasetType 

45 

46log = logging.getLogger(__name__) 

47 

48 

49class ConstraintsValidationError(ValidationError): 

50 """Exception thrown when a constraints list has mutually exclusive 

51 definitions.""" 

52 pass 

53 

54 

55class ConstraintsConfig(Config): 

56 """Configuration information for `Constraints`""" 

57 pass 

58 

59 

60class Constraints: 

61 """Determine whether a `DatasetRef`, `DatasetType`, or `StorageClass` 

62 is allowed to be handled. 

63 

64 Parameters 

65 ---------- 

66 config : `ConstraintsConfig` or `str` 

67 Load configuration. If `None` then this is equivalent to having 

68 no restrictions. 

69 universe : `DimensionUniverse` 

70 The set of all known dimensions, used to normalize any lookup keys 

71 involving dimensions. 

72 """ 

73 

74 matchAllKey = LookupKey("all") 

75 """Configuration key associated with matching everything.""" 

76 

77 def __init__(self, config: Optional[Union[ConstraintsConfig, str]], *, universe: DimensionUniverse): 

78 # Default is to accept all and reject nothing 

79 self._accept = set() 

80 self._reject = set() 

81 

82 if config is not None: 

83 self.config = ConstraintsConfig(config) 

84 

85 if "accept" in self.config: 

86 self._accept = processLookupConfigList(self.config["accept"], universe=universe) 

87 if "reject" in self.config: 

88 self._reject = processLookupConfigList(self.config["reject"], universe=universe) 

89 

90 if self.matchAllKey in self._accept and self.matchAllKey in self._reject: 

91 raise ConstraintsValidationError("Can not explicitly accept 'all' and reject 'all'" 

92 " in one configuration") 

93 

94 def __str__(self) -> str: 

95 # Standard stringification 

96 if not self._accept and not self._reject: 

97 return "Accepts: all" 

98 

99 accepts = ", ".join(str(k) for k in self._accept) 

100 rejects = ", ".join(str(k) for k in self._reject) 

101 return f"Accepts: {accepts}; Rejects: {rejects}" 

102 

103 def isAcceptable(self, entity: Union[DatasetRef, DatasetType, StorageClass]) -> bool: 

104 """Check whether the supplied entity will be acceptable to whatever 

105 this `Constraints` class is associated with. 

106 

107 Parameters 

108 ---------- 

109 entity : `DatasetType`, `DatasetRef`, or `StorageClass` 

110 Instance to use to look in constraints table. 

111 The entity itself reports the `LookupKey` that is relevant. 

112 

113 Returns 

114 ------- 

115 allowed : `bool` 

116 `True` if the entity is allowed. 

117 """ 

118 # Get the names to use for lookup 

119 names = set(entity._lookupNames()) 

120 

121 # Test if this entity is explicitly mentioned for accept/reject 

122 isExplicitlyAccepted = bool(names & self._accept) 

123 

124 if isExplicitlyAccepted: 

125 return True 

126 

127 isExplicitlyRejected = bool(names & self._reject) 

128 

129 if isExplicitlyRejected: 

130 return False 

131 

132 # Now look for wildcard match -- we have to also check for dataId 

133 # overrides 

134 

135 # Generate a new set of lookup keys that use the wildcard name 

136 # but the supplied dimensions 

137 wildcards = {k.clone(name=self.matchAllKey.name) for k in names} 

138 

139 isWildcardAccepted = bool(wildcards & self._accept) 

140 isWildcardRejected = bool(wildcards & self._reject) 

141 

142 if isWildcardRejected: 

143 return False 

144 

145 # If all the wildcard and explicit rejections have failed then 

146 # if the accept list is empty, or if a wildcard acceptance worked 

147 # we can accept, else reject 

148 if isWildcardAccepted or not self._accept: 

149 return True 

150 

151 return False 

152 

153 def getLookupKeys(self) -> Set[LookupKey]: 

154 """Retrieve the look up keys for all the constraints entries. 

155 

156 Returns 

157 ------- 

158 keys : `set` of `LookupKey` 

159 The keys available for determining constraints. Does not include 

160 the special "all" lookup key. 

161 """ 

162 all = self._accept | self._accept 

163 return set(a for a in all if a.name != self.matchAllKey.name)