Coverage for python/lsst/daf/butler/core/constraints.py: 26%
52 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-08-05 01:26 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-08-05 01:26 +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 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/>.
22"""Code relating to constraints based on `DatasetRef`, `DatasetType`, or
23`StorageClass`.
24"""
26from __future__ import annotations
28__all__ = ("Constraints", "ConstraintsValidationError", "ConstraintsConfig")
30import logging
31from typing import TYPE_CHECKING
33from .config import Config
34from .configSupport import LookupKey, processLookupConfigList
35from .exceptions import ValidationError
37if TYPE_CHECKING:
38 from .datasets import DatasetRef, DatasetType
39 from .dimensions import DimensionUniverse
40 from .storageClass import StorageClass
42log = logging.getLogger(__name__)
45class ConstraintsValidationError(ValidationError):
46 """Thrown when a constraints list has mutually exclusive definitions."""
48 pass
51class ConstraintsConfig(Config):
52 """Configuration information for `Constraints`."""
54 pass
57class Constraints:
58 """Determine whether an entity is allowed to be handled.
60 Supported entities are `DatasetRef`, `DatasetType`, or `StorageClass`.
62 Parameters
63 ----------
64 config : `ConstraintsConfig` or `str`
65 Load configuration. If `None` then this is equivalent to having
66 no restrictions.
67 universe : `DimensionUniverse`
68 The set of all known dimensions, used to normalize any lookup keys
69 involving dimensions.
70 """
72 matchAllKey = LookupKey("all")
73 """Configuration key associated with matching everything."""
75 def __init__(self, config: ConstraintsConfig | str | None, *, universe: DimensionUniverse):
76 # Default is to accept all and reject nothing
77 self._accept = set()
78 self._reject = set()
80 if config is not None:
81 self.config = ConstraintsConfig(config)
83 if "accept" in self.config:
84 self._accept = processLookupConfigList(self.config["accept"], universe=universe)
85 if "reject" in self.config:
86 self._reject = processLookupConfigList(self.config["reject"], universe=universe)
88 if self.matchAllKey in self._accept and self.matchAllKey in self._reject:
89 raise ConstraintsValidationError(
90 "Can not explicitly accept 'all' and reject 'all' in one configuration"
91 )
93 def __str__(self) -> str:
94 # Standard stringification
95 if not self._accept and not self._reject:
96 return "Accepts: all"
98 accepts = ", ".join(str(k) for k in self._accept)
99 rejects = ", ".join(str(k) for k in self._reject)
100 return f"Accepts: {accepts}; Rejects: {rejects}"
102 def isAcceptable(self, entity: DatasetRef | DatasetType | StorageClass) -> bool:
103 """Check whether the supplied entity will be acceptable.
105 Parameters
106 ----------
107 entity : `DatasetType`, `DatasetRef`, or `StorageClass`
108 Instance to use to look in constraints table.
109 The entity itself reports the `LookupKey` that is relevant.
111 Returns
112 -------
113 allowed : `bool`
114 `True` if the entity is allowed.
115 """
116 # Get the names to use for lookup
117 names = set(entity._lookupNames())
119 # Test if this entity is explicitly mentioned for accept/reject
120 isExplicitlyAccepted = bool(names & self._accept)
122 if isExplicitlyAccepted:
123 return True
125 isExplicitlyRejected = bool(names & self._reject)
127 if isExplicitlyRejected:
128 return False
130 # Now look for wildcard match -- we have to also check for dataId
131 # overrides
133 # Generate a new set of lookup keys that use the wildcard name
134 # but the supplied dimensions
135 wildcards = {k.clone(name=self.matchAllKey.name) for k in names}
137 isWildcardAccepted = bool(wildcards & self._accept)
138 isWildcardRejected = bool(wildcards & self._reject)
140 if isWildcardRejected:
141 return False
143 # If all the wildcard and explicit rejections have failed then
144 # if the accept list is empty, or if a wildcard acceptance worked
145 # we can accept, else reject
146 if isWildcardAccepted or not self._accept:
147 return True
149 return False
151 def getLookupKeys(self) -> set[LookupKey]:
152 """Retrieve the look up keys for all the constraints entries.
154 Returns
155 -------
156 keys : `set` of `LookupKey`
157 The keys available for determining constraints. Does not include
158 the special "all" lookup key.
159 """
160 all = self._accept | self._accept
161 return {a for a in all if a.name != self.matchAllKey.name}