Coverage for python / lsst / pipe / base / tests / mocks / _data_id_match.py: 40%
69 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 08:59 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 08:59 +0000
1# This file is part of pipe_base.
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/>.
28from __future__ import annotations
30__all__ = ["DataIdMatch"]
32import operator
33from collections.abc import Callable
34from typing import Any
35from uuid import UUID
37import astropy.time
39from lsst.daf.butler import DataId
40from lsst.daf.butler.queries.expressions.parser import Node, TreeVisitor, parse_expression
43class _DataIdMatchTreeVisitor(TreeVisitor):
44 """Expression tree visitor which evaluates expression using values from
45 `~lsst.daf.butler.DataId`.
46 """
48 def __init__(self, dataId: DataId):
49 self.dataId = dataId
51 def visitNumericLiteral(self, value: str, node: Node) -> Any:
52 # docstring is inherited from base class
53 try:
54 return int(value)
55 except ValueError:
56 return float(value)
58 def visitStringLiteral(self, value: str, node: Node) -> Any:
59 # docstring is inherited from base class
60 return value
62 def visitTimeLiteral(self, value: astropy.time.Time, node: Node) -> Any:
63 # docstring is inherited from base class
64 return value
66 def visitUuidLiteral(self, value: UUID, node: Node) -> Any:
67 # docstring is inherited from base class
68 return value
70 def visitRangeLiteral(self, start: int, stop: int, stride: int | None, node: Node) -> Any:
71 # docstring is inherited from base class
72 if stride is None:
73 return range(start, stop + 1)
74 else:
75 return range(start, stop + 1, stride)
77 def visitIdentifier(self, name: str, node: Node) -> Any:
78 # docstring is inherited from base class
79 return self.dataId[name]
81 def visitBind(self, name: str, node: Node) -> Any:
82 # docstring is inherited from base class
83 raise NotImplementedError()
85 def visitUnaryOp(self, operator_name: str, operand: Any, node: Node) -> Any:
86 # docstring is inherited from base class
87 operators: dict[str, Callable[[Any], Any]] = {
88 "NOT": operator.not_,
89 "+": operator.pos,
90 "-": operator.neg,
91 }
92 return operators[operator_name](operand)
94 def visitBinaryOp(self, operator_name: str, lhs: Any, rhs: Any, node: Node) -> Any:
95 # docstring is inherited from base class
96 operators = {
97 "OR": operator.or_,
98 "AND": operator.and_,
99 "+": operator.add,
100 "-": operator.sub,
101 "*": operator.mul,
102 "/": operator.truediv,
103 "%": operator.mod,
104 "=": operator.eq,
105 "!=": operator.ne,
106 "<": operator.lt,
107 ">": operator.gt,
108 "<=": operator.le,
109 ">=": operator.ge,
110 }
111 return operators[operator_name](lhs, rhs)
113 def visitIsIn(self, lhs: Any, values: list[Any], not_in: bool, node: Node) -> Any:
114 # docstring is inherited from base class
115 is_in = True
116 for value in values:
117 if not isinstance(value, range):
118 value = [value]
119 if lhs in value:
120 break
121 else:
122 is_in = False
123 if not_in:
124 is_in = not is_in
125 return is_in
127 def visitParens(self, expression: Any, node: Node) -> Any:
128 # docstring is inherited from base class
129 return expression
131 def visitTupleNode(self, items: tuple[Any, ...], node: Node) -> Any:
132 # docstring is inherited from base class
133 raise NotImplementedError()
135 def visitFunctionCall(self, name: str, args: list[Any], node: Node) -> Any:
136 # docstring is inherited from base class
137 raise NotImplementedError()
139 def visitPointNode(self, ra: Any, dec: Any, node: Node) -> Any:
140 # docstring is inherited from base class
141 raise NotImplementedError()
143 def visitCircleNode(self, ra: Any, dec: Any, radius: Any, node: Node) -> Any:
144 # docstring is inherited from base class
145 raise NotImplementedError()
147 def visitBoxNode(self, ra: Any, dec: Any, width: Any, height: Any, node: Node) -> Any:
148 # docstring is inherited from base class
149 raise NotImplementedError()
151 def visitPolygonNode(self, vertices: list[tuple[Any, Any]], node: Node) -> Any:
152 # docstring is inherited from base class
153 raise NotImplementedError()
155 def visitRegionNode(self, pos: Any, node: Node) -> Any:
156 # docstring is inherited from base class
157 raise NotImplementedError()
159 def visitGlobNode(self, expression: Any, pattern: Any, node: Node) -> Any:
160 # docstring is inherited from base class
161 raise NotImplementedError()
164class DataIdMatch:
165 """Class that can match DataId against the user-defined string expression.
167 Parameters
168 ----------
169 expression : `str`
170 User-defined expression, supports syntax defined by daf_butler
171 expression parser. Maps identifiers in the expression to the values of
172 DataId.
173 """
175 def __init__(self, expression: str):
176 self.expression = expression
177 self.tree = parse_expression(expression)
179 def match(self, dataId: DataId) -> bool:
180 """Match DataId contents against the expression.
182 Parameters
183 ----------
184 dataId : `~lsst.daf.butler.DataId`
185 DataId that is matched against an expression.
187 Returns
188 -------
189 match : `bool`
190 Result of expression evaluation.
192 Raises
193 ------
194 KeyError
195 Raised when identifier in expression is not defined for given
196 `~lsst.daf.butler.DataId`.
197 TypeError
198 Raised when expression evaluates to a non-boolean type or when
199 operation in expression cannot be performed on operand types.
200 NotImplementedError
201 Raised when expression includes valid but unsupported syntax, e.g.
202 function call.
203 """
204 if self.tree is None:
205 return True
206 visitor = _DataIdMatchTreeVisitor(dataId)
207 result = self.tree.visit(visitor)
208 if not isinstance(result, bool):
209 raise TypeError(f"Expression '{self.expression}' returned non-boolean object {type(result)}")
210 return result