Coverage for python / lsst / daf / butler / queries / expressions / parser / treeVisitor.py: 98%

45 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-22 08:55 +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# (https://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 <https://www.gnu.org/licenses/>. 

27 

28from __future__ import annotations 

29 

30__all__ = ["TreeVisitor"] 

31 

32from abc import ABC, abstractmethod 

33from typing import TYPE_CHECKING, Generic, TypeVar 

34from uuid import UUID 

35 

36if TYPE_CHECKING: 

37 import astropy.time 

38 

39 from .exprTree import ( 

40 BoxNode, 

41 CircleNode, 

42 GlobNode, 

43 Node, 

44 PointNode, 

45 PolygonNode, 

46 RangeLiteral, 

47 RegionNode, 

48 ) 

49 

50 

51T = TypeVar("T") 

52 

53 

54class TreeVisitor(Generic[T], ABC): 

55 """Definition of interface for visitor classes. 

56 

57 Visitors and tree node classes implement Visitor pattern for tree 

58 traversal. Typical use case is to generate different representation 

59 of the tree, e.g. transforming parsed tree into SQLAlchemy clause. 

60 

61 All methods of the class can (and most likely should) return the 

62 "transformed" value of the visited node. This value will be returned 

63 from the `Node.visit` method and it will also be passed as an argument 

64 to other methods of the visitor. 

65 """ 

66 

67 @abstractmethod 

68 def visitNumericLiteral(self, value: str, node: Node) -> T: 

69 """Visit NumericLiteral node. 

70 

71 Parameters 

72 ---------- 

73 value : `str` 

74 The value associated with the visited node, the value is string, 

75 exactly as it appears in the original expression. Depending on 

76 use case it may need to be converted to `int` or `float`. 

77 node : `Node` 

78 Corresponding tree node, mostly useful for diagnostics. 

79 """ 

80 

81 @abstractmethod 

82 def visitStringLiteral(self, value: str, node: Node) -> T: 

83 """Visit StringLiteral node. 

84 

85 Parameters 

86 ---------- 

87 value : `str` 

88 The value associated with the visited node. 

89 node : `Node` 

90 Corresponding tree node, mostly useful for diagnostics. 

91 """ 

92 

93 @abstractmethod 

94 def visitUuidLiteral(self, value: UUID, node: Node) -> T: 

95 """Visit UuidLiteral node. 

96 

97 Parameters 

98 ---------- 

99 value : `UUID` 

100 The value associated with the visited node, the value is UUID. 

101 node : `Node` 

102 Corresponding tree node, mostly useful for diagnostics. 

103 """ 

104 

105 @abstractmethod 

106 def visitTimeLiteral(self, value: astropy.time.Time, node: Node) -> T: 

107 """Visit TimeLiteral node. 

108 

109 Parameters 

110 ---------- 

111 value : `astropy.time.Time` 

112 The value associated with the visited node. 

113 node : `Node` 

114 Corresponding tree node, mostly useful for diagnostics. 

115 """ 

116 

117 @abstractmethod 

118 def visitRangeLiteral(self, start: int, stop: int, stride: int | None, node: RangeLiteral) -> T: 

119 """Visit RangeLiteral node. 

120 

121 Parameters 

122 ---------- 

123 start : `int` 

124 Range starting value. 

125 stop : `int` 

126 Range final value. 

127 stride : `int` or `None` 

128 Stride, can be `None` if not specified (should be treated same 

129 as 1). 

130 node : `Node` 

131 Corresponding tree node, mostly useful for diagnostics. 

132 """ 

133 

134 @abstractmethod 

135 def visitIdentifier(self, name: str, node: Node) -> T: 

136 """Visit Identifier node. 

137 

138 Parameters 

139 ---------- 

140 name : `str` 

141 Identifier name. 

142 node : `Node` 

143 Corresponding tree node, mostly useful for diagnostics. 

144 """ 

145 

146 @abstractmethod 

147 def visitBind(self, name: str, node: Node) -> T: 

148 """Visit BindName node. 

149 

150 Parameters 

151 ---------- 

152 name : `str` 

153 Bind name. 

154 node : `Node` 

155 Corresponding tree node, mostly useful for diagnostics. 

156 """ 

157 

158 @abstractmethod 

159 def visitUnaryOp(self, operator: str, operand: T, node: Node) -> T: 

160 """Visit UnaryOp node. 

161 

162 Parameters 

163 ---------- 

164 operator : `str` 

165 Operator name, e.g. "NOT" or "+". 

166 operand : `object` 

167 Operand, this object is returned by one of the methods of this 

168 class as a result of transformation of some other tree node. 

169 node : `Node` 

170 Corresponding tree node, mostly useful for diagnostics. 

171 """ 

172 

173 @abstractmethod 

174 def visitBinaryOp(self, operator: str, lhs: T, rhs: T, node: Node) -> T: 

175 """Visit BinaryOp node. 

176 

177 Parameters 

178 ---------- 

179 operator : `str` 

180 Operator name, e.g. "NOT" or "+". 

181 lhs : `object` 

182 Left hand side operand, this object is returned by one of the 

183 methods of this class as a result of transformation of some other 

184 tree node. 

185 rhs : `object` 

186 Right hand side operand, this object is returned by one of the 

187 methods of this class as a result of transformation of some other 

188 tree node. 

189 node : `Node` 

190 Corresponding tree node, mostly useful for diagnostics. 

191 """ 

192 

193 @abstractmethod 

