Coverage for python/lsst/daf/relation/_columns/_container.py: 80%
73 statements
« prev ^ index » next coverage.py v7.3.0, created at 2023-08-19 09:55 +0000
« prev ^ index » next coverage.py v7.3.0, created at 2023-08-19 09:55 +0000
1# This file is part of daf_relation.
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 program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22from __future__ import annotations
24__all__ = (
25 "ColumnContainer",
26 "ColumnRangeLiteral",
27 "ColumnExpressionSequence",
28 "ColumnInContainer",
29)
31import dataclasses
32from abc import ABC, abstractmethod
33from collections.abc import Sequence, Set
34from typing import TYPE_CHECKING
36from lsst.utils.classes import cached_getter
38from ._predicate import Predicate
39from ._tag import ColumnTag
41if TYPE_CHECKING:
42 from .._engine import Engine
43 from ._expression import ColumnExpression
46class ColumnContainer(ABC):
47 """A abstract base class and factory for expressions that represent
48 containers of multiple column values.
50 `ColumnContainer` inheritance is closed to the types already provided by
51 this package. These concrete types can all be constructed via factory
52 methods on `ColumnContainer` itself, so the derived types themselves only
53 need to be referenced when writing `match` expressions that process an
54 expression tree. See :ref:`lsst.daf.relation-overview-extensibility` for
55 rationale and details.
56 """
58 def __init_subclass__(cls) -> None:
59 assert cls.__name__ in {
60 "ColumnRangeLiteral",
61 "ColumnExpressionSequence",
62 }, "ColumnContainer inheritance is closed to predefined types in daf_relation."
64 dtype: type | None
65 """The Python type of the elements in the container (`type` or `None`).
67 Interpretation of this attribute is up to the `Engine` or other algorithms
68 that operate on the expression tree; it is ignored by all code in the
69 `lsst.daf.relation` package.
70 """
72 @property
73 @abstractmethod
74 def columns_required(self) -> Set[ColumnTag]:
75 """Columns required by this expression
76 (`~collections.abc.Set` [ `ColumnTag` ]).
78 This includes columns required by expressions nested within this one.
79 """
80 raise NotImplementedError()
82 @abstractmethod
83 def is_supported_by(self, engine: Engine) -> bool:
84 """Test whether the given engine is capable of evaluating this
85 expression.
87 Parameters
88 ----------
89 engine : `Engine`
90 Engine to test.
92 Returns
93 -------
94 supported : `bool`
95 Whether the engine supports this expression and all expressions
96 nested within it.
97 """
98 raise NotImplementedError()
100 def contains(self, item: ColumnExpression) -> ColumnInContainer:
101 """Construct a boolean column expression that tests whether a scalar
102 expression is present in this container expression.
104 Parameters
105 ----------
106 item : `ColumnExpression`
107 Item expression to test.
109 Returns
110 -------
111 contains : `ColumnInContainer`
112 Boolean column expression that tests for membership in the
113 container.
114 """
115 return ColumnInContainer(item, self)
117 @classmethod
118 def range_literal(cls, r: range) -> ColumnRangeLiteral:
119 """Construct a container expression from a range of indices.
121 Parameters
122 ----------
123 r : `range`
124 Range object.
126 Returns
127 -------
128 container : `ColumnRangeLiteral`
129 Container expression object representing the range.
130 """
131 return ColumnRangeLiteral(r)
133 @classmethod
134 def sequence(
135 cls, items: Sequence[ColumnExpression], dtype: type | None = None
136 ) -> ColumnExpressionSequence:
137 """Construct a container expression from a sequence of item
138 expressions.
140 Parameters
141 ----------
142 items : `~collections.abc.Sequence` [ `ColumnExpression` ]
143 Sequence of item expressions.
144 dtype : `type`, optional
145 The Python type of the elements in the container.
147 Returns
148 -------
149 container : `ColumnExpressionSequence`
150 Container expression object backed by the given items.
151 """
152 return ColumnExpressionSequence(items, dtype)
155@dataclasses.dataclass(frozen=True)
156class ColumnRangeLiteral(ColumnContainer):
157 """A container expression backed by a range of integer indices."""
159 value: range
160 """Range value that backs the expression (`range`).
161 """
163 dtype: type[int] = int
164 """The Python type of the elements in the container (`type` [ `int` ] )."""
166 @property
167 def columns_required(self) -> Set[ColumnTag]:
168 # Docstring inherited.
169 return frozenset()
171 def __str__(self) -> str:
172 return f"[{self.value.start}:{self.value.stop}:{self.value.step}]"
174 def is_supported_by(self, engine: Engine) -> bool:
175 # Docstring inherited.
176 return True
179@dataclasses.dataclass(frozen=True)
180class ColumnExpressionSequence(ColumnContainer):
181 """A container expression backed by a sequence of scalar column
182 expressions.
183 """
185 items: Sequence[ColumnExpression]
186 """Sequence of item expressions
187 (`~collections.abc.Sequence` [ `ColumnExpression` ]).
188 """
190 dtype: type | None
191 """The Python type of the elements in the container (`type` or `None`)."""
193 @property
194 @cached_getter
195 def columns_required(self) -> Set[ColumnTag]:
196 # Docstring inherited.
197 result: set[ColumnTag] = set()
198 for item in self.items:
199 result.update(item.columns_required)
200 return result
202 def __str__(self) -> str:
203 return f"[{', '.join(str(i) for i in self.items)}]"
205 def is_supported_by(self, engine: Engine) -> bool:
206 # Docstring inherited.
207 return all(item.is_supported_by(engine) for item in self.items)
210@dataclasses.dataclass(frozen=True)
211class ColumnInContainer(Predicate):
212 """A boolean column expression that tests whether a scalar column
213 expression is present in a container expression.
214 """
216 item: ColumnExpression
217 """Item to be tested (`ColumnExpression`)."""
219 container: ColumnContainer
220 """Container to be tested (`ColumnContainer`)."""
222 @property
223 @cached_getter
224 def columns_required(self) -> Set[ColumnTag]:
225 # Docstring inherited.
226 return self.item.columns_required | self.container.columns_required
228 def __str__(self) -> str:
229 return f"{self.item}∈{self.container}"
231 def is_supported_by(self, engine: Engine) -> bool:
232 # Docstring inherited.
233 return self.item.is_supported_by(engine) and self.container.is_supported_by(engine)
235 def as_trivial(self) -> None:
236 # Docstring inherited.
237 return None