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 

22"""Code relating to constraints based on `DatasetRef`, `DatasetType`, or 

23`StorageClass`.""" 

24 

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

26 

27import logging 

28from .config import Config 

29from .configSupport import LookupKey, processLookupConfigList 

30from .exceptions import ValidationError 

31 

32log = logging.getLogger(__name__) 

33 

34 

35class ConstraintsValidationError(ValidationError): 

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

37 definitions.""" 

38 pass 

39 

40 

41class ConstraintsConfig(Config): 

42 """Configuration information for `Constraints`""" 

43 pass 

44 

45 

46class Constraints: 

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

48 is allowed to be handled. 

49 

50 Parameters 

51 ---------- 

52 config : `ConstraintsConfig` or `str` 

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

54 no restrictions. 

55 universe : `DimensionUniverse` 

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

57 involving dimensions. 

58 """ 

59 

60 matchAllKey = LookupKey("all") 

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

62 

63 def __init__(self, config, *, universe): 

64 # Default is to accept all and reject nothing 

65 self._accept = set() 

66 self._reject = set() 

67 

68 if config is not None: 

69 self.config = ConstraintsConfig(config) 

70 

71 if "accept" in self.config: 

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

73 if "reject" in self.config: 

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

75 

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

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

78 " in one configuration") 

79 

80 def __str__(self): 

81 # Standard stringification 

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

83 return "Accepts: all" 

84 

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

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

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

88 

89 def isAcceptable(self, entity): 

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

91 this `Constraints` class is associated with. 

92 

93 Parameters 

94 ---------- 

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

96 Instance to use to look in constraints table. 

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

98 

99 Returns 

100 ------- 

101 allowed : `bool` 

102 `True` if the entity is allowed. 

103 """ 

104 # Get the names to use for lookup 

105 names = set(entity._lookupNames()) 

106 

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

108 isExplicitlyAccepted = bool(names & self._accept) 

109 

110 if isExplicitlyAccepted: 

111 return True 

112 

113 isExplicitlyRejected = bool(names & self._reject) 

114 

115 if isExplicitlyRejected: 

116 return False 

117 

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

119 # overrides 

120 

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

122 # but the supplied dimensions 

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

124 

125 isWildcardAccepted = bool(wildcards & self._accept) 

126 isWildcardRejected = bool(wildcards & self._reject) 

127 

128 if isWildcardRejected: 

129 return False 

130 

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

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

133 # we can accept, else reject 

134 if isWildcardAccepted or not self._accept: 

135 return True 

136 

137 return False 

138 

139 def getLookupKeys(self): 

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

141 

142 Returns 

143 ------- 

144 keys : `set` of `LookupKey` 

145 The keys available for determining constraints. Does not include 

146 the special "all" lookup key. 

147 """ 

148 all = self._accept | self._accept 

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