Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

- `LatestValueCache` now takes an optional `key` function, which returns the key for each incoming message, and the latest value for each key is cached and can be retrieved separately.

- `LatestValueCache` got a new `clear` method that clears the latest value. When an optional `key` argument is specified, it clears the value only for that key.

## Bug Fixes

<!-- Here goes notable bug fixes that are worth a special mention or explanation -->
16 changes: 16 additions & 0 deletions src/frequenz/channels/_latest_value_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,22 @@ def has_value(self, key: HashableT | Sentinel = NO_KEY) -> bool:
return key in self._latest_value_by_key
return not isinstance(self._latest_value, Sentinel)

def clear(self, key: HashableT | Sentinel = NO_KEY) -> None:
"""Clear the latest value or the latest value for a specific key.

If `key` is provided, it clears the latest value for that key. If no key is
provided, it clears the latest value received overall.

Args:
key: An optional key to clear the latest value for that key. If not
provided, it clears the latest value received overall.
"""
if not isinstance(key, Sentinel):
_ = self._latest_value_by_key.pop(key, None)
return

self._latest_value = NO_VALUE_RECEIVED

Comment on lines +197 to +212
Copy link
Contributor

@ela-kotulska-frequenz ela-kotulska-frequenz Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why call clear() doesn't remove all keys and values in self._latest_value_by_key ?
Or shouldn't you remove _latest_value_by_key[self._key(self._latest_value)]], too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh damn, because my usecase didn't need clearing all keys. I wonder if we should change the API slightly to keep the option open for a "clear everything" in the future.

any opinions @llucax ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option is to make the key parameter required, so that I can make progress and we can decide how to generalise later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some options I can think of:

  1. clear_all(), clear() but it is true that clear() with no key looks ambiguous
  2. clear_all(), clear_key() maybe is more clear that without a key it clears the no-key?

I'm starting to find the use case for a "global" key a bit weird. I guess we need to support it for backwards compatibility, otherwise maybe we should always require a key and let the user use something like None if they don't want one? Not sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We haven't released it. I can actually pull out all the new stuff into experimental.GroupingLatestValueCache that doesn't have a global mode.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done here: #428

async def _run(self) -> None:
async for value in self._receiver:
self._latest_value = value
Expand Down
15 changes: 15 additions & 0 deletions tests/test_latest_value_cache_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,18 @@ async def test_latest_value_cache_key() -> None:
assert cache.get(6) == (6, "g")

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

cache.clear()
assert not cache.has_value()

assert cache.keys() == {5, 6, 12}
assert cache.has_value(5)
assert cache.get(5) == (5, "c")

cache.clear(5)
assert not cache.has_value(5)
assert cache.has_value(6)

with pytest.raises(ValueError, match="No value received for key: 5"):
assert cache.get(5)
assert cache.keys() == {6, 12}
Loading