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

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/>.
22from __future__ import annotations
24"""Code relating to constraints based on `DatasetRef`, `DatasetType`, or
25`StorageClass`."""
27__all__ = ("Constraints", "ConstraintsValidationError", "ConstraintsConfig")
29from typing import (
30 TYPE_CHECKING,
31 Optional,
32 Set,
33 Union,
34)
36import logging
37from .config import Config
38from .configSupport import LookupKey, processLookupConfigList
39from .exceptions import ValidationError
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
46log = logging.getLogger(__name__)
49class ConstraintsValidationError(ValidationError):
50 """Exception thrown when a constraints list has mutually exclusive
51 definitions."""
52 pass
55class ConstraintsConfig(Config):
56 """Configuration information for `Constraints`"""
57 pass
60class Constraints:
61 """Determine whether a `DatasetRef`, `DatasetType`, or `StorageClass`
62 is allowed to be handled.
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 """
74 matchAllKey = LookupKey("all")
75 """Configuration key associated with matching everything."""
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()
82 if config is not None:
83 self.config = ConstraintsConfig(config)
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)
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")
94 def __str__(self) -> str:
95 # Standard stringification
96 if not self._accept and not self._reject:
97 return "Accepts: all"
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}"
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.
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.
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())
121 # Test if this entity is explicitly mentioned for accept/reject
122 isExplicitlyAccepted = bool(names & self._accept)
124 if isExplicitlyAccepted:
125 return True
127 isExplicitlyRejected = bool(names & self._reject)
129 if isExplicitlyRejected:
130 return False
132 # Now look for wildcard match -- we have to also check for dataId
133 # overrides
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}
139 isWildcardAccepted = bool(wildcards & self._accept)
140 isWildcardRejected = bool(wildcards & self._reject)
142 if isWildcardRejected:
143 return False
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
151 return False
153 def getLookupKeys(self) -> Set[LookupKey]:
154 """Retrieve the look up keys for all the constraints entries.
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)