Skip to content

Commit 18b492e

Browse files
author
Matias Melograno
committed
added wrapper for PipelineRedis
1 parent a002fcd commit 18b492e

File tree

3 files changed

+101
-38
lines changed

3 files changed

+101
-38
lines changed

splitio/storage/adapters/redis.py

Lines changed: 78 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -53,25 +53,18 @@ class SentinelConfigurationException(Exception):
5353
pass
5454

5555

56-
class RedisAdapter(object): # pylint: disable=too-many-public-methods
57-
"""
58-
Instance decorator for Redis clients such as StrictRedis.
59-
60-
Adds an extra layer handling addition/removal of user prefix when handling
61-
keys
62-
"""
56+
class PrefixTrait(object):
57+
"""Prefix generator."""
6358

64-
def __init__(self, decorated, prefix=None):
59+
def __init__(self, prefix=None):
6560
"""
66-
Store the user prefix and the redis client instance.
61+
Class constructor.
6762
68-
:param decorated: Instance of redis cache client to decorate.
6963
:param prefix: User prefix to add.
7064
"""
7165
self._prefix = prefix
72-
self._decorated = decorated
7366

74-
def _add_prefix(self, k):
67+
def add_prefix(self, k):
7568
"""
7669
Add a prefix to the contents of k.
7770
@@ -105,7 +98,7 @@ def _add_prefix(self, k):
10598
"Cannot append prefix correctly. Wrong type for key(s) provided"
10699
)
107100

108-
def _remove_prefix(self, k):
101+
def remove_prefix(self, k):
109102
"""
110103
Remove the user prefix from a key before handling it back to the requester.
111104
@@ -127,6 +120,25 @@ def _remove_prefix(self, k):
127120
"Cannot remove prefix correctly. Wrong type for key(s) provided"
128121
)
129122

123+
124+
class RedisAdapter(object): # pylint: disable=too-many-public-methods
125+
"""
126+
Instance decorator for Redis clients such as StrictRedis.
127+
128+
Adds an extra layer handling addition/removal of user prefix when handling
129+
keys
130+
"""
131+
132+
def __init__(self, decorated, prefix=None):
133+
"""
134+
Store the user prefix and the redis client instance.
135+
136+
:param decorated: Instance of redis cache client to decorate.
137+
:param prefix: User prefix to add.
138+
"""
139+
self._decorated = decorated
140+
self._prefix_trait = PrefixTrait(prefix)
141+
130142
# Below starts a list of methods that implement the interface of a standard
131143
# redis client.
132144

@@ -135,7 +147,7 @@ def keys(self, pattern):
135147
try:
136148
return [
137149
_bytes_to_string(key)
138-
for key in self._remove_prefix(self._decorated.keys(self._add_prefix(pattern)))
150+
for key in self._prefix_trait.remove_prefix(self._decorated.keys(self._prefix_trait.add_prefix(pattern)))
139151
]
140152
except RedisError as exc:
141153
raise_from(RedisAdapterException('Failed to execute keys operation'), exc)
@@ -144,43 +156,43 @@ def set(self, name, value, *args, **kwargs):
144156
"""Mimic original redis function but using user custom prefix."""
145157
try:
146158
return self._decorated.set(
147-
self._add_prefix(name), value, *args, **kwargs
159+
self._prefix_trait.add_prefix(name), value, *args, **kwargs
148160
)
149161
except RedisError as exc:
150162
raise RedisAdapterException('Failed to execute set operation', exc)
151163

152164
def get(self, name):
153165
"""Mimic original redis function but using user custom prefix."""
154166
try:
155-
return _bytes_to_string(self._decorated.get(self._add_prefix(name)))
167+
return _bytes_to_string(self._decorated.get(self._prefix_trait.add_prefix(name)))
156168
except RedisError as exc:
157169
raise RedisAdapterException('Error executing get operation', exc)
158170

