Skip to content

Commit 8b6ef83

Browse files
committed
Implement Mapping for GroupingLatestValueCache
Signed-off-by: Sahas Subramanian <[email protected]>
1 parent 468500c commit 8b6ef83

File tree

2 files changed

+58
-27
lines changed

2 files changed

+58
-27
lines changed

src/frequenz/channels/experimental/_grouping_latest_value_cache.py

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
takes a [Receiver][frequenz.channels.Receiver] and a `key` function as arguments and
1111
stores the latest value received by that receiver for each key separately.
1212
13-
As soon as a value is received for a `key`, the
14-
[`has_value`][frequenz.channels.experimental.GroupingLatestValueCache.has_value] method
15-
returns `True` for that `key`, and the [`get`][frequenz.channels.LatestValueCache.get]
16-
method for that `key` returns the latest value received. The `get` method will raise an
17-
exception if called before any messages have been received from the receiver for a given
18-
`key`.
13+
The `GroupingLatestValueCache` implements the [`Mapping`][collections.abc.Mapping]
14+
interface, so it can be used like a dictionary. In addition, it provides a
15+
[has_value][frequenz.channels.experimental.GroupingLatestValueCache.has_value] method to
16+
check if a value has been received for a specific key, and a
17+
[clear][frequenz.channels.experimental.GroupingLatestValueCache.clear] method to clear
18+
the cached value for a specific key.
1919
2020
Example:
2121
```python
@@ -39,20 +39,21 @@
3939

4040
import asyncio
4141
import typing
42-
from collections.abc import Set
42+
from collections.abc import Iterator, KeysView, Mapping
43+
44+
from typing_extensions import override
4345

4446
from .._receiver import Receiver
4547

4648
T_co = typing.TypeVar("T_co", covariant=True)
49+
T = typing.TypeVar("T")
4750
HashableT = typing.TypeVar("HashableT", bound=typing.Hashable)
4851

4952

50-
class GroupingLatestValueCache(typing.Generic[T_co, HashableT]):
51-
"""A cache that stores the latest value in a receiver.
52-
53-
It provides a way to look up the latest value in a stream without any delay,
54-
as long as there has been one value received.
55-
"""
53+
class GroupingLatestValueCache(
54+
typing.Generic[T_co, HashableT], Mapping[HashableT, T_co]
55+
):
56+
"""A cache that stores the latest value in a receiver, grouped by key."""
5657

5758
def __init__(
5859
self,
@@ -84,32 +85,65 @@ def unique_id(self) -> str:
8485
"""The unique identifier of this instance."""
8586
return self._unique_id
8687

87-
def keys(self) -> Set[HashableT]:
88+
@override
89+
def keys(self) -> KeysView[HashableT]:
8890
"""Return the set of keys for which values have been received.
8991
9092
If no key function is provided, this will return an empty set.
9193
"""
9294
return self._latest_value_by_key.keys()
9395

94-
def get(self, key: HashableT) -> T_co:
95-
"""Return the latest value that has been received.
96+
@typing.overload
97+
def get(self, key: HashableT, default: None = None) -> T_co | None:
98+
"""Return the latest value that has been received for a specific key."""
9699

97-
This raises a `ValueError` if no value has been received yet. Use `has_value` to
98-
check whether a value has been received yet, before trying to access the value,
99-
to avoid the exception.
100+
# MyPy passes this overload as a valid signature, but pylint does not like it.
101+
@typing.overload
102+
def get( # pylint: disable=signature-differs
103+
self, key: HashableT, default: T
104+
) -> T_co | T:
105+
"""Return the latest value that has been received for a specific key."""
106+
107+
@override
108+
def get(self, key: HashableT, default: T | None = None) -> T_co | T | None:
109+
"""Return the latest value that has been received.
100110
101111
Args:
102112
key: An optional key to retrieve the latest value for that key. If not
103113
provided, it retrieves the latest value received overall.
114+
default: The default value to return if no value has been received yet for
115+
the specified key. If not provided, it defaults to `None`.
104116
105117
Returns:
106118
The latest value that has been received.
119+
"""
120+
return self._latest_value_by_key.get(key, default)
121+
122+
@override
123+
def __iter__(self) -> Iterator[HashableT]:
124+
"""Return an iterator over the keys for which values have been received."""
125+
return iter(self._latest_value_by_key)
126+
127+
@override
128+
def __len__(self) -> int:
129+
"""Return the number of keys for which values have been received."""
130+
return len(self._latest_value_by_key)
131+
132+
@override
133+
def __getitem__(self, key: HashableT) -> T_co:
134+
"""Return the latest value that has been received for a specific key.
135+
136+
Args:
137+
key: The key to retrieve the latest value for.
138+
139+
Returns:
140+
The latest value that has been received for that key.
107141
108142
Raises:
109-
ValueError: If no value has been received yet.
143+
KeyError: If no value has been received yet for that key.
110144
"""
111145
if key not in self._latest_value_by_key:
112-
raise ValueError(f"No value received for key: {key!r}")
146+
raise KeyError(f"No value received for key: {key!r}")
113147
return self._latest_value_by_key[key]
114148

115149
def has_value(self, key: HashableT) -> bool:

tests/test_grouping_latest_value_cache_integration.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ async def test_latest_value_cache_key() -> None:
2222
sender = channel.new_sender()
2323

2424
assert not cache.has_value(5)
25-
with pytest.raises(ValueError, match="No value received for key: 0"):
26-
cache.get(0)
25+
assert cache.get(0) is None
2726

2827
assert cache.keys() == set()
2928

@@ -41,8 +40,7 @@ async def test_latest_value_cache_key() -> None:
4140

4241
assert cache.keys() == {5, 6}
4342

44-
with pytest.raises(ValueError, match="No value received for key: 7"):
45-
cache.get(7)
43+
assert cache.get(7, default=(7, "default")) == (7, "default")
4644

4745
await sender.send((12, "d"))
4846
await asyncio.sleep(0)
@@ -65,6 +63,5 @@ async def test_latest_value_cache_key() -> None:
6563
assert not cache.has_value(5)
6664
assert cache.has_value(6)
6765

68-
with pytest.raises(ValueError, match="No value received for key: 5"):
69-
assert cache.get(5)
66+
assert cache.get(5) is None
7067
assert cache.keys() == {6, 12}

0 commit comments

Comments
 (0)