Coverage for python/lsst/daf/relation/_leaf_relation.py: 79%

43 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-06 10:42 +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/>. 

21 

22from __future__ import annotations 

23 

24__all__ = ("LeafRelation",) 

25 

26import dataclasses 

27from collections.abc import Sequence, Set 

28from typing import TYPE_CHECKING, Any, Literal, final 

29 

30from ._relation import BaseRelation 

31 

32if TYPE_CHECKING: 

33 from ._columns import ColumnTag 

34 from ._engine import Engine 

35 

36 

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 """ 

43 

44 engine: Engine = dataclasses.field(repr=False, compare=True) 

45 """The engine that is responsible for interpreting this relation 

46 (`Engine`). 

47 """ 

48 

49 columns: frozenset[ColumnTag] = dataclasses.field(repr=False, compare=True) 

50 """The columns in this relation (`~collections.abc.Set` [ `ColumnTag` ] ). 

51 """ 

52 

53 payload: Any = dataclasses.field(repr=False, compare=False) 

54 """The engine-specific contents of the relation.""" 

55 

56 name: str = dataclasses.field(repr=True, compare=True, default="") 

57 """Name used to identify and reconstruct this relation (`str`).""" 

58 

59 name_prefix: dataclasses.InitVar[str] = "leaf" 

60 """Prefix used when calling `Engine.get_relation_name` when `name` is not 

61 provided (`str`). 

62 """ 

63 

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` ]). 

67 

68 This is typically used to explain why a leaf relation has no rows when 

69 ``max_rows==0``; see `make_doomed`. 

70 """ 

71 

72 parameters: Any = dataclasses.field(repr=True, compare=True, default=None) 

73 """Extra data used to uniquely identify and/or reconstruct this relation. 

74 """ 

75 

76 min_rows: int = dataclasses.field(repr=False, compare=False, default=0) 

77 """The minimum number of rows this relation might have (`int`).""" 

78 

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 """ 

82 

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`). 

87 

88 See `Relation.is_locked`. 

89 """ 

90 return True 

91 

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})") 

97 

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. 

104 

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. 

115 

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 ) 

130 

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. 

134 

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. 

141 

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 ) 

155 

156 def __str__(self) -> str: 

157 return self.name if self.parameters is None else f"{self.name}({self.parameters})"