Skip to content

Commit ed39f82

Browse files
committed
Add at method for single element access of moving window
This method implements corrected access of single elements in the moving window. Similar to its `window` counterpart for slices, in future it could provide additional features such as replacing missing values. Signed-off-by: cwasicki <[email protected]>
1 parent a064c03 commit ed39f82

File tree

2 files changed

+52
-0
lines changed

2 files changed

+52
-0
lines changed

src/frequenz/sdk/timeseries/_moving_window.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,48 @@ def capacity(self) -> int:
241241
"""
242242
return self._buffer.maxlen
243243

244+
# pylint before 3.0 only accepts names with 3 or more chars
245+
def at(self, key: int | datetime) -> float: # pylint: disable=invalid-name
246+
"""
247+
Return the sample at the given index or timestamp.
248+
249+
In contrast to the [`window`][frequenz.sdk.timeseries.MovingWindow.window] method,
250+
which expects a slice as argument, this method expects a single index as argument
251+
and returns a single value.
252+
253+
Args:
254+
key: The index or timestamp of the sample to return.
255+
256+
Returns:
257+
The sample at the given index or timestamp.
258+
259+
Raises:
260+
IndexError: If the buffer is empty or the index is out of bounds.
261+
"""
262+
if self._buffer.count_valid() == 0:
263+
raise IndexError("The buffer is empty.")
264+
265+
if isinstance(key, datetime):
266+
assert self._buffer.oldest_timestamp is not None
267+
assert self._buffer.newest_timestamp is not None
268+
if (
269+
key < self._buffer.oldest_timestamp
270+
or key > self._buffer.newest_timestamp
271+
):
272+
raise IndexError(
273+
f"Timestamp {key} is out of range [{self._buffer.oldest_timestamp}, "
274+
f"{self._buffer.newest_timestamp}]"
275+
)
276+
return self._buffer[self._buffer.to_internal_index(key)]
277+
278+
if isinstance(key, int):
279+
_logger.debug("Returning value at index %s ", key)
280+
timestamp = self._buffer.get_timestamp(key)
281+
assert timestamp is not None
282+
return self._buffer[self._buffer.to_internal_index(timestamp)]
283+
284+
raise TypeError("Key has to be either a timestamp or an integer.")
285+
244286
def window(
245287
self,
246288
start: datetime | int | None,
@@ -251,6 +293,10 @@ def window(
251293
"""
252294
Return an array containing the samples in the given time interval.
253295
296+
In contrast to the [`at`][frequenz.sdk.timeseries.MovingWindow.at] method,
297+
which expects a single index as argument, this method expects a slice as argument
298+
and returns an array.
299+
254300
Args:
255301
start: The start of the time interval. If `None`, the start of the
256302
window is used.

tests/timeseries/test_moving_window.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,18 @@ async def test_access_window_by_timestamp() -> None:
9999
async with window:
100100
await push_logical_meter_data(sender, [0, 1, 2])
101101
assert np.array_equal(window[dt(1)], 1.0)
102+
assert np.array_equal(window.at(dt(1)), 1.0)
102103
assert np.array_equal(window[dt(2)], 2.0)
104+
assert np.array_equal(window.at(dt(2)), 2.0)
103105
assert np.array_equal(window[dt(3)], 1.0) # bug: should raise
104106
with pytest.raises(IndexError):
105107
_ = window[dt(0)]
108+
with pytest.raises(IndexError):
109+
_ = window.at(dt(0))
106110
with pytest.raises(IndexError):
107111
_ = window[dt(4)]
112+
with pytest.raises(IndexError):
113+
_ = window.at(dt(3))
108114

109115

110116
async def test_access_window_by_int_slice() -> None:

0 commit comments

Comments
 (0)