159171
def setex(self, name, time, value):
160172
"""Mimic original redis function but using user custom prefix."""
161173
try:
162-
return self._decorated.setex(self._add_prefix(name), time, value)
174+
return self._decorated.setex(self._prefix_trait.add_prefix(name), time, value)
163175
except RedisError as exc:
164176
raise RedisAdapterException('Error executing setex operation', exc)
165177

166178
def delete(self, *names):
167179
"""Mimic original redis function but using user custom prefix."""
168180
try:
169-
return self._decorated.delete(*self._add_prefix(list(names)))
181+
return self._decorated.delete(*self._prefix_trait.add_prefix(list(names)))
170182
except RedisError as exc:
171183
raise_from(RedisAdapterException('Error executing delete operation'), exc)
172184

173185
def exists(self, name):
174186
"""Mimic original redis function but using user custom prefix."""
175187
try:
176-
return self._decorated.exists(self._add_prefix(name))
188+
return self._decorated.exists(self._prefix_trait.add_prefix(name))
177189
except RedisError as exc:
178190
raise_from(RedisAdapterException('Error executing exists operation'), exc)
179191

180192
def lrange(self, key, start, end):
181193
"""Mimic original redis function but using user custom prefix."""
182194
try:
183-
return self._decorated.lrange(self._add_prefix(key), start, end)
195+
return self._decorated.lrange(self._prefix_trait.add_prefix(key), start, end)
184196
except RedisError as exc:
185197
raise_from(RedisAdapterException('Error executing exists operation'), exc)
186198

@@ -189,7 +201,7 @@ def mget(self, names):
189201
try:
190202
return [
191203
_bytes_to_string(item)
192-
for item in self._decorated.mget(self._add_prefix(names))
204+
for item in self._decorated.mget(self._prefix_trait.add_prefix(names))
193205
]
194206
except RedisError as exc:
195207
raise_from(RedisAdapterException('Error executing mget operation'), exc)
@@ -199,110 +211,140 @@ def smembers(self, name):
199211
try:
200212
return [
201213
_bytes_to_string(item)
202-
for item in self._decorated.smembers(self._add_prefix(name))
214+
for item in self._decorated.smembers(self._prefix_trait.add_prefix(name))
203215
]
204216
except RedisError as exc:
205217
raise_from(RedisAdapterException('Error executing smembers operation'), exc)
206218

207219
def sadd(self, name, *values):
208220
"""Mimic original redis function but using user custom prefix."""
209221
try:
210-
return self._decorated.sadd(self._add_prefix(name), *values)
222+
return self._decorated.sadd(self._prefix_trait.add_prefix(name), *values)
211223
except RedisError as exc:
212224
raise_from(RedisAdapterException('Error executing sadd operation'), exc)
213225

214226
def srem(self, name, *values):
215227
"""Mimic original redis function but using user custom prefix."""
216228
try:
217-
return self._decorated.srem(self._add_prefix(name), *values)
229+
return self._decorated.srem(self._prefix_trait.add_prefix(name), *values)
218230
except RedisError as exc:
219231
raise_from(RedisAdapterException('Error executing srem operation'), exc)
220232

221233
def sismember(self, name, value):
222234
"""Mimic original redis function but using user custom prefix."""
223235
try:
224-
return self._decorated.sismember(self._add_prefix(name), value)
236+
return self._decorated.sismember(self._prefix_trait.add_prefix(name), value)
225237
except RedisError as exc:
226238
raise_from(RedisAdapterException('Error executing sismember operation'), exc)
227239

228240
def eval(self, script, number_of_keys, *keys):
229241
"""Mimic original redis function but using user custom prefix."""
230242
try:
231-
return self._decorated.eval(script, number_of_keys, *self._add_prefix(list(keys)))
243+
return self._decorated.eval(script, number_of_keys, *self._prefix_trait.add_prefix(list(keys)))
232244
except RedisError as exc:
233245
raise_from(RedisAdapterException('Error executing eval operation'), exc)
234246

