Coverage for python/lsst/daf/butler/queries/tree/_base.py: 91%
47 statements
« prev ^ index » next coverage.py v7.4.3, created at 2024-03-05 11:36 +0000
« prev ^ index » next coverage.py v7.4.3, created at 2024-03-05 11:36 +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/>.
28from __future__ import annotations
30__all__ = (
31 "QueryTreeBase",
32 "ColumnExpressionBase",
33 "DatasetFieldName",
34 "InvalidQueryError",
35 "DATASET_FIELD_NAMES",
36)
38from abc import ABC, abstractmethod
39from typing import TYPE_CHECKING, Any, ClassVar, Literal, TypeAlias, TypeVar, cast, get_args
41import pydantic
43from ...column_spec import ColumnType
45if TYPE_CHECKING:
46 from ..visitors import ColumnExpressionVisitor
47 from ._column_literal import ColumnLiteral
48 from ._column_set import ColumnSet
51# Type annotation for string literals that can be used as dataset fields in
52# the public API. The 'collection' and 'run' fields are string collection
53# names. Internal interfaces may define other dataset field strings (e.g.
54# collection primary key values) and hence should use `str` rather than this
55# type.
56DatasetFieldName: TypeAlias = Literal["dataset_id", "ingest_date", "run", "collection", "timespan"]
58# Tuple of the strings that can be use as dataset fields in public APIs.
59DATASET_FIELD_NAMES: tuple[DatasetFieldName, ...] = tuple(get_args(DatasetFieldName))
61_T = TypeVar("_T")
62_L = TypeVar("_L")
63_A = TypeVar("_A")
64_O = TypeVar("_O")
67class InvalidQueryError(RuntimeError):
68 """Exception raised when a query is not valid."""
71class QueryTreeBase(pydantic.BaseModel):
72 """Base class for all non-primitive types in a query tree."""
74 model_config = pydantic.ConfigDict(frozen=True, extra="forbid", strict=True)
77class ColumnExpressionBase(QueryTreeBase, ABC):
78 """Base class for objects that represent non-boolean column expressions in
79 a query tree.
81 Notes
82 -----
83 This is a closed hierarchy whose concrete, `~typing.final` derived classes
84 are members of the `ColumnExpression` union. That union should be used in
85 type annotations rather than the technically-open base class.
86 """
88 expression_type: str
89 """String literal corresponding to a concrete expression type."""
91 is_literal: ClassVar[bool] = False
92 """Whether this expression wraps a literal Python value."""
94 @property
95 @abstractmethod
96 def precedence(self) -> int:
97 """Operator precedence for this operation.
99 Lower values bind more tightly, so parentheses are needed when printing
100 an expression where an operand has a higher value than the expression
101 itself.
102 """
103 raise NotImplementedError()
105 @property
106 @abstractmethod
107 def column_type(self) -> ColumnType:
108 """A string enumeration value representing the type of the column
109 expression.
110 """
111 raise NotImplementedError()
113 def get_literal_value(self) -> Any | None:
114 """Return the literal value wrapped by this expression, or `None` if
115 it is not a literal.
116 """
117 return None
119 @abstractmethod
120 def gather_required_columns(self, columns: ColumnSet) -> None:
121 """Add any columns required to evaluate this expression to the
122 given column set.
124 Parameters
125 ----------
126 columns : `ColumnSet`
127 Set of columns to modify in place.
128 """
129 raise NotImplementedError()
131 @abstractmethod
132 def visit(self, visitor: ColumnExpressionVisitor[_T]) -> _T:
133 """Invoke the visitor interface.
135 Parameters
136 ----------
137 visitor : `ColumnExpressionVisitor`
138 Visitor to invoke a method on.
140 Returns
141 -------
142 result : `object`
143 Forwarded result from the visitor.
144 """
145 raise NotImplementedError()
148class ColumnLiteralBase(ColumnExpressionBase):
149 """Base class for objects that represent literal values as column
150 expressions in a query tree.
152 Notes
153 -----
154 This is a closed hierarchy whose concrete, `~typing.final` derived classes
155 are members of the `ColumnLiteral` union. That union should be used in
156 type annotations rather than the technically-open base class. The concrete
157 members of that union are only semi-public; they appear in the serialized
158 form of a column expression tree, but should only be constructed via the
159 `make_column_literal` factory function. All concrete members of the union
160 are also guaranteed to have a read-only ``value`` attribute holding the
161 wrapped literal, but it is unspecified whether that is a regular attribute
162 or a `property` (and hence cannot be type-annotated).
163 """
165 is_literal: ClassVar[bool] = True
166 """Whether this expression wraps a literal Python value."""
168 @property
169 def precedence(self) -> int:
170 # Docstring inherited.
171 return 0
173 def get_literal_value(self) -> Any:
174 # Docstring inherited.
175 return cast("ColumnLiteral", self).value
177 def gather_required_columns(self, columns: ColumnSet) -> None:
178 # Docstring inherited.
179 pass
181 @property
182 def column_type(self) -> ColumnType:
183 # Docstring inherited.
184 return cast(ColumnType, self.expression_type)
186 def visit(self, visitor: ColumnExpressionVisitor[_T]) -> _T:
187 # Docstring inherited
188 return visitor.visit_literal(cast("ColumnLiteral", self))