Skip to content
This repository was archived by the owner on Jan 24, 2023. It is now read-only.

Commit b451b43

Browse files
Added support for DUPLICATE_POLICY / ON_DUPLICATE keywords ( TS.INFO update accordingly ) (#66)
* [add] Added support for DUPLICATE_POLICY / ON_DUPLICATE keywords ( TS.INFO update accordingly ) * [fix] Fixed CI build issues around pytest not being present
1 parent 040eaa2 commit b451b43

File tree

3 files changed

+112
-13
lines changed

3 files changed

+112
-13
lines changed

.circleci/config.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,6 @@ jobs:
5858
. venv/bin/activate
5959
REDIS_PORT=6379 coverage run test_commands.py
6060
61-
- early_return_for_forked_pull_requests
62-
6361
- run:
6462
name: codecove
6563
command: |

redistimeseries/client.py

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class TSInfo(object):
1616
# As of RedisTimeseries >= v1.4 max_samples_per_chunk is deprecated in favor of chunk_size
1717
max_samples_per_chunk = None
1818
chunk_size = None
19+
duplicate_policy = None
1920

2021

2122
def __init__(self, args):
@@ -34,6 +35,10 @@ def __init__(self, args):
3435
self.chunk_size = self.max_samples_per_chunk * 16 # backward compatible changes
3536
if 'chunkSize' in response:
3637
self.chunk_size = response['chunkSize']
38+
if 'duplicatePolicy' in response:
39+
self.duplicate_policy = response['duplicatePolicy']
40+
if type(self.duplicate_policy) == bytes:
41+
self.duplicate_policy = self.duplicate_policy.decode()
3742

3843
def list_to_dict(aList):
3944
return {nativestr(aList[i][0]):nativestr(aList[i][1])
@@ -163,7 +168,15 @@ def appendChunkSize(params, chunk_size):
163168
if chunk_size is not None:
164169
params.extend(['CHUNK_SIZE', chunk_size])
165170

166-
def create(self, key, retention_msecs=None, uncompressed=False, labels={}, chunk_size=None):
171+
@staticmethod
172+
def appendDuplicatePolicy(params, command, duplicate_policy):
173+
if duplicate_policy is not None:
174+
if command == 'TS.ADD':
175+
params.extend(['ON_DUPLICATE', duplicate_policy])
176+
else:
177+
params.extend(['DUPLICATE_POLICY', duplicate_policy])
178+
179+
def create(self, key, **kwargs):
167180
"""
168181
Create a new time-series.
169182
@@ -177,28 +190,45 @@ def create(self, key, retention_msecs=None, uncompressed=False, labels={}, chunk
177190
labels: Set of label-value pairs that represent metadata labels of the key.
178191
chunk_size: Each time-serie uses chunks of memory of fixed size for time series samples.
179192
You can alter the default TSDB chunk size by passing the chunk_size argument (in Bytes).
193+
duplicate_policy: since RedisTimeSeries v1.4 you can specify the duplicate sample policy ( Configure what to do on duplicate sample. )
194+
Can be one of:
195+
- 'block': an error will occur for any out of order sample
196+
- 'first': ignore the new value
197+
- 'last': override with latest value
198+
- 'min': only override if the value is lower than the existing value
199+
- 'max': only override if the value is higher than the existing value
200+
When this is not set, the server-wide default will be used.
180201
"""
202+
retention_msecs = kwargs.get('retention_msecs', None)
203+
uncompressed = kwargs.get('uncompressed', False)
204+
labels = kwargs.get('labels', {})
205+
chunk_size = kwargs.get('chunk_size', None)
206+
duplicate_policy = kwargs.get('duplicate_policy', None)
181207
params = [key]
182208
self.appendRetention(params, retention_msecs)
183209
self.appendUncompressed(params, uncompressed)
184210
self.appendChunkSize(params, chunk_size)
211+
self.appendDuplicatePolicy(params, self.CREATE_CMD, duplicate_policy)
185212
self.appendLabels(params, labels)
186213

187214
return self.redis.execute_command(self.CREATE_CMD, *params)
188215

189-
def alter(self, key, retention_msecs=None, labels={}):
216+
def alter(self, key, **kwargs):
190217
"""
191218
Update the retention, labels of an existing key. The parameters
192219
are the same as TS.CREATE.
193220
"""
221+
retention_msecs = kwargs.get('retention_msecs', None)
222+
labels = kwargs.get('labels', {})
223+
duplicate_policy = kwargs.get('duplicate_policy', None)
194224
params = [key]
195225
self.appendRetention(params, retention_msecs)
226+
self.appendDuplicatePolicy(params, self.ALTER_CMD, duplicate_policy)
196227
self.appendLabels(params, labels)
197228

198229
return self.redis.execute_command(self.ALTER_CMD, *params)
199230

200-
def add(self, key, timestamp, value, retention_msecs=None,
201-
uncompressed=False, labels={}, chunk_size=None):
231+
def add(self, key, timestamp, value, **kwargs):
202232
"""
203233
Append (or create and append) a new sample to the series.
204234
@@ -214,11 +244,25 @@ def add(self, key, timestamp, value, retention_msecs=None,
214244
labels: Set of label-value pairs that represent metadata labels of the key.
215245
chunk_size: Each time-serie uses chunks of memory of fixed size for time series samples.
216246
You can alter the default TSDB chunk size by passing the chunk_size argument (in Bytes).
247+
duplicate_policy: since RedisTimeSeries v1.4 you can specify the duplicate sample policy ( Configure what to do on duplicate sample. )
248+
Can be one of:
249+
- 'block': an error will occur for any out of order sample
250+
- 'first': ignore the new value
251+
- 'last': override with latest value
252+
- 'min': only override if the value is lower than the existing value
253+
- 'max': only override if the value is higher than the existing value
254+
When this is not set, the server-wide default will be used.
217255
"""
256+
retention_msecs = kwargs.get('retention_msecs', None)
257+
uncompressed = kwargs.get('uncompressed', False)
258+
labels = kwargs.get('labels', {})
259+
chunk_size = kwargs.get('chunk_size', None)
260+
duplicate_policy = kwargs.get('duplicate_policy', None)
218261
params = [key, timestamp, value]
219262
self.appendRetention(params, retention_msecs)
220263
self.appendUncompressed(params, uncompressed)
221264
self.appendChunkSize(params, chunk_size)
265+
self.appendDuplicatePolicy(params, self.ADD_CMD, duplicate_policy)
222266
self.appendLabels(params, labels)
223267

224268
return self.redis.execute_command(self.ADD_CMD, *params)
@@ -237,8 +281,7 @@ def madd(self, ktv_tuples):
237281

238282
return self.redis.execute_command(self.MADD_CMD, *params)
239283

240-
def incrby(self, key, value, timestamp=None, retention_msecs=None,
241-
uncompressed=False, labels={}, chunk_size=None):
284+
def incrby(self, key, value, **kwargs):
242285
"""
243286
Increment (or create an time-series and increment) the latest sample's of a series.
244287
This command can be used as a counter or gauge that automatically gets history as a time series.
@@ -256,6 +299,11 @@ def incrby(self, key, value, timestamp=None, retention_msecs=None,
256299
chunk_size: Each time-serie uses chunks of memory of fixed size for time series samples.
257300
You can alter the default TSDB chunk size by passing the chunk_size argument (in Bytes).
258301
"""
302+
timestamp = kwargs.get('timestamp', None)
303+
retention_msecs = kwargs.get('retention_msecs', None)
304+
uncompressed = kwargs.get('uncompressed', False)
305+
labels = kwargs.get('labels', {})
306+
chunk_size = kwargs.get('chunk_size', None)
259307
params = [key, value]
260308
self.appendTimestamp(params, timestamp)
261309
self.appendRetention(params, retention_msecs)
@@ -265,8 +313,7 @@ def incrby(self, key, value, timestamp=None, retention_msecs=None,
265313

266314
return self.redis.execute_command(self.INCRBY_CMD, *params)
267315

268-
def decrby(self, key, value, timestamp=None, retention_msecs=None,
269-
uncompressed=False, labels={}, chunk_size=None):
316+
def decrby(self, key, value, **kwargs):
270317
"""
271318
Decrement (or create an time-series and decrement) the latest sample's of a series.
272319
This command can be used as a counter or gauge that automatically gets history as a time series.
@@ -284,6 +331,11 @@ def decrby(self, key, value, timestamp=None, retention_msecs=None,
284331
chunk_size: Each time-serie uses chunks of memory of fixed size for time series samples.
285332
You can alter the default TSDB chunk size by passing the chunk_size argument (in Bytes).
286333
"""
334+
timestamp = kwargs.get('timestamp', None)
335+
retention_msecs = kwargs.get('retention_msecs', None)
336+
uncompressed = kwargs.get('uncompressed', False)
337+
labels = kwargs.get('labels', {})
338+
chunk_size = kwargs.get('chunk_size', None)
287339
params = [key, value]
288340
self.appendTimestamp(params, timestamp)
289341
self.appendRetention(params, retention_msecs)

test_commands.py

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,35 @@ def testCreate(self):
4040
info = rts.info("time-serie-1")
4141
self.assertEqual(128, info.chunk_size)
4242

43+
# Test for duplicate policy
44+
for duplicate_policy in ["block","last","first","min","max"]:
45+
ts_name = "time-serie-ooo-{0}".format(duplicate_policy)
46+
self.assertTrue(rts.create(ts_name, duplicate_policy=duplicate_policy))
47+
info = rts.info(ts_name)
48+
self.assertEqual(duplicate_policy, info.duplicate_policy)
4349

4450
def testAlter(self):
4551
'''Test TS.ALTER calls'''
4652

47-
rts.create(1)
53+
self.assertTrue(rts.create(1))
4854
self.assertEqual(0, rts.info(1).retention_msecs)
49-
rts.alter(1, retention_msecs=10)
55+
self.assertTrue(rts.alter(1, retention_msecs=10))
5056
self.assertEqual({}, rts.info(1).labels)
5157
self.assertEqual(10, rts.info(1).retention_msecs)
52-
rts.alter(1, labels={'Time':'Series'})
58+
self.assertTrue(rts.alter(1, labels={'Time':'Series'}))
5359
self.assertEqual('Series', rts.info(1).labels['Time'])
5460
self.assertEqual(10, rts.info(1).retention_msecs)
5561
pipe = rts.pipeline()
5662
self.assertTrue(pipe.create(2))
5763

64+
if version is None or version < 14000:
65+
return
66+
info = rts.info(1)
67+
self.assertEqual(None, info.duplicate_policy)
68+
self.assertTrue(rts.alter(1, duplicate_policy='min'))
69+
info = rts.info(1)
70+
self.assertEqual('min', info.duplicate_policy)
71+
5872
def testAdd(self):
5973
'''Test TS.ADD calls'''
6074

@@ -76,6 +90,34 @@ def testAdd(self):
7690
info = rts.info("time-serie-1")
7791
self.assertEqual(128, info.chunk_size)
7892

93+
# Test for duplicate policy BLOCK
94+
self.assertEqual(1, rts.add("time-serie-add-ooo-block", 1, 5.0))
95+
try:
96+
rts.add("time-serie-add-ooo-block", 1, 5.0, duplicate_policy='block')
97+
except Exception as e:
98+
self.assertEqual("TSDB: Error at upsert, update is not supported in BLOCK mode",e.__str__())
99+
100+
# Test for duplicate policy LAST
101+
self.assertEqual(1, rts.add("time-serie-add-ooo-last", 1, 5.0))
102+
self.assertEqual(1, rts.add("time-serie-add-ooo-last", 1, 10.0, duplicate_policy='last'))
103+
self.assertEqual(10.0, rts.get("time-serie-add-ooo-last")[1])
104+
105+
# Test for duplicate policy FIRST
106+
self.assertEqual(1, rts.add("time-serie-add-ooo-first", 1, 5.0))
107+
self.assertEqual(1, rts.add("time-serie-add-ooo-first", 1, 10.0, duplicate_policy='first'))
108+
self.assertEqual(5.0, rts.get("time-serie-add-ooo-first")[1])
109+
110+
# Test for duplicate policy MAX
111+
self.assertEqual(1, rts.add("time-serie-add-ooo-max", 1, 5.0))
112+
self.assertEqual(1, rts.add("time-serie-add-ooo-max", 1, 10.0, duplicate_policy='max'))
113+
self.assertEqual(10.0, rts.get("time-serie-add-ooo-max")[1])
114+
115+
# Test for duplicate policy MIN
116+
self.assertEqual(1, rts.add("time-serie-add-ooo-min", 1, 5.0))
117+
self.assertEqual(1, rts.add("time-serie-add-ooo-min", 1, 10.0, duplicate_policy='min'))
118+
self.assertEqual(5.0, rts.get("time-serie-add-ooo-min")[1])
119+
120+
79121
def testMAdd(self):
80122
'''Test TS.MADD calls'''
81123

@@ -259,6 +301,13 @@ def testInfo(self):
259301
info = rts.info(1)
260302
self.assertEqual(5, info.retention_msecs)
261303
self.assertEqual(info.labels['currentLabel'], 'currentData')
304+
if version is None or version < 14000:
305+
return
306+
self.assertEqual(None, info.duplicate_policy)
307+
308+
rts.create('time-serie-2', duplicate_policy='min')
309+
info = rts.info('time-serie-2')
310+
self.assertEqual('min', info.duplicate_policy)
262311

263312
def testQueryIndex(self):
264313
'''Test TS.QUERYINDEX calls'''

0 commit comments

Comments
 (0)