235247
def hset(self, name, key, value):
236248
"""Mimic original redis function but using user custom prefix."""
237249
try:
238-
return self._decorated.hset(self._add_prefix(name), key, value)
250+
return self._decorated.hset(self._prefix_trait.add_prefix(name), key, value)
239251
except RedisError as exc:
240252
raise_from(RedisAdapterException('Error executing hset operation'), exc)
241253

242254
def hget(self, name, key):
243255
"""Mimic original redis function but using user custom prefix."""
244256
try:
245-
return _bytes_to_string(self._decorated.hget(self._add_prefix(name), key))
257+
return _bytes_to_string(self._decorated.hget(self._prefix_trait.add_prefix(name), key))
246258
except RedisError as exc:
247259
raise_from(RedisAdapterException('Error executing hget operation'), exc)
248260

249261
def incr(self, name, amount=1):
250262
"""Mimic original redis function but using user custom prefix."""
251263
try:
252-
return self._decorated.incr(self._add_prefix(name), amount)
264+
return self._decorated.incr(self._prefix_trait.add_prefix(name), amount)
253265
except RedisError as exc:
254266
raise_from(RedisAdapterException('Error executing incr operation'), exc)
255267

256268
def getset(self, name, value):
257269
"""Mimic original redis function but using user custom prefix."""
258270
try:
259-
return _bytes_to_string(self._decorated.getset(self._add_prefix(name), value))
271+
return _bytes_to_string(self._decorated.getset(self._prefix_trait.add_prefix(name), value))
260272
except RedisError as exc:
261273
raise_from(RedisAdapterException('Error executing getset operation'), exc)
262274

263275
def rpush(self, key, *values):
264276
"""Mimic original redis function but using user custom prefix."""
265277
try:
266-
return self._decorated.rpush(self._add_prefix(key), *values)
278+
return self._decorated.rpush(self._prefix_trait.add_prefix(key), *values)
267279
except RedisError as exc:
268280
raise_from(RedisAdapterException('Error executing rpush operation'), exc)
269281

270282
def expire(self, key, value):
271283
"""Mimic original redis function but using user custom prefix."""
272284
try:
273-
return self._decorated.expire(self._add_prefix(key), value)
285+
return self._decorated.expire(self._prefix_trait.add_prefix(key), value)
274286
except RedisError as exc:
275287
raise_from(RedisAdapterException('Error executing expire operation'), exc)
276288

277289
def rpop(self, key):
278290
"""Mimic original redis function but using user custom prefix."""
279291
try:
280-
return _bytes_to_string(self._decorated.rpop(self._add_prefix(key)))
292+
return _bytes_to_string(self._decorated.rpop(self._prefix_trait.add_prefix(key)))
281293
except RedisError as exc:
282294
raise_from(RedisAdapterException('Error executing rpop operation'), exc)
283295

284296
def ttl(self, key):
285297
"""Mimic original redis function but using user custom prefix."""
286298
try:
287-
return self._decorated.ttl(self._add_prefix(key))
299+
return self._decorated.ttl(self._prefix_trait.add_prefix(key))
288300
except RedisError as exc:
289301
raise_from(RedisAdapterException('Error executing ttl operation'), exc)
290302

291303
def lpop(self, key):
292304
"""Mimic original redis function but using user custom prefix."""
293305
try:
294-
return self._decorated.lpop(self._add_prefix(key))
306+
return self._decorated.lpop(self._prefix_trait.add_prefix(key))
295307
except RedisError as exc:
296308
raise_from(RedisAdapterException('Error executing lpop operation'), exc)
297309

298310
def pipeline(self):
299311
"""Mimic original redis pipeline."""
300312
try:
301-
return self._decorated.pipeline()
313+
return RedisPipelineAdapter(self._decorated, self._prefix_trait)
302314
except RedisError as exc:
303315
raise_from(RedisAdapterException('Error executing ttl operation'), exc)
304316

305317

