2222"""Type variable for the keys used to group values in the `GroupingLatestValueCache`."""
2323
2424
25+ class _NotSpecified :
26+ """A sentinel value to indicate that no default value was provided."""
27+
28+ def __repr__ (self ) -> str :
29+ """Return a string representation of this sentinel."""
30+ return "<_NotSpecified>"
31+
32+
2533class GroupingLatestValueCache (Mapping [HashableT , ValueT_co ]):
2634 """A cache that stores the latest value in a receiver, grouped by key.
2735
@@ -33,12 +41,15 @@ class GroupingLatestValueCache(Mapping[HashableT, ValueT_co]):
3341 stores the latest value received by that receiver for each key separately.
3442
3543 The `GroupingLatestValueCache` implements the [`Mapping`][collections.abc.Mapping]
36- interface, so it can be used like a dictionary. It is not
37- a [`MutableMapping`][collections.abc.MutableMapping] because users can't mutate the
38- cache directly, it is only mutated by the underlying receiver. There is one exception
39- though, users can clear individual keys from the cache using the
40- [__delitem__][frequenz.channels.experimental.GroupingLatestValueCache.__delitem__]
41- method.
44+ interface, so it can be used like a dictionary. Additionally other methods from
45+ [`MutableMapping`][collections.abc.MutableMapping] are implemented, but only
46+ methods removing items from the cache are allowed, such as
47+ [`pop()`][frequenz.channels.experimental.GroupingLatestValueCache.pop],
48+ [`popitem()`][frequenz.channels.experimental.GroupingLatestValueCache.popitem],
49+ [`clear()`][frequenz.channels.experimental.GroupingLatestValueCache.clear], and
50+ [`__delitem__()`][frequenz.channels.experimental.GroupingLatestValueCache.__delitem__].
51+ Other update methods are not provided because the user should not update the
52+ cache values directly.
4253
4354 Example:
4455 ```python
@@ -220,6 +231,50 @@ def __delitem__(self, key: HashableT) -> None:
220231 """
221232 del self ._latest_value_by_key [key ]
222233
234+ @typing .overload
235+ def pop (self , key : HashableT , / ) -> ValueT_co | None :
236+ """Remove the latest value for a specific key and return it."""
237+
238+ @typing .overload
239+ def pop (self , key : HashableT , / , default : DefaultT ) -> ValueT_co | DefaultT :
240+ """Remove the latest value for a specific key and return it."""
241+
242+ def pop (
243+ self , key : HashableT , / , default : DefaultT | _NotSpecified = _NotSpecified ()
244+ ) -> ValueT_co | DefaultT | None :
245+ """Remove the latest value for a specific key and return it.
246+
247+ If no value has been received yet for that key, it returns the default value or
248+ raises a `KeyError` if no default value is provided.
249+
250+ Args:
251+ key: The key for which to remove the latest value.
252+ default: The default value to return if no value has been received yet for
253+ the specified key.
254+
255+ Returns:
256+ The latest value that has been received for that key, or the default value if
257+ no value has been received yet and a default value is provided.
258+ """
259+ if isinstance (default , _NotSpecified ):
260+ return self ._latest_value_by_key .pop (key )
261+ return self ._latest_value_by_key .pop (key , default )
262+
263+ def popitem (self ) -> tuple [HashableT , ValueT_co ]:
264+ """Remove and return a (key, value) pair from the cache.
265+
266+ Pairs are returned in LIFO (last-in, first-out) order.
267+
268+ Returns:
269+ A tuple containing the key and the latest value that has been received for
270+ that key.
271+ """
272+ return self ._latest_value_by_key .popitem ()
273+
274+ def clear (self ) -> None :
275+ """Clear all entries from the cache."""
276+ self ._latest_value_by_key .clear ()
277+
223278 async def stop (self ) -> None :
224279 """Stop the cache."""
225280 if not self ._task .done ():
0 commit comments