Skip to content

Commit c5a535f

Browse files
committed
Cover TS.INCRBY and TS.DECRBY
1 parent c1f4815 commit c5a535f

File tree

2 files changed

+158
-46
lines changed

2 files changed

+158
-46
lines changed

redis/commands/timeseries/commands.py

Lines changed: 99 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def create(
8888
self._append_retention(params, retention_msecs)
8989
self._append_uncompressed(params, uncompressed)
9090
self._append_chunk_size(params, chunk_size)
91-
self._append_duplicate_policy(params, CREATE_CMD, duplicate_policy)
91+
self._append_duplicate_policy(params, duplicate_policy)
9292
self._append_labels(params, labels)
9393
self._append_ignore_filters(params, ignore_max_time_diff, ignore_max_val_diff)
9494

@@ -98,6 +98,7 @@ def alter(
9898
self,
9999
key: KeyT,
100100
retention_msecs: Optional[int] = None,
101+
uncompressed: Optional[bool] = False,
101102
labels: Optional[Dict[str, str]] = None,
102103
chunk_size: Optional[int] = None,
103104
duplicate_policy: Optional[str] = None,
@@ -117,6 +118,8 @@ def alter(
117118
retention_msecs:
118119
Maximum age for samples, compared to the highest reported timestamp in
119120
milliseconds. If None or 0 is passed, the series is not trimmed at all.
121+
uncompressed:
122+
Changes data storage from compressed (default) to uncompressed.
120123
labels:
121124
A dictionary of label-value pairs that represent metadata labels of the
122125
key.
@@ -153,8 +156,9 @@ def alter(
153156
"""
154157
params = [key]
155158
self._append_retention(params, retention_msecs)
159+
self._append_uncompressed(params, uncompressed)
156160
self._append_chunk_size(params, chunk_size)
157-
self._append_duplicate_policy(params, ALTER_CMD, duplicate_policy)
161+
self._append_duplicate_policy(params, duplicate_policy)
158162
self._append_labels(params, labels)
159163
self._append_ignore_filters(params, ignore_max_time_diff, ignore_max_val_diff)
160164

@@ -172,6 +176,7 @@ def add(
172176
duplicate_policy: Optional[str] = None,
173177
ignore_max_time_diff: Optional[int] = None,
174178
ignore_max_val_diff: Optional[Number] = None,
179+
on_duplicate: Optional[str] = None,
175180
):
176181
"""
177182
Append (or create and append) a new sample to a time series.
@@ -225,14 +230,18 @@ def add(
225230
is lower than this threshold, the new entry is ignored. Only applicable
226231
if `duplicate_policy` is set to `last`, and if `ignore_max_time_diff` is
227232
also set. Available since RedisTimeSeries version 1.12.0.
233+
on_duplicate:
234+
Use a specific duplicate policy for the specified timestamp. Overrides
235+
the duplicate policy set by `duplicate_policy`.
228236
"""
229237
params = [key, timestamp, value]
230238
self._append_retention(params, retention_msecs)
231239
self._append_uncompressed(params, uncompressed)
232240
self._append_chunk_size(params, chunk_size)
233-
self._append_duplicate_policy(params, ADD_CMD, duplicate_policy)
241+
self._append_duplicate_policy(params, duplicate_policy)
234242
self._append_labels(params, labels)
235243
self._append_ignore_filters(params, ignore_max_time_diff, ignore_max_val_diff)
244+
self._append_on_duplicate(params, on_duplicate)
236245

237246
return self.execute_command(ADD_CMD, *params)
238247

@@ -260,6 +269,9 @@ def incrby(
260269
uncompressed: Optional[bool] = False,
261270
labels: Optional[Dict[str, str]] = None,
262271
chunk_size: Optional[int] = None,
272+
duplicate_policy: Optional[str] = None,
273+
ignore_max_time_diff: Optional[int] = None,
274+
ignore_max_val_diff: Optional[Number] = None,
263275
):
264276
"""
265277
Increment (or create an time-series and increment) the latest sample's of a series.
@@ -276,21 +288,52 @@ def incrby(
276288
Timestamp of the sample. `*` can be used for automatic timestamp (using
277289
the system clock).
278290
retention_msecs:
279-
Maximum age for samples compared to last event time (in milliseconds).
280-
If `None` or `0` is passed then the series is not trimmed at all.
291+
Maximum age for samples, compared to the highest reported timestamp in
292+
milliseconds. If None or 0 is passed, the series is not trimmed at all.
281293
uncompressed:
282-
Changes data storage from compressed (by default) to uncompressed.
294+
Changes data storage from compressed (default) to uncompressed.
283295
labels:
284-
Set of label-value pairs that represent metadata labels of the key.
296+
A dictionary of label-value pairs that represent metadata labels of the
297+
key.
285298
chunk_size:
286-
Memory size, in bytes, allocated for each data chunk.
299+
Memory size, in bytes, allocated for each data chunk. Must be a multiple
300+
of 8 in the range [128..1048576].
301+
duplicate_policy:
302+
Policy for handling multiple samples with identical timestamps. Can be
303+
one of:
304+
- 'block': An error will occur for any out of order sample.
305+
- 'first': Ignore the new value.
306+
- 'last': Override with the latest value.
307+
- 'min': Only override if the value is lower than the existing
308+
value.
309+
- 'max': Only override if the value is higher than the existing
310+
value.
311+
- 'sum': If a previous sample exists, add the new sample to it so
312+
that the updated value is equal to (previous + new). If no
313+
previous sample exists, set the updated value equal to the new
314+
value.
315+
ignore_max_time_diff:
316+
A non-negative integer value, in milliseconds, that sets an ignore
317+
threshold for added timestamps. If the difference between the last
318+
timestamp and the new timestamp is lower than this threshold, the new
319+
entry is ignored. Only applicable if `duplicate_policy` is set to
320+
`last`, and if `ignore_max_val_diff` is also set. Available since
321+
RedisTimeSeries version 1.12.0.
322+
ignore_max_val_diff:
323+
A non-negative floating point value, that sets an ignore threshold for
324+
added values. If the difference between the last value and the new value
325+
is lower than this threshold, the new entry is ignored. Only applicable
326+
if `duplicate_policy` is set to `last`, and if `ignore_max_time_diff` is
327+
also set. Available since RedisTimeSeries version 1.12.0.
287328
"""
288329
params = [key, value]
289330
self._append_timestamp(params, timestamp)
290331
self._append_retention(params, retention_msecs)
291332
self._append_uncompressed(params, uncompressed)
292333
self._append_chunk_size(params, chunk_size)
334+
self._append_duplicate_policy(params, duplicate_policy)
293335
self._append_labels(params, labels)
336+
self._append_ignore_filters(params, ignore_max_time_diff, ignore_max_val_diff)
294337

295338
return self.execute_command(INCRBY_CMD, *params)
296339

@@ -303,6 +346,9 @@ def decrby(
303346
uncompressed: Optional[bool] = False,
304347
labels: Optional[Dict[str, str]] = None,
305348
chunk_size: Optional[int] = None,
349+
duplicate_policy: Optional[str] = None,
350+
ignore_max_time_diff: Optional[int] = None,
351+
ignore_max_val_diff: Optional[Number] = None,
306352
):
307353
"""
308354
Decrement (or create an time-series and decrement) the latest sample's of a series.
@@ -319,21 +365,52 @@ def decrby(
319365
Timestamp of the sample. `*` can be used for automatic timestamp (using
320366
the system clock).
321367
retention_msecs:
322-
Maximum age for samples compared to last event time (in milliseconds).
323-
If `None` or `0` is passed then the series is not trimmed at all.
368+
Maximum age for samples, compared to the highest reported timestamp in
369+
milliseconds. If None or 0 is passed, the series is not trimmed at all.
324370
uncompressed:
325-
Changes data storage from compressed (by default) to uncompressed.
371+
Changes data storage from compressed (default) to uncompressed.
326372
labels:
327-
Set of label-value pairs that represent metadata labels of the key.
373+
A dictionary of label-value pairs that represent metadata labels of the
374+
key.
328375
chunk_size:
329-
Memory size, in bytes, allocated for each data chunk.
376+
Memory size, in bytes, allocated for each data chunk. Must be a multiple
377+
of 8 in the range [128..1048576].
378+
duplicate_policy:
379+
Policy for handling multiple samples with identical timestamps. Can be
380+
one of:
381+
- 'block': An error will occur for any out of order sample.
382+
- 'first': Ignore the new value.
383+
- 'last': Override with the latest value.
384+
- 'min': Only override if the value is lower than the existing
385+
value.
386+
- 'max': Only override if the value is higher than the existing
387+
value.
388+
- 'sum': If a previous sample exists, add the new sample to it so
389+
that the updated value is equal to (previous + new). If no
390+
previous sample exists, set the updated value equal to the new
391+
value.
392+
ignore_max_time_diff:
393+
A non-negative integer value, in milliseconds, that sets an ignore
394+
threshold for added timestamps. If the difference between the last
395+
timestamp and the new timestamp is lower than this threshold, the new
396+
entry is ignored. Only applicable if `duplicate_policy` is set to
397+
`last`, and if `ignore_max_val_diff` is also set. Available since
398+
RedisTimeSeries version 1.12.0.
399+
ignore_max_val_diff:
400+
A non-negative floating point value, that sets an ignore threshold for
401+
added values. If the difference between the last value and the new value
402+
is lower than this threshold, the new entry is ignored. Only applicable
403+
if `duplicate_policy` is set to `last`, and if `ignore_max_time_diff` is
404+
also set. Available since RedisTimeSeries version 1.12.0.
330405
"""
331406
params = [key, value]
332407
self._append_timestamp(params, timestamp)
333408
self._append_retention(params, retention_msecs)
334409
self._append_uncompressed(params, uncompressed)
335410
self._append_chunk_size(params, chunk_size)
411+
self._append_duplicate_policy(params, duplicate_policy)
336412
self._append_labels(params, labels)
413+
self._append_ignore_filters(params, ignore_max_time_diff, ignore_max_val_diff)
337414

338415
return self.execute_command(DECRBY_CMD, *params)
339416

@@ -942,17 +1019,16 @@ def _append_chunk_size(params: List[str], chunk_size: Optional[int]):
9421019
params.extend(["CHUNK_SIZE", chunk_size])
9431020

9441021
@staticmethod
945-
def _append_duplicate_policy(
946-
params: List[str], command: Optional[str], duplicate_policy: Optional[str]
947-
):
948-
"""Append DUPLICATE_POLICY property to params on CREATE
949-
and ON_DUPLICATE on ADD.
950-
"""
1022+
def _append_duplicate_policy(params: List[str], duplicate_policy: Optional[str]):
1023+
"""Append DUPLICATE_POLICY property to params."""
9511024
if duplicate_policy is not None:
952-
if command == "TS.ADD":
953-
params.extend(["ON_DUPLICATE", duplicate_policy])
954-
else:
955-
params.extend(["DUPLICATE_POLICY", duplicate_policy])
1025+
params.extend(["DUPLICATE_POLICY", duplicate_policy])
1026+
1027+
@staticmethod
1028+
def _append_on_duplicate(params: List[str], on_duplicate: Optional[str]):
1029+
"""Append ON_DUPLICATE property to params."""
1030+
if on_duplicate is not None:
1031+
params.extend(["ON_DUPLICATE", on_duplicate])
9561032

9571033
@staticmethod
9581034
def _append_filer_by_ts(params: List[str], ts_list: Optional[List[int]]):

tests/test_timeseries.py

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from time import sleep
44

55
import pytest
6-
76
import redis
7+
88
from .conftest import assert_resp_response, is_resp2_connection, skip_ifmodversion_lt
99

1010

@@ -103,38 +103,32 @@ def test_add(client):
103103

104104

105105
@skip_ifmodversion_lt("1.4.0", "timeseries")
106-
def test_add_duplicate_policy(client):
106+
def test_add_on_duplicate(client):
107107
# Test for duplicate policy BLOCK
108108
assert 1 == client.ts().add("time-serie-add-ooo-block", 1, 5.0)
109109
with pytest.raises(Exception):
110110
client.ts().add("time-serie-add-ooo-block", 1, 5.0, duplicate_policy="block")
111111

112112
# Test for duplicate policy LAST
113113
assert 1 == client.ts().add("time-serie-add-ooo-last", 1, 5.0)
114-
assert 1 == client.ts().add(
115-
"time-serie-add-ooo-last", 1, 10.0, duplicate_policy="last"
116-
)
114+
assert 1 == client.ts().add("time-serie-add-ooo-last", 1, 10.0, on_duplicate="last")
117115
assert 10.0 == client.ts().get("time-serie-add-ooo-last")[1]
118116

119117
# Test for duplicate policy FIRST
120118
assert 1 == client.ts().add("time-serie-add-ooo-first", 1, 5.0)
121119
assert 1 == client.ts().add(
122-
"time-serie-add-ooo-first", 1, 10.0, duplicate_policy="first"
120+
"time-serie-add-ooo-first", 1, 10.0, on_duplicate="first"
123121
)
124122
assert 5.0 == client.ts().get("time-serie-add-ooo-first")[1]
125123

126124
# Test for duplicate policy MAX
127125
assert 1 == client.ts().add("time-serie-add-ooo-max", 1, 5.0)
128-
assert 1 == client.ts().add(
129-
"time-serie-add-ooo-max", 1, 10.0, duplicate_policy="max"
130-
)
126+
assert 1 == client.ts().add("time-serie-add-ooo-max", 1, 10.0, on_duplicate="max")
131127
assert 10.0 == client.ts().get("time-serie-add-ooo-max")[1]
132128

133129
# Test for duplicate policy MIN
134130
assert 1 == client.ts().add("time-serie-add-ooo-min", 1, 5.0)
135-
assert 1 == client.ts().add(
136-
"time-serie-add-ooo-min", 1, 10.0, duplicate_policy="min"
137-
)
131+
assert 1 == client.ts().add("time-serie-add-ooo-min", 1, 10.0, on_duplicate="min")
138132
assert 5.0 == client.ts().get("time-serie-add-ooo-min")[1]
139133

140134

@@ -982,14 +976,11 @@ def test_create_with_insertion_filters(client):
982976
)
983977
assert 1000 == client.ts().add("time-series-1", 1000, 1.0)
984978
assert 1010 == client.ts().add("time-series-1", 1010, 11.0)
985-
# Within 5 ms of the last timestamp and value diff less than 10.0
986979
assert 1010 == client.ts().add("time-series-1", 1013, 10.0)
987-
# Value difference less than 10.0, but timestamp diff larger than 5 ms
988980
assert 1020 == client.ts().add("time-series-1", 1020, 11.5)
989-
# Timestamp diff less than 5 ms, but value diff larger than 10.0
990981
assert 1021 == client.ts().add("time-series-1", 1021, 22.0)
991982

992-
data_points = client.ts().range("time-series-1", '-', '+')
983+
data_points = client.ts().range("time-series-1", "-", "+")
993984
expected_points = [(1000, 1.0), (1010, 11.0), (1020, 11.5), (1021, 22.0)]
994985
assert expected_points == data_points
995986

@@ -1003,11 +994,10 @@ def test_create_with_insertion_filters_other_duplicate_policy(client):
1003994
)
1004995
assert 1000 == client.ts().add("time-series-1", 1000, 1.0)
1005996
assert 1010 == client.ts().add("time-series-1", 1010, 11.0)
1006-
# Within 5 ms of the last timestamp and value diff less than 10.0.
1007997
# Still accepted because the duplicate_policy is not `last`.
1008998
assert 1013 == client.ts().add("time-series-1", 1013, 10.0)
1009999

1010-
data_points = client.ts().range("time-series-1", '-', '+')
1000+
data_points = client.ts().range("time-series-1", "-", "+")
10111001
expected_points = [(1000, 1.0), (1010, 11.0), (1013, 10)]
10121002
assert expected_points == data_points
10131003

@@ -1022,13 +1012,12 @@ def test_alter_with_insertion_filters(client):
10221012
"time-series-1",
10231013
duplicate_policy="last",
10241014
ignore_max_time_diff=5,
1025-
ignore_max_val_diff=10.0
1015+
ignore_max_val_diff=10.0,
10261016
)
10271017

1028-
# Within 5 ms of the last timestamp and value diff less than 10.0.
10291018
assert 1013 == client.ts().add("time-series-1", 1015, 11.5)
10301019

1031-
data_points = client.ts().range("time-series-1", '-', '+')
1020+
data_points = client.ts().range("time-series-1", "-", "+")
10321021
expected_points = [(1000, 1.0), (1010, 11.0), (1013, 10.0)]
10331022
assert expected_points == data_points
10341023

@@ -1044,9 +1033,56 @@ def test_add_with_insertion_filters(client):
10441033
ignore_max_val_diff=10.0,
10451034
)
10461035

1047-
# Within 5 ms of the last timestamp and value diff less than 10.0.
10481036
assert 1000 == client.ts().add("time-series-1", 1004, 3.0)
10491037

1050-
data_points = client.ts().range("time-series-1", '-', '+')
1038+
data_points = client.ts().range("time-series-1", "-", "+")
1039+
expected_points = [(1000, 1.0)]
1040+
assert expected_points == data_points
1041+
1042+
1043+
@skip_ifmodversion_lt("1.12.0", "timeseries")
1044+
def test_incrby_with_insertion_filters(client):
1045+
assert 1000 == client.ts().incrby(
1046+
"time-series-1",
1047+
1.0,
1048+
timestamp=1000,
1049+
duplicate_policy="last",
1050+
ignore_max_time_diff=5,
1051+
ignore_max_val_diff=10.0,
1052+
)
1053+
1054+
assert 1000 == client.ts().incrby("time-series-1", 3.0, timestamp=1000)
1055+
1056+
data_points = client.ts().range("time-series-1", "-", "+")
10511057
expected_points = [(1000, 1.0)]
10521058
assert expected_points == data_points
1059+
1060+
assert 1000 == client.ts().incrby("time-series-1", 10.1, timestamp=1000)
1061+
1062+
data_points = client.ts().range("time-series-1", "-", "+")
1063+
expected_points = [(1000, 11.1)]
1064+
assert expected_points == data_points
1065+
1066+
1067+
@skip_ifmodversion_lt("1.12.0", "timeseries")
1068+
def test_decrby_with_insertion_filters(client):
1069+
assert 1000 == client.ts().decrby(
1070+
"time-series-1",
1071+
1.0,
1072+
timestamp=1000,
1073+
duplicate_policy="last",
1074+
ignore_max_time_diff=5,
1075+
ignore_max_val_diff=10.0,
1076+
)
1077+
1078+
assert 1000 == client.ts().decrby("time-series-1", 3.0, timestamp=1000)
1079+
1080+
data_points = client.ts().range("time-series-1", "-", "+")
1081+
expected_points = [(1000, -1.0)]
1082+
assert expected_points == data_points
1083+
1084+
assert 1000 == client.ts().decrby("time-series-1", 10.1, timestamp=1000)
1085+
1086+
data_points = client.ts().range("time-series-1", "-", "+")
1087+
expected_points = [(1000, -11.1)]
1088+
assert expected_points == data_points

0 commit comments

Comments
 (0)