318+
class RedisPipelineAdapter(object):
319+
"""
320+
Instance decorator for Redis Pipeline.
321+
322+
Adds an extra layer handling addition/removal of user prefix when handling
323+
keys
324+
"""
325+
def __init__(self, decorated, prefix_trait):
326+
"""
327+
Store the user prefix and the redis client instance.
328+
329+
:param decorated: Instance of redis cache client to decorate.
330+
:param prefix_trait: Prefix Trait utility
331+
"""
332+
self._prefix_trait = prefix_trait
333+
self._pipe = decorated.pipeline()
334+
335+
def rpush(self, key, *values):
336+
"""Mimic original redis function but using user custom prefix."""
337+
self._pipe.rpush(self._prefix_trait.add_prefix(key), *values)
338+
339+
def incr(self, name, amount=1):
340+
"""Mimic original redis function but using user custom prefix."""
341+
self._pipe.incr(self._prefix_trait.add_prefix(name), amount)
342+
343+
def execute(self):
344+
"""Mimic original redis function but using user custom prefix."""
345+
return self._pipe.execute()
346+
347+
306348
def _build_default_client(config): # pylint: disable=too-many-locals
307349
"""
308350
Build a redis adapter.

tests/integration/test_client_e2e.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,7 @@ def teardown_method(self):
743743
"""Clear redis cache."""
744744
keys_to_delete = [
745745
"SPLITIO/python-1.2.3/some_ip/latency.sdk.getTreatment.bucket.0",
746+
"SPLITIO/python-1.2.3/some_ip/latency.sdk.getTreatmentWithConfig.bucket.0",
746747
"SPLITIO.segment.human_beigns",
747748
"SPLITIO.segment.employees.till",
748749
"SPLITIO.split.sample_feature",
@@ -752,6 +753,7 @@ def teardown_method(self):
752753
"SPLITIO.split.whitelist_feature",
753754
"SPLITIO.segment.employees",
754755
"SPLITIO/python-1.2.3/some_ip/latency.sdk.getTreatments.bucket.0",
756+
"SPLITIO/python-1.2.3/some_ip/latency.sdk.getTreatmentsWithConfig.bucket.0",
755757
"SPLITIO.split.regex_test",
756758
"SPLITIO.segment.human_beigns.till",
757759
"SPLITIO.split.boolean_test",
@@ -805,7 +807,7 @@ def setup_method(self):
805807
self.factory = SplitFactory('some_api_key', storages, True, recorder) # pylint:disable=attribute-defined-outside-init
806808

807809

808-
class LocalhostIntegrationTests(object): #pylint: disable=too-few-public-methods
810+
class LocalhostIntegrationTests(object): # pylint: disable=too-few-public-methods
809811
"""Client & Manager integration tests."""
810812

811813
def test_localhost_e2e(self):

tests/storage/adapters/test_redis_adapter.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
import pytest
44
from splitio.storage.adapters import redis
5-
from redis import StrictRedis
5+
from redis import StrictRedis, Redis
66
from redis.sentinel import Sentinel
77

8+
89
class RedisStorageAdapterTests(object):
910
"""Redis storage adapter test cases."""
1011

@@ -178,3 +179,21 @@ def test_sentinel_ssl_fails(self):
178179
'redisSentinels': ['a', 'b'],
179180
'redisSsl': True,
180181
})
182+
183+
184+
class RedisPipelineAdapterTests(object):
185+
"""Redis pipelined adapter test cases."""
186+
187+
def test_forwarding(self, mocker):
188+
"""Test that all redis functions forward prefix appropriately."""
189+
redis_mock = mocker.Mock(StrictRedis)
190+
redis_mock_2 = mocker.Mock(Redis)
191+
redis_mock.pipeline.return_value = redis_mock_2
192+
prefix_trait = redis.PrefixTrait('some_prefix')
193+
adapter = redis.RedisPipelineAdapter(redis_mock, prefix_trait)
194+
195+
adapter.rpush('key1', 'value1', 'value2')
196+
assert redis_mock_2.rpush.mock_calls[0] == mocker.call('some_prefix.key1', 'value1', 'value2')
197+
198+
adapter.incr('key1')
199+
assert redis_mock_2.incr.mock_calls[0] == mocker.call('some_prefix.key1', 1)

0 commit comments

Comments
 (0)