Coverage for python/lsst/daf/butler/core/constraints.py: 26%

52 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-10-02 08:00 +0000

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 software is dual licensed under the GNU General Public License and also 

10# under a 3-clause BSD license. Recipients may choose which of these licenses 

11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, 

12# respectively. If you choose the GPL option then the following text applies 

13# (but note that there is still no warranty even if you opt for BSD instead): 

14# 

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

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

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

18# (at your option) any later version. 

19# 

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

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

22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

23# GNU General Public License for more details. 

24# 

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

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

27 

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

29`StorageClass`. 

30""" 

31 

32from __future__ import annotations 

33 

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

35 

36import logging 

37from typing import TYPE_CHECKING 

38 

39from .config import Config 

40from .configSupport import LookupKey, processLookupConfigList 

41from .exceptions import ValidationError 

42 

43if TYPE_CHECKING: 

44 from .datasets import DatasetRef, DatasetType 

45 from .dimensions import DimensionUniverse 

46 from .storageClass import StorageClass 

47 

48log = logging.getLogger(__name__) 

49 

50 

51class ConstraintsValidationError(ValidationError): 

52 """Thrown when a constraints list has mutually exclusive definitions.""" 

53 

54 pass 

55 

56 

57class ConstraintsConfig(Config): 

58 """Configuration information for `Constraints`.""" 

59 

60 pass 

61 

62 

63class Constraints: 

64 """Determine whether an entity is allowed to be handled. 

65 

66 Supported entities are `DatasetRef`, `DatasetType`, or `StorageClass`. 

67 

68 Parameters 

69 ---------- 

70 config : `ConstraintsConfig` or `str` 

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

72 no restrictions. 

73 universe : `DimensionUniverse` 

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

75 involving dimensions. 

76 """ 

77 

78 matchAllKey = LookupKey("all") 

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

80 

81 def __init__(self, config: ConstraintsConfig | str | None, *, universe: DimensionUniverse): 

82 # Default is to accept all and reject nothing 

83 self._accept = set() 

84 self._reject = set() 

85 

86 if config is not None: 

87 self.config = ConstraintsConfig(config) 

88 

89 if "accept" in self.config: 

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

91 if "reject" in self.config: 

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

93 

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

95 raise ConstraintsValidationError( 

96 "Can not explicitly accept 'all' and reject 'all' in one configuration" 

97 ) 

98 

99 def __str__(self) -> str: 

100 # Standard stringification 

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

102 return "Accepts: all" 

103 

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

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

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

107 

108 def isAcceptable(self, entity: DatasetRef | DatasetType | StorageClass) -> bool: 

109 """Check whether the supplied entity will be acceptable. 

110 

111 Parameters 

112 ---------- 

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

114 Instance to use to look in constraints table. 

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

116 

117 Returns 

118 ------- 

119 allowed : `bool` 

120 `True` if the entity is allowed. 

121 """ 

122 # Get the names to use for lookup 

123 names = set(entity._lookupNames()) 

124 

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

126 isExplicitlyAccepted = bool(names & self._accept) 

127 

128 if isExplicitlyAccepted: 

129 return True 

130 

131 isExplicitlyRejected = bool(names & self._reject) 

132 

133 if isExplicitlyRejected: 

134 return False 

135 

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

137 # overrides 

138 

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

140 # but the supplied dimensions 

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

142 

143 isWildcardAccepted = bool(wildcards & self._accept) 

144 isWildcardRejected = bool(wildcards & self._reject) 

145 

146 if isWildcardRejected: 

147 return False 

148 

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

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

151 # we can accept, else reject 

152 if isWildcardAccepted or not self._accept: 

153 return True 

154 

155 return False 

156 

157 def getLookupKeys(self) -> set[LookupKey]: 

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

159 

160 Returns 

161 ------- 

162 keys : `set` of `LookupKey` 

163 The keys available for determining constraints. Does not include 

164 the special "all" lookup key. 

165 """ 

166 all = self._accept | self._accept 

167 return {a for a in all if a.name != self.matchAllKey.name}