Coverage for python/lsst/daf/butler/queries/tree/_base.py: 91%
43 statements
« prev ^ index » next coverage.py v7.4.3, created at 2024-03-12 10:07 +0000
« prev ^ index » next coverage.py v7.4.3, created at 2024-03-12 10:07 +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 is_column_reference: ClassVar[bool] = False
95 """Whether this expression wraps a direct reference to column."""
97 @property
98 @abstractmethod
99 def column_type(self) -> ColumnType:
100 """A string enumeration value representing the type of the column
101 expression.
102 """
103 raise NotImplementedError()
105 def get_literal_value(self) -> Any | None:
106 """Return the literal value wrapped by this expression, or `None` if
107 it is not a literal.
108 """
109 return None
111 @abstractmethod
112 def gather_required_columns(self, columns: ColumnSet) -> None:
113 """Add any columns required to evaluate this expression to the
114 given column set.
116 Parameters
117 ----------
118 columns : `ColumnSet`
119 Set of columns to modify in place.
120 """
121 raise NotImplementedError()
123 @abstractmethod
124 def visit(self, visitor: ColumnExpressionVisitor[_T]) -> _T:
125 """Invoke the visitor interface.
127 Parameters
128 ----------
129 visitor : `ColumnExpressionVisitor`
130 Visitor to invoke a method on.
132 Returns
133 -------
134 result : `object`
135 Forwarded result from the visitor.
136 """
137 raise NotImplementedError()
140class ColumnLiteralBase(ColumnExpressionBase):
141 """Base class for objects that represent literal values as column
142 expressions in a query tree.
144 Notes
145 -----
146 This is a closed hierarchy whose concrete, `~typing.final` derived classes
147 are members of the `ColumnLiteral` union. That union should be used in
148 type annotations rather than the technically-open base class. The concrete
149 members of that union are only semi-public; they appear in the serialized
150 form of a column expression tree, but should only be constructed via the
151 `make_column_literal` factory function. All concrete members of the union
152 are also guaranteed to have a read-only ``value`` attribute holding the
153 wrapped literal, but it is unspecified whether that is a regular attribute
154 or a `property` (and hence cannot be type-annotated).
155 """
157 is_literal: ClassVar[bool] = True
158 """Whether this expression wraps a literal Python value."""
160 def get_literal_value(self) -> Any:
161 # Docstring inherited.
162 return cast("ColumnLiteral", self).value
164 def gather_required_columns(self, columns: ColumnSet) -> None:
165 # Docstring inherited.
166 pass
168 @property
169 def column_type(self) -> ColumnType:
170 # Docstring inherited.
171 return cast(ColumnType, self.expression_type)
173 def visit(self, visitor: ColumnExpressionVisitor[_T]) -> _T:
174 # Docstring inherited
175 return visitor.visit_literal(cast("ColumnLiteral", self))