194 def visitIsIn(self, lhs: T, values: list[T], not_in: bool, node: Node) -> T: 

195 """Visit IsIn node. 

196 

197 Parameters 

198 ---------- 

199 lhs : `object` 

200 Left hand side operand, this object is returned by one of the 

201 methods of this class as a result of transformation of some other 

202 tree node. 

203 values : `list` of `object` 

204 Right hand side operand, list of objects returned by methods of 

205 this class as a result of transformation of some other tree nodes. 

206 not_in : `bool` 

207 `True` for "NOT IN" expression. 

208 node : `Node` 

209 Corresponding tree node, mostly useful for diagnostics. 

210 """ 

211 

212 @abstractmethod 

213 def visitParens(self, expression: T, node: Node) -> T: 

214 """Visit Parens node. 

215 

216 Parameters 

217 ---------- 

218 expression : `object` 

219 Expression inside parentheses, this object is returned by one of 

220 the methods of this class as a result of transformation of some 

221 other tree node. 

222 node : `Node` 

223 Corresponding tree node, mostly useful for diagnostics. 

224 """ 

225 

226 @abstractmethod 

227 def visitTupleNode(self, items: tuple[T, ...], node: Node) -> T: 

228 """Visit TupleNode node. 

229 

230 Parameters 

231 ---------- 

232 items : `tuple` of `object` 

233 Expressions inside parentheses, tuple of objects returned by one 

234 of the methods of this class as a result of transformation of 

235 tuple items. 

236 node : `Node` 

237 Corresponding tree node, mostly useful for diagnostics. 

238 """ 

239 

240 def visitFunctionCall(self, name: str, args: list[T], node: Node) -> T: 

241 """Visit FunctionCall node. 

242 

243 Parameters 

244 ---------- 

245 name : `str` 

246 Name of the function. 

247 args : `list` of `object` 

248 Arguments to function, list of objects returned by methods of 

249 this class as a result of transformation of function arguments. 

250 node : `Node` 

251 Corresponding tree node, mostly useful for diagnostics. 

252 

253 Notes 

254 ----- 

255 For now we only have to support a small set of functions, and for those 

256 functions we define special node types (e.g. `PointNode`, `GlobNode`) 

257 and special visit methods. `FunctionCall` node type represents a 

258 generic function and regular visitors do not handle generic function. 

259 This non-abstract method is a common implementation for those visitors 

260 which raises an exception. 

261 """ 

262 raise ValueError(f"Unknown function '{name}' in expression") 

263 

264 @abstractmethod 

265 def visitPointNode(self, ra: T, dec: T, node: PointNode) -> T: 

266 """Visit PointNode node. 

267 

268 Parameters 

269 ---------- 

270 ra, dec : `object` 

271 Representation of 'ra' and 'dec' values, objects returned by 

272 methods of this class as a result of transformation of function 

273 arguments. 

274 node : `PointNode` 

275 Corresponding tree node, mostly useful for diagnostics. 

276 """ 

277 

278 @abstractmethod 

279 def visitCircleNode(self, ra: T, dec: T, radius: T, node: CircleNode) -> T: 

280 """Visit CircleNode node. 

281 

282 Parameters 

283 ---------- 

284 ra, dec, radius : `object` 

285 Representation of 'ra', 'dec', and 'radius' values, objects 

286 returned by methods of this class as a result of transformation of 

287 function arguments. 

288 node : `CircleNode` 

289 Corresponding tree node, mostly useful for diagnostics. 

290 """ 

291 

292 @abstractmethod 

293 def visitBoxNode(self, ra: T, dec: T, width: T, height: T, node: BoxNode) -> T: 

294 """Visit BoxNode node. 

295 

296 Parameters 

297 ---------- 

298 ra, dec, width, height : `object` 

299 Representation of BoxNode parameters values, objects returned by 

300 methods of this class as a result of transformation of function 

301 arguments. 

302 node : `BoxNode` 

303 Corresponding tree node, mostly useful for diagnostics. 

304 """ 

305 

306 @abstractmethod 

307 def visitPolygonNode(self, vertices: list[tuple[T, T]], node: PolygonNode) -> T: 

308 """Visit BoxNode node. 

309 

310 Parameters 

311 ---------- 

312 vertices : `list` [`tuple`] 

313 Representation of BoxNode vertices, objects returned by 

314 methods of this class as a result of transformation of function 

315 arguments. 

316 node : `PolygonNode` 

317 Corresponding tree node, mostly useful for diagnostics. 

318 """ 

319 

320 @abstractmethod 

321 def visitRegionNode(self, pos: T, node: RegionNode) -> T: 

322 """Visit RegionNode node. 

323 

324 Parameters 

325 ---------- 

326 pos : `object` 

327 Representation of RegionNode argument, object returned by 

328 methods of this class as a result of transformation of function 

329 arguments. 

330 node : `RegionNode` 

331 Corresponding tree node, mostly useful for diagnostics. 

332 """ 

333 

334 @abstractmethod 

335 def visitGlobNode(self, expression: T, pattern: T, node: GlobNode) -> T: 

336 """Visit GlobNode node. 

337 

338 Parameters 

339 ---------- 

340 expression, pattern : `object` 

341 Representation of 'pattern' and 'expression' values, objects 

342 returned by methods of this class as a result of transformation of 

343 function arguments. 

344 node : `GlobNode` 

345 Corresponding tree node, mostly useful for diagnostics. 

346 """