1010takes a [Receiver][frequenz.channels.Receiver] and a `key` function as arguments and
1111stores 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
2020Example:
2121```python
3939
4040import asyncio
4141import typing
42- from collections .abc import Set
42+ from collections .abc import ItemsView , Iterator , KeysView , Mapping , ValuesView
43+
44+ from typing_extensions import override
4345
4446from .._receiver import Receiver
4547
4648T_co = typing .TypeVar ("T_co" , covariant = True )
49+ T = typing .TypeVar ("T" )
4750HashableT = 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 (Mapping [HashableT , T_co ]):
54+ """A cache that stores the latest value in a receiver, grouped by key."""
5655
5756 def __init__ (
5857 self ,
5958 receiver : Receiver [T_co ],
60- key : typing .Callable [[T_co ], typing .Any ],
6159 * ,
60+ key : typing .Callable [[T_co ], HashableT ],
6261 unique_id : str | None = None ,
6362 ) -> None :
6463 """Create a new cache.
@@ -84,47 +83,119 @@ def unique_id(self) -> str:
8483 """The unique identifier of this instance."""
8584 return self ._unique_id
8685
87- def keys (self ) -> Set [HashableT ]:
86+ @override
87+ def keys (self ) -> KeysView [HashableT ]:
8888 """Return the set of keys for which values have been received.
8989
9090 If no key function is provided, this will return an empty set.
9191 """
9292 return self ._latest_value_by_key .keys ()
9393
94- def get (self , key : HashableT ) -> T_co :
94+ @override
95+ def items (self ) -> ItemsView [HashableT , T_co ]:
96+ """Return an iterator over the key-value pairs of the latest values received."""
97+ return self ._latest_value_by_key .items ()
98+
99+ @override
100+ def values (self ) -> ValuesView [T_co ]:
101+ """Return an iterator over the latest values received."""
102+ return self ._latest_value_by_key .values ()
103+
104+ @typing .overload
105+ def get (self , key : HashableT , default : None = None ) -> T_co | None :
106+ """Return the latest value that has been received for a specific key."""
107+
108+ # MyPy passes this overload as a valid signature, but pylint does not like it.
109+ @typing .overload
110+ def get ( # pylint: disable=signature-differs
111+ self , key : HashableT , default : T
112+ ) -> T_co | T :
113+ """Return the latest value that has been received for a specific key."""
114+
115+ @override
116+ def get (self , key : HashableT , default : T | None = None ) -> T_co | T | None :
95117 """Return the latest value that has been received.
96118
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-
101119 Args:
102120 key: An optional key to retrieve the latest value for that key. If not
103121 provided, it retrieves the latest value received overall.
122+ default: The default value to return if no value has been received yet for
123+ the specified key. If not provided, it defaults to `None`.
104124
105125 Returns:
106126 The latest value that has been received.
127+ """
128+ return self ._latest_value_by_key .get (key , default )
129+
130+ @override
131+ def __iter__ (self ) -> Iterator [HashableT ]:
132+ """Return an iterator over the keys for which values have been received."""
133+ return iter (self ._latest_value_by_key )
134+
135+ @override
136+ def __len__ (self ) -> int :
137+ """Return the number of keys for which values have been received."""
138+ return len (self ._latest_value_by_key )
139+
140+ @override
141+ def __getitem__ (self , key : HashableT ) -> T_co :
142+ """Return the latest value that has been received for a specific key.
143+
144+ Args:
145+ key: The key to retrieve the latest value for.
146+
147+ Returns:
148+ The latest value that has been received for that key.
107149
108150 Raises:
109- ValueError : If no value has been received yet.
151+ KeyError : If no value has been received yet for that key .
110152 """
111153 if key not in self ._latest_value_by_key :
112- raise ValueError (f"No value received for key: { key !r} " )
154+ raise KeyError (f"No value received for key: { key !r} " )
113155 return self ._latest_value_by_key [key ]
114156
115- def has_value (self , key : HashableT ) -> bool :
116- """Check whether a value has been received yet.
117-
118- If `key` is provided, it checks whether a value has been received for that key.
157+ @override
158+ def __contains__ (self , key : object , / ) -> bool :
159+ """Check if a value has been received for a specific key.
119160
120161 Args:
121- key: An optional key to check if a value has been received for that key .
162+ key: The key to check for.
122163
123164 Returns:
124- `True` if a value has been received, `False` otherwise.
165+ `True` if a value has been received for that key , `False` otherwise.
125166 """
126167 return key in self ._latest_value_by_key
127168
169+ @override
170+ def __eq__ (self , other : object , / ) -> bool :
171+ """Check if this cache is equal to another object.
172+
173+ Two caches are considered equal if they have the same keys and values.
174+
175+ Args:
176+ other: The object to compare with.
177+
178+ Returns:
179+ `True` if the caches are equal, `False` otherwise.
180+ """
181+ if not isinstance (other , GroupingLatestValueCache ):
182+ return NotImplemented
183+ return self ._latest_value_by_key == other ._latest_value_by_key
184+
185+ @override
186+ def __ne__ (self , value : object , / ) -> bool :
187+ """Check if this cache is not equal to another object.
188+
189+ Args:
190+ value: The object to compare with.
191+
192+ Returns:
193+ `True` if the caches are not equal, `False` otherwise.
194+ """
195+ if not isinstance (value , GroupingLatestValueCache ):
196+ return NotImplemented
197+ return self ._latest_value_by_key != value ._latest_value_by_key
198+
128199 def clear (self , key : HashableT ) -> None :
129200 """Clear the latest value for a specific key.
130201
0 commit comments