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
77from 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} )"
0 commit comments