Skip to content

Conversation

@shsms
Copy link
Contributor

@shsms shsms commented Jun 3, 2025

LatestValueCache now takes an optional key function. When
specified, it is used to get the key for each incoming message, and
the latest value for each key is cached and can be retrieved
separately.

Copilot AI review requested due to automatic review settings June 3, 2025 09:58
@shsms shsms requested a review from a team as a code owner June 3, 2025 09:58
@shsms shsms requested a review from Marenz June 3, 2025 09:58
@github-actions github-actions bot added part:docs Affects the documentation part:tests Affects the unit, integration and performance (benchmarks) tests labels Jun 3, 2025
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds support for key-based grouping in the LatestValueCache, enabling the cache to store and retrieve the latest value for each key.

  • Added integration tests to verify key-based caching and overall cache behavior.
  • Modified the LatestValueCache to accept an optional key function and maintain separate caches per key.
  • Updated RELEASE_NOTES.md to document the new feature.

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
tests/test_latest_value_cache_integration.py Added integration tests for key-based caching functionality.
src/frequenz/channels/_latest_value_cache.py Updated the cache implementation to support grouping by keys via a key function.
RELEASE_NOTES.md Updated release notes to reflect the new key function feature.

receiver: Receiver[T_co],
*,
unique_id: str | None = None,
key: typing.Callable[[T_co], typing.Any] | None = None,
Copy link

Copilot AI Jun 3, 2025

Choose a reason for hiding this comment

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

The implementation signature uses a generic return type for 'key' compared to the overloads which expect a HashableT. Consider updating the annotation to 'typing.Callable[[T_co], HashableT] | None' for consistency.

Suggested change
key: typing.Callable[[T_co], typing.Any] | None = None,
key: typing.Callable[[T_co], HashableT] | None = None,

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, it has to be a superset of the two overloads HashableT and None.

Copy link
Contributor

Choose a reason for hiding this comment

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

But not HashableT | None?

Comment on lines 67 to 97
@typing.overload
def __init__(
self: "LatestValueCache[T_co, None]",
receiver: Receiver[T_co],
*,
unique_id: str | None = None,
key: None = None,
) -> None:
"""Create a new cache that does not use keys.
Args:
receiver: The receiver to cache.
unique_id: A string to help uniquely identify this instance. If not
provided, a unique identifier will be generated from the object's
[`id()`][id]. It is used mostly for debugging purposes.
key: This parameter is ignored when set to `None`.
"""

@typing.overload
def __init__(
self: "LatestValueCache[T_co, HashableT]",
receiver: Receiver[T_co],
*,
unique_id: str | None = None,
key: typing.Callable[[T_co], HashableT],
) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

Damn, is this overload over self really necessary? Doesn't self just works magically if you don't annotate it at all? Have you tried with Self?

Also, you should from __future__ import annotations and remove the quotes from the types if you still need it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is how to avoid having to explicitly specify HashableT and default to None when no key is specified.

I will remove the quotes.

Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if using a (non-hashable) sentinel in the type could help with this too, if the issue is that None is Hashable.

shsms added 3 commits June 3, 2025 13:46
`LatestValueCache` now takes an optional `key` function.  When
specified, it is used to get the key for each incoming message, and
the latest value for each key is cached and can be retrieved
separately.

Signed-off-by: Sahas Subramanian <[email protected]>
Signed-off-by: Sahas Subramanian <[email protected]>
@shsms shsms force-pushed the 🔑💸 branch 2 times, most recently from 3d6fd84 to 3ad6744 Compare June 3, 2025 13:15
llucax
llucax previously approved these changes Jun 3, 2025
Copy link
Contributor

@llucax llucax left a comment

Choose a reason for hiding this comment

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

Some suggestions for extra type-safety, but LGTM.

receiver: Receiver[T_co],
*,
unique_id: str | None = None,
key: Sentinel = NO_KEY_FUNCTION,
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you use Literal[NO_KEY_FUNCTION] here instead of Sentinel? Otherwise this will also accept key=NO_VALUE_RECEIVER for example. I guess it is still OK (the alternative is to create one type per sentinel), but if Literal works it could be a very nice solution.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, Literal is only available for fundamental types. I think it is fine, the sentinels are interchangeable and their meaning comes from context, not from their names. Also, they are not exposed, so users can't use them without accessing private symbols.

Copy link
Contributor

Choose a reason for hiding this comment

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

And Enums, that could be an alternative, but it is probably not worth it, if the values are not exposed anyway, then it doesn't add much.

This makes the code easier to read and the documentation look better.

Signed-off-by: Sahas Subramanian <[email protected]>
@shsms shsms added this pull request to the merge queue Jun 3, 2025
Merged via the queue into frequenz-floss:v1.x.x with commit 3edcb49 Jun 3, 2025
5 checks passed
@shsms shsms deleted the 🔑💸 branch June 3, 2025 13:48
shsms added a commit to shsms/frequenz-channels-python that referenced this pull request Jun 16, 2025
shsms added a commit to shsms/frequenz-channels-python that referenced this pull request Jun 16, 2025
…s#424)"

This reverts commit 3edcb49, reversing
changes made to f7fb341.

Signed-off-by: Sahas Subramanian <[email protected]>
@llucax llucax added this to the v1.10.0 milestone Jun 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

part:docs Affects the documentation part:tests Affects the unit, integration and performance (benchmarks) tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants