Skip to content

Commit 97ed01d

Browse files
committed
HEXISTS and HLEN implementations
added a socket_timeout parameter to allow commands to timeout and raise an error
1 parent 5723a54 commit 97ed01d

File tree

4 files changed

+61
-13
lines changed

4 files changed

+61
-13
lines changed

CHANGES

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
* 1.3.6
2+
* Implementation of all Hash commands
3+
* Pipelines now wrap their execution with MULTI and EXEC commands to
4+
process all commands atomically.
5+
* Connections can now set timeout. If command execution exceeds the
6+
timeout, an exception is raised.
7+
* Numerous bug fixes and more tests.
18
* 1.3.4
29
* Skipped version numbers ahead so that the client version matches the
310
Redis version it is feature-compatible with. Going forward, the client

redis/client.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ def make_connection_key(self, host, port, db):
1515
"Create a unique key for the specified host, port and db"
1616
return '%s:%s:%s' % (host, port, db)
1717

18-
def get_connection(self, host, port, db, password):
18+
def get_connection(self, host, port, db, password, socket_timeout):
1919
"Return a specific connection for the specified host, port and db"
2020
key = self.make_connection_key(host, port, db)
2121
if key not in self.connections:
22-
self.connections[key] = Connection(host, port, db, password)
22+
self.connections[key] = Connection(
23+
host, port, db, password, socket_timeout)
2324
return self.connections[key]
2425

2526
def get_all_connections(self):
@@ -30,11 +31,13 @@ def get_all_connections(self):
3031

3132
class Connection(object):
3233
"Manages TCP communication to and from a Redis server"
33-
def __init__(self, host='localhost', port=6379, db=0, password=None):
34+
def __init__(self, host='localhost', port=6379, db=0, password=None,
35+
socket_timeout=None):
3436
self.host = host
3537
self.port = port
3638
self.db = db
3739
self.password = password
40+
self.socket_timeout = socket_timeout
3841
self._sock = None
3942
self._fp = None
4043

@@ -49,6 +52,7 @@ def connect(self, redis_instance):
4952
raise ConnectionError("Error %s connecting to %s:%s. %s." % \
5053
(e.args[0], self.host, self.port, e.args[1]))
5154
sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
55+
sock.settimeout(self.socket_timeout)
5256
self._sock = sock
5357
self._fp = sock.makefile('r')
5458
redis_instance._setup_connection()
@@ -179,13 +183,13 @@ class Redis(threading.local):
179183
"""
180184
RESPONSE_CALLBACKS = dict_merge(
181185
string_keys_to_dict(
182-
'AUTH DEL EXISTS EXPIRE HDEL MOVE MSETNX RENAMENX '
186+
'AUTH DEL EXISTS EXPIRE HDEL HEXISTS MOVE MSETNX RENAMENX '
183187
'SADD SISMEMBER SMOVE SETNX SREM ZADD ZREM',
184188
bool
185189
),
186190
string_keys_to_dict(
187-
'DECRBY INCRBY LLEN SCARD SDIFFSTORE SINTERSTORE SUNIONSTORE '
188-
'ZCARD ZRANK ZREMRANGEBYSCORE ZREVRANK',
191+
'DECRBY HLEN INCRBY LLEN SCARD SDIFFSTORE SINTERSTORE '
192+
'SUNIONSTORE ZCARD ZRANK ZREMRANGEBYSCORE ZREVRANK',
189193
int
190194
),
191195
string_keys_to_dict(
@@ -216,11 +220,11 @@ class Redis(threading.local):
216220
)
217221

218222
def __init__(self, host='localhost', port=6379,
219-
db=0, password=None,
223+
db=0, password=None, socket_timeout=None,
220224
charset='utf-8', errors='strict'):
221225
self.encoding = charset
222226
self.errors = errors
223-
self.select(host, port, db, password)
227+
self.select(host, port, db, password, socket_timeout)
224228

225229
#### Legacty accessors of connection information ####
226230
def _get_host(self):
@@ -350,9 +354,10 @@ def format_multi_bulk(self, *args, **options):
350354
)
351355

352356
#### CONNECTION HANDLING ####
353-
def get_connection(self, host, port, db, password):
357+
def get_connection(self, host, port, db, password, socket_timeout):
354358
"Returns a connection object"
355-
conn = connection_manager.get_connection(host, port, db, password)
359+
conn = connection_manager.get_connection(
360+
host, port, db, password, socket_timeout)
356361
# if for whatever reason the connection gets a bad password, make
357362
# sure a subsequent attempt with the right password makes its way
358363
# to the connection
@@ -370,15 +375,16 @@ def _setup_connection(self):
370375
raise AuthenticationError("Invalid Password")
371376
self.format_inline('SELECT', self.connection.db)
372377

373-
def select(self, host, port, db, password=None):
378+
def select(self, host, port, db, password=None, socket_timeout=None):
374379
"""
375380
Switch to a different database on the current host/port
376381
377382
Note this method actually replaces the underlying connection object
378383
prior to issuing the SELECT command. This makes sure we protect
379384
the thread-safe connections
380385
"""
381-
self.connection = self.get_connection(host, port, db, password)
386+
self.connection = self.get_connection(
387+
host, port, db, password, socket_timeout)
382388

383389
#### SERVER INFORMATION ####
384390
def bgsave(self):
@@ -920,6 +926,10 @@ def zscore(self, name, value):
920926
def hdel(self, name, key):
921927
"Delete ``key`` from hash ``name``"
922928
return self.format_bulk('HDEL', name, key)
929+
930+
def hexists(self, name, key):
931+
"Returns a boolean indicating if ``key`` exists within hash ``name``"
932+
return self.format_bulk('HEXISTS', name, key)
923933

924934
def hget(self, name, key):
925935
"Return the value of ``key`` within the hash ``name``"
@@ -933,6 +943,10 @@ def hkeys(self, name):
933943
"Return the list of keys within hash ``name``"
934944
return self.format_inline('HKEYS', name)
935945

946+
def hlen(self, name):
947+
"Return the number of elements in hash ``name``"
948+
return self.format_inline('HLEN', name)
949+
936950
def hset(self, name, key, value):
937951
"""
938952
Set ``key`` to ``value`` within hash ``name``

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
@brief Setuptools configuration for redis client
88
"""
99

10-
version = '1.34.1'
10+
version = '1.36'
1111

1212
sdict = {
1313
'name' : 'redis',

tests/server_commands.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,20 @@ def test_hdel(self):
728728
self.assert_(self.client.hdel('a', 'a2'))
729729
self.assertEquals(self.client.hget('a', 'a2'), None)
730730

731+
def test_hexists(self):
732+
# key is not a hash
733+
self.client['a'] = 'a'
734+
self.assertRaises(redis.ResponseError, self.client.hexists, 'a', 'a1')
735+
del self.client['a']
736+
# no key
737+
self.assertEquals(self.client.hexists('a', 'a1'), False)
738+
# real logic
739+
self.make_hash('a', {'a1': 1, 'a2': 2, 'a3': 3})
740+
self.assertEquals(self.client.hexists('a', 'a1'), True)
741+
self.assertEquals(self.client.hexists('a', 'a4'), False)
742+
self.client.hdel('a', 'a1')
743+
self.assertEquals(self.client.hexists('a', 'a1'), False)
744+
731745
def test_hgetall(self):
732746
# key is not a hash
733747
self.client['a'] = 'a'
@@ -758,6 +772,19 @@ def test_hkeys(self):
758772
remote_keys.sort()
759773
self.assertEquals(keys, remote_keys)
760774

775+
def test_hlen(self):
776+
# key is not a hash
777+
self.client['a'] = 'a'
778+
self.assertRaises(redis.ResponseError, self.client.hlen, 'a')
779+
del self.client['a']
780+
# no key
781+
self.assertEquals(self.client.hlen('a'), 0)
782+
# real logic
783+
self.make_hash('a', {'a1': 1, 'a2': 2, 'a3': 3})
784+
self.assertEquals(self.client.hlen('a'), 3)
785+
self.client.hdel('a', 'a3')
786+
self.assertEquals(self.client.hlen('a'), 2)
787+
761788
def test_hvals(self):
762789
# key is not a hash
763790
self.client['a'] = 'a'

0 commit comments

Comments
 (0)