Coverage for python/lsst/daf/relation/_leaf_relation.py: 72%
46 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-22 02:59 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-22 02:59 -0800
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__ = ("LeafRelation",)
26import dataclasses
27from collections.abc import Sequence, Set
28from typing import TYPE_CHECKING, Any, Literal, final
30from ._relation import BaseRelation
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@final
38@dataclasses.dataclass(frozen=True)
39class LeafRelation(BaseRelation):
40 """A `Relation` class that represents direct storage of rows, rather than
41 an operation on some other relation.
42 """
44 engine: Engine = dataclasses.field(repr=False, compare=True)
45 """The engine that is responsible for interpreting this relation
46 (`Engine`).
47 """
49 columns: frozenset[ColumnTag] = dataclasses.field(repr=False, compare=True)
50 """The columns in this relation (`~collections.abc.Set` [ `ColumnTag` ] ).
51 """
53 payload: Any = dataclasses.field(repr=False, compare=False)
54 """The engine-specific contents of the relation."""
56 name: str = dataclasses.field(repr=True, compare=True, default="")
57 """Name used to identify and reconstruct this relation (`str`)."""
59 name_prefix: dataclasses.InitVar[str | None] = "leaf"
60 """Prefix used when calling `Engine.get_relation_name` when `name` is not
61 provided (`str`).
62 """
64 messages: Sequence[str] = dataclasses.field(repr=False, compare=False, default=())
65 """Messages for use when processing the relation with the `Diagnostics`
66 class or similar algorithms (`~collections.abc.Sequence` [ `str` ]).
68 This is typically used to explain why a leaf relation has no rows when
69 ``max_rows==0``; see `make_doomed`.
70 """
72 parameters: Any = dataclasses.field(repr=True, compare=True, default=None)
73 """Extra data used to uniquely identify and/or reconstruct this relation.
74 """
76 min_rows: int = dataclasses.field(repr=False, compare=False, default=0)
77 """The minimum number of rows this relation might have (`int`)."""
79 max_rows: int | None = dataclasses.field(repr=False, compare=False, default=None)
80 """The maximum number of rows this relation might have (`int` or `None`).
81 """
83 @property
84 def is_locked(self) -> Literal[True]:
85 """Whether this relation and those upstream of it should be considered
86 fixed by tree-manipulation algorithms (`bool`).
88 See `Relation.is_locked`.
89 """
90 return True
92 def __post_init__(self, name_prefix: str = "leaf") -> None:
93 if not self.name:
94 object.__setattr__(self, "name", self.engine.get_relation_name(name_prefix))
95 if self.max_rows is not None and self.max_rows < self.min_rows:
96 raise ValueError(f"max_rows ({self.max_rows}) < min_rows ({self.min_rows})")
98 @classmethod
99 def make_doomed(
100 cls, engine: Engine, columns: Set[ColumnTag], messages: Sequence[str], name: str = "0"
101 ) -> LeafRelation:
102 """Construct a leaf relation with no rows and one or more messages
103 explaining why.
105 Parameters
106 ----------
107 engine : `Engine`
108 The engine that is responsible for interpreting this relation.
109 columns : `~collections.abc.Set` [ `ColumnTag` ]
110 The columns in this relation.
111 messages : `~collections.abc.Sequence [ `str` ]
112 One or more messages explaining why the relation has no rows.
113 name : `str`, optional
114 Name used to identify and reconstruct this relation.
116 Returns
117 -------
118 relation : `LeafRelation`
119 Doomed leaf relation.
120 """
121 return LeafRelation(
122 engine=engine,
123 columns=frozenset(columns),
124 min_rows=0,
125 max_rows=0,
126 payload=engine.get_doomed_payload(columns),
127 name=name,
128 messages=messages,
129 )
131 @classmethod
132 def make_join_identity(cls, engine: Engine, name: str = "I") -> LeafRelation:
133 """Construct a leaf relation with no columns and exactly one row.
135 Parameters
136 ----------
137 engine : `Engine`
138 The engine that is responsible for interpreting this relation.
139 name : `str`, optional
140 Name used to identify and reconstruct this relation.
142 Returns
143 -------
144 relation : `LeafRelation`
145 Leaf relation with no columns and one row.
146 """
147 return LeafRelation(
148 engine=engine,
149 columns=frozenset(),
150 min_rows=1,
151 max_rows=1,
152 payload=engine.get_join_identity_payload(),
153 name=name,
154 )
156 def __str__(self) -> str:
157 return self.name if self.parameters is None else f"{self.name}({self.parameters})"