Coverage for python/lsst/daf/relation/_marker_relation.py: 63%
41 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-10 10:30 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-10 10:30 +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# (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 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 <http://www.gnu.org/licenses/>.
22from __future__ import annotations
24__all__ = ("MarkerRelation",)
26import dataclasses
27from collections.abc import Set
28from typing import TYPE_CHECKING, Any, final
30from ._relation import BaseRelation, Relation
32if TYPE_CHECKING: 32 ↛ 33line 32 didn't jump to line 33, because the condition on line 32 was never true
33 from ._columns import ColumnTag
34 from ._engine import Engine
37@dataclasses.dataclass(frozen=True)
38class MarkerRelation(BaseRelation):
39 """An extensible relation base class that provides additional information
40 about another relation without changing its row-and-column content.
42 As with all other relation types, `MarkerRelation` subclasses should be
43 frozen dataclasses. Since `MarkerRelation` is itself a dataclass, it is
44 not formally an abstract base class, but it should be considered one
45 conceptually.
46 """
48 target: Relation
49 """The target relation the marker wraps (`Relation`).
50 """
52 payload: Any = dataclasses.field(repr=False, compare=False, default=None)
53 """The engine-specific contents of the relation."""
55 @final
56 @property
57 def columns(self) -> Set[ColumnTag]:
58 """The columns in this relation
59 (`~collections.abc.Set` [ `ColumnTag` ] ).
60 """
61 return self.target.columns
63 @property
64 def engine(self) -> Engine:
65 """The engine that is responsible for interpreting this relation
66 (`Engine`).
67 """
68 return self.target.engine
70 @property
71 def is_locked(self) -> bool:
72 """Whether this relation and those upstream of it should be considered
73 fixed by tree-manipulation algorithms (`bool`).
74 """
75 return False
77 @final
78 @property
79 def min_rows(self) -> int:
80 """The minimum number of rows this relation might have (`int`)."""
81 return self.target.min_rows
83 @final
84 @property
85 def max_rows(self) -> int | None:
86 """The maximum number of rows this relation might have (`int` or
87 `None`).
89 This is `None` for relations whose size is not bounded from above.
90 """
91 return self.target.max_rows
93 def attach_payload(self: Relation, payload: Any) -> None:
94 """Attach an engine-specific `payload` to this relation.
96 This method may be called exactly once on a `MarkerRelation` instance
97 that was not initialized with a `payload`, despite the fact that
98 `Relation` objects are otherwise considered immutable.
100 Parameters
101 ----------
102 payload
103 Engine-specific content to attach.
105 Raises
106 ------
107 TypeError
108 Raised if this relation already has a payload, or if this marker
109 subclass can never have a payload. `TypeError` is used here for
110 consistency with other attempts to assign to an attribute of an
111 immutable object.
112 """
113 if self.payload is None:
114 object.__setattr__(self, "payload", payload)
115 else:
116 raise TypeError(
117 f"Cannot attach payload {payload} to relation {self} with existing payload "
118 f"{self.payload}; relation payloads are write-once."
119 )
121 def reapply(self, target: Relation, payload: Any | None = None) -> MarkerRelation:
122 """Mark a new target relation, returning a new instance of the same
123 type.
125 Parameters
126 ----------
127 target : `Relation`
128 New relation to mark.
129 payload, optional
130 Payload to attach to the new relation.
132 Returns
133 -------
134 relation : `MarkerRelation`
135 A new relation with the given target.
137 Notes
138 -----
139 This method is primarily intended for use by operations that "unroll"
140 a relation tree to perform some modification upstream and then "replay"
141 the operations and markers that were downstream. `MarkerRelation`
142 implementations with state that depends on the target will need to
143 override this method to update that state accordingly.
144 """
145 if target is self.target and payload is self.payload:
146 return self
147 return dataclasses.replace(self, target=target, payload=payload)