Skip to content

Commit 56ae688

Browse files
authored
Rename OnlyIfPrevious to WithPrevious (frequenz-floss#357)
The new name makes is more clear that this predicate is adding an extra condition to the message filtering based on the previous message. Fixes frequenz-floss#355.
2 parents 4c7f2c1 + 474a1df commit 56ae688

File tree

5 files changed

+31
-213
lines changed

5 files changed

+31
-213
lines changed

RELEASE_NOTES.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@
44

55
### Experimental
66

7-
- A new predicate, `OnlyIfPrevious`, to `filter()` messages based on the previous message.
8-
- A new special case of `OnlyIfPrevious`, `ChangedOnly`, to skip messages if they are equal to the previous message.
7+
- A new composable predicate, `WithPrevious`, to filter messages based on the previous message.

src/frequenz/channels/experimental/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@
1010
"""
1111

1212
from ._pipe import Pipe
13-
from ._predicates import ChangedOnly, OnlyIfPrevious
1413
from ._relay_sender import RelaySender
14+
from ._with_previous import WithPrevious
1515

1616
__all__ = [
17-
"ChangedOnly",
18-
"OnlyIfPrevious",
17+
"WithPrevious",
1918
"Pipe",
2019
"RelaySender",
2120
]
Lines changed: 15 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# License: MIT
22
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH
33

4-
"""Predicates to be used in conjuntion with `Receiver.filter()`."""
4+
"""Composable predicate to cache and compare with the previous message."""
55

66

77
from typing import Callable, Final, Generic, TypeGuard
@@ -20,48 +20,43 @@ def __str__(self) -> str:
2020
_SENTINEL: Final[_Sentinel] = _Sentinel()
2121

2222

23-
class OnlyIfPrevious(Generic[ChannelMessageT]):
24-
"""A predicate to check if a message has a particular relationship with the previous one.
23+
class WithPrevious(Generic[ChannelMessageT]):
24+
"""A composable predicate to build predicates that can use also the previous message.
2525
26-
This predicate can be used to filter out messages based on a custom condition on the
26+
This predicate can be used to filter messages based on a custom condition on the
2727
previous and current messages. This can be useful in cases where you want to
2828
process messages only if they satisfy a particular condition with respect to the
2929
previous message.
3030
31-
Tip:
32-
If you want to use `==` as predicate, you can use the
33-
[`ChangedOnly`][frequenz.channels.experimental.ChangedOnly] predicate.
34-
35-
Example: Receiving only messages that are not the same instance as the previous one.
31+
Example: Receiving only messages that are different from the previous one.
3632
```python
3733
from frequenz.channels import Broadcast
38-
from frequenz.channels.experimental import OnlyIfPrevious
34+
from frequenz.channels.experimental import WithPrevious
3935
40-
channel = Broadcast[int | bool](name="example")
41-
receiver = channel.new_receiver().filter(OnlyIfPrevious(lambda old, new: old is not new))
36+
channel = Broadcast[int](name="example")
37+
receiver = channel.new_receiver().filter(WithPrevious(lambda old, new: old != new))
4238
sender = channel.new_sender()
4339
4440
# This message will be received as it is the first message.
4541
await sender.send(1)
4642
assert await receiver.receive() == 1
4743
48-
# This message will be skipped as it is the same instance as the previous one.
44+
# This message will be skipped as it equals to the previous one.
4945
await sender.send(1)
5046
51-
# This message will be received as it is a different instance from the previous
52-
# one.
53-
await sender.send(True)
54-
assert await receiver.receive() is True
47+
# This message will be received as it is a different from the previous one.
48+
await sender.send(0)
49+
assert await receiver.receive() == 0
5550
```
5651
5752
Example: Receiving only messages if they are bigger than the previous one.
5853
```python
5954
from frequenz.channels import Broadcast
60-
from frequenz.channels.experimental import OnlyIfPrevious
55+
from frequenz.channels.experimental import WithPrevious
6156
6257
channel = Broadcast[int](name="example")
6358
receiver = channel.new_receiver().filter(
64-
OnlyIfPrevious(lambda old, new: new > old, first_is_true=False)
59+
WithPrevious(lambda old, new: new > old, first_is_true=False)
6560
)
6661
sender = channel.new_sender()
6762
@@ -90,6 +85,7 @@ class OnlyIfPrevious(Generic[ChannelMessageT]):
9085
def __init__(
9186
self,
9287
predicate: Callable[[ChannelMessageT, ChannelMessageT], bool],
88+
/,
9389
*,
9490
first_is_true: bool = True,
9591
) -> None:
@@ -127,58 +123,3 @@ def __str__(self) -> str:
127123
def __repr__(self) -> str:
128124
"""Return a string representation of this instance."""
129125
return f"<{type(self).__name__}: {self._predicate!r} first_is_true={self._first_is_true!r}>"
130-
131-
132-
class ChangedOnly(OnlyIfPrevious[object]):
133-
"""A predicate to check if a message is different from the previous one.
134-
135-
This predicate can be used to filter out messages that are the same as the previous
136-
one. This can be useful in cases where you want to avoid processing duplicate
137-
messages.
138-
139-
Warning:
140-
This predicate uses the `!=` operator to compare messages, which includes all
141-
the weirdnesses of Python's equality comparison (e.g., `1 == 1.0`, `True == 1`,
142-
`True == 1.0`, `False == 0` are all `True`).
143-
144-
If you need to use a different comparison, you can create a custom predicate
145-
using [`OnlyIfPrevious`][frequenz.channels.experimental.OnlyIfPrevious].
146-
147-
Example:
148-
```python
149-
from frequenz.channels import Broadcast
150-
from frequenz.channels.experimental import ChangedOnly
151-
152-
channel = Broadcast[int](name="skip_duplicates_test")
153-
receiver = channel.new_receiver().filter(ChangedOnly())
154-
sender = channel.new_sender()
155-
156-
# This message will be received as it is the first message.
157-
await sender.send(1)
158-
assert await receiver.receive() == 1
159-
160-
# This message will be skipped as it is the same as the previous one.
161-
await sender.send(1)
162-
163-
# This message will be received as it is different from the previous one.
164-
await sender.send(2)
165-
assert await receiver.receive() == 2
166-
```
167-
"""
168-
169-
def __init__(self, *, first_is_true: bool = True) -> None:
170-
"""Initialize this instance.
171-
172-
Args:
173-
first_is_true: Whether the first message should be considered as different
174-
from the previous one. Defaults to `True`.
175-
"""
176-
super().__init__(lambda old, new: old != new, first_is_true=first_is_true)
177-
178-
def __str__(self) -> str:
179-
"""Return a string representation of this instance."""
180-
return f"{type(self).__name__}"
181-
182-
def __repr__(self) -> str:
183-
"""Return a string representation of this instance."""
184-
return f"{type(self).__name__}(first_is_true={self._first_is_true!r})"

tests/experimental/test_predicates_changed_only.py

Lines changed: 0 additions & 121 deletions
This file was deleted.

tests/experimental/test_predicates_only_if_previous.py renamed to tests/experimental/test_with_previous.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
# License: MIT
22
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH
33

4-
"""Tests for the OnlyIfPrevious implementation."""
4+
"""Tests for the WithPrevious implementation."""
55

66
from dataclasses import dataclass
77
from typing import Callable, Generic, TypeVar
88

99
import pytest
1010

11-
from frequenz.channels.experimental import OnlyIfPrevious
11+
from frequenz.channels.experimental import WithPrevious
1212

1313
_T = TypeVar("_T")
1414

1515

1616
@dataclass(frozen=True, kw_only=True)
1717
class PredicateTestCase(Generic[_T]):
18-
"""Test case for testing OnlyIfPrevious behavior with different predicates."""
18+
"""Test case for testing WithPrevious behavior with different predicates."""
1919

2020
title: str
2121
messages: list[_T]
@@ -113,12 +113,12 @@ def is_not_same_instance(old: object, new: object) -> bool:
113113
ids=lambda test_case: test_case.title,
114114
)
115115
def test_only_if_previous(test_case: PredicateTestCase[_T]) -> None:
116-
"""Test the OnlyIfPrevious with different predicates and sequences.
116+
"""Test the WithPrevious with different predicates and sequences.
117117
118118
Args:
119119
test_case: The test case containing the input values and expected results.
120120
"""
121-
only_if_previous = OnlyIfPrevious(
121+
only_if_previous = WithPrevious(
122122
test_case.predicate,
123123
first_is_true=test_case.first_is_true,
124124
)
@@ -127,9 +127,9 @@ def test_only_if_previous(test_case: PredicateTestCase[_T]) -> None:
127127

128128

129129
def test_only_if_previous_state_independence() -> None:
130-
"""Test that multiple OnlyIfPrevious instances maintain independent state."""
131-
only_if_previous1 = OnlyIfPrevious(is_greater)
132-
only_if_previous2 = OnlyIfPrevious(is_greater)
130+
"""Test that multiple WithPrevious instances maintain independent state."""
131+
only_if_previous1 = WithPrevious(is_greater)
132+
only_if_previous2 = WithPrevious(is_greater)
133133

134134
# First message should be accepted (first_is_true default is True)
135135
assert only_if_previous1(1) is True
@@ -141,17 +141,17 @@ def test_only_if_previous_state_independence() -> None:
141141

142142

143143
def test_only_if_previous_str_representation() -> None:
144-
"""Test the string representation of OnlyIfPrevious."""
145-
only_if_previous = OnlyIfPrevious(is_greater)
146-
assert str(only_if_previous) == "OnlyIfPrevious:is_greater"
144+
"""Test the string representation of WithPrevious."""
145+
only_if_previous = WithPrevious(is_greater)
146+
assert str(only_if_previous) == "WithPrevious:is_greater"
147147
assert (
148-
repr(only_if_previous) == f"<OnlyIfPrevious: {is_greater!r} first_is_true=True>"
148+
repr(only_if_previous) == f"<WithPrevious: {is_greater!r} first_is_true=True>"
149149
)
150150

151151

152152
def test_only_if_previous_sentinel_str() -> None:
153153
"""Test the string representation of the sentinel value."""
154-
only_if_previous = OnlyIfPrevious(always_true)
154+
only_if_previous = WithPrevious(always_true)
155155

156156
# Access the private attribute for testing purposes
157157
# pylint: disable=protected-access

0 commit comments

Comments
 (0)