Coverage for python/lsst/daf/butler/queries/tree/_base.py: 91%
42 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-26 02:47 -0700
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-26 02:47 -0700
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 "DATASET_FIELD_NAMES",
35)
37from abc import ABC, abstractmethod
38from typing import TYPE_CHECKING, Any, ClassVar, Literal, TypeAlias, TypeVar, cast, get_args
40import pydantic
42from ...column_spec import ColumnType
44if TYPE_CHECKING:
45 from ..visitors import ColumnExpressionVisitor
46 from ._column_literal import ColumnLiteral
47 from ._column_set import ColumnSet
50# Type annotation for string literals that can be used as dataset fields in
51# the public API. The 'collection' and 'run' fields are string collection
52# names. Internal interfaces may define other dataset field strings (e.g.
53# collection primary key values) and hence should use `str` rather than this
54# type.
55DatasetFieldName: TypeAlias = Literal["dataset_id", "ingest_date", "run", "collection", "timespan"]
57# Tuple of the strings that can be use as dataset fields in public APIs.
58DATASET_FIELD_NAMES: tuple[DatasetFieldName, ...] = tuple(get_args(DatasetFieldName))
60_T = TypeVar("_T")
61_L = TypeVar("_L")
62_A = TypeVar("_A")
63_O = TypeVar("_O")
66class QueryTreeBase(pydantic.BaseModel):
67 """Base class for all non-primitive types in a query tree."""
69 model_config = pydantic.ConfigDict(frozen=True, extra="forbid", strict=True)
72class ColumnExpressionBase(QueryTreeBase, ABC):
73 """Base class for objects that represent non-boolean column expressions in
74 a query tree.
76 Notes
77 -----
78 This is a closed hierarchy whose concrete, `~typing.final` derived classes
79 are members of the `ColumnExpression` union. That union should be used in
80 type annotations rather than the technically-open base class.
81 """
83 expression_type: str
84 """String literal corresponding to a concrete expression type."""
86 is_literal: ClassVar[bool] = False
87 """Whether this expression wraps a literal Python value."""
89 is_column_reference: ClassVar[bool] = False
90 """Whether this expression wraps a direct reference to column."""
92 @property
93 @abstractmethod
94 def column_type(self) -> ColumnType:
95 """A string enumeration value representing the type of the column
96 expression.
97 """
98 raise NotImplementedError()
100 def get_literal_value(self) -> Any | None:
101 """Return the literal value wrapped by this expression, or `None` if
102 it is not a literal.
103 """
104 return None
106 @abstractmethod
107 def gather_required_columns(self, columns: ColumnSet) -> None:
108 """Add any columns required to evaluate this expression to the
109 given column set.
111 Parameters
112 ----------
113 columns : `ColumnSet`
114 Set of columns to modify in place.
115 """
116 raise NotImplementedError()
118 @abstractmethod
119 def visit(self, visitor: ColumnExpressionVisitor[_T]) -> _T:
120 """Invoke the visitor interface.
122 Parameters
123 ----------
124 visitor : `ColumnExpressionVisitor`
125 Visitor to invoke a method on.
127 Returns
128 -------
129 result : `object`
130 Forwarded result from the visitor.
131 """
132 raise NotImplementedError()
135class ColumnLiteralBase(ColumnExpressionBase):
136 """Base class for objects that represent literal values as column
137 expressions in a query tree.
139 Notes
140 -----
141 This is a closed hierarchy whose concrete, `~typing.final` derived classes
142 are members of the `ColumnLiteral` union. That union should be used in
143 type annotations rather than the technically-open base class. The concrete
144 members of that union are only semi-public; they appear in the serialized
145 form of a column expression tree, but should only be constructed via the
146 `make_column_literal` factory function. All concrete members of the union
147 are also guaranteed to have a read-only ``value`` attribute holding the
148 wrapped literal, but it is unspecified whether that is a regular attribute
149 or a `property` (and hence cannot be type-annotated).
150 """
152 is_literal: ClassVar[bool] = True
153 """Whether this expression wraps a literal Python value."""
155 def get_literal_value(self) -> Any:
156 # Docstring inherited.
157 return cast("ColumnLiteral", self).value
159 def gather_required_columns(self, columns: ColumnSet) -> None:
160 # Docstring inherited.
161 pass
163 @property
164 def column_type(self) -> ColumnType:
165 # Docstring inherited.
166 return cast(ColumnType, self.expression_type)
168 def visit(self, visitor: ColumnExpressionVisitor[_T]) -> _T:
169 # Docstring inherited
170 return visitor.visit_literal(cast("ColumnLiteral", self))