Coverage for python/lsst/daf/relation/_marker_relation.py: 63%

41 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-03 02:24 -0700

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__ = ("MarkerRelation",) 

25 

26import dataclasses 

27from collections.abc import Set 

28from typing import TYPE_CHECKING, Any, final 

29 

30from ._relation import BaseRelation, Relation 

31 

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 

35 

36 

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. 

41 

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

47 

48 target: Relation 

49 """The target relation the marker wraps (`Relation`). 

50 """ 

51 

52 payload: Any = dataclasses.field(repr=False, compare=False, default=None) 

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

54 

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 

62 

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 

69 

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 

76 

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 

82 

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

88 

89 This is `None` for relations whose size is not bounded from above. 

90 """ 

91 return self.target.max_rows 

92 

93 def attach_payload(self: Relation, payload: Any) -> None: 

94 """Attach an engine-specific `payload` to this relation. 

95 

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. 

99 

100 Parameters 

101 ---------- 

102 payload 

103 Engine-specific content to attach. 

104 

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 ) 

120 

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. 

124 

125 Parameters 

126 ---------- 

127 target : `Relation` 

128 New relation to mark. 

129 payload, optional 

130 Payload to attach to the new relation. 

131 

132 Returns 

133 ------- 

134 relation : `MarkerRelation` 

135 A new relation with the given target. 

136 

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)