summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy McCurdy <andy@andymccurdy.com>2014-07-21 10:54:09 -0700
committerAndy McCurdy <andy@andymccurdy.com>2014-07-21 10:54:09 -0700
commit49b438bf149919011f2e08e031f329c570f9ed15 (patch)
tree49fdde7e8e59229f8e51d6af8048d029e050dfd4
parente03b828f083b50d46db03d5c6bf443c1fe2bfcba (diff)
parent9da5664e9d4b1f95c9c4e01db765996fdbc4dba6 (diff)
downloadredis-py-49b438bf149919011f2e08e031f329c570f9ed15.tar.gz
Merge branch 'master' into pr/505
Conflicts: redis/connection.py
-rw-r--r--CHANGES10
-rwxr-xr-xredis/client.py46
-rwxr-xr-xredis/connection.py35
-rw-r--r--tests/test_commands.py22
4 files changed, 95 insertions, 18 deletions
diff --git a/CHANGES b/CHANGES
index 32fce25..27ff773 100644
--- a/CHANGES
+++ b/CHANGES
@@ -6,6 +6,16 @@
another thread to release it, you need to disable thread local storage.
Refer to the doc strings on the Lock class about the thread_local
argument information.
+ * Fixed a regression in from_url where "charset" and "errors" weren't
+ valid options. "encoding" and "encoding_errors" are still accepted
+ and preferred.
+ * The "charset" and "errors" options have been deprecated. Passing
+ either to StrictRedis.__init__ or from_url will still work but will
+ also emit a DeprecationWarning. Instead use the "encoding" and
+ "encoding_errors" options.
+ * Fixed a compatability bug with Python 3 when the server closes a
+ connection.
+ * Added BITPOS command. Thanks https://github.com/jettify.
* 2.10.1
* Fixed a bug where Sentinel connections to a server that's no longer a
master and receives a READONLY error will disconnect and reconnect to
diff --git a/redis/client.py b/redis/client.py
index 0ab8081..74cca86 100755
--- a/redis/client.py
+++ b/redis/client.py
@@ -293,10 +293,10 @@ class StrictRedis(object):
bool
),
string_keys_to_dict(
- 'BITCOUNT DECRBY DEL GETBIT HDEL HLEN INCRBY LINSERT LLEN LPUSHX '
- 'PFADD PFCOUNT RPUSHX SADD SCARD SDIFFSTORE SETBIT SETRANGE '
- 'SINTERSTORE SREM STRLEN SUNIONSTORE ZADD ZCARD ZLEXCOUNT ZREM '
- 'ZREMRANGEBYLEX ZREMRANGEBYRANK ZREMRANGEBYSCORE',
+ 'BITCOUNT BITPOS DECRBY DEL GETBIT HDEL HLEN INCRBY LINSERT LLEN '
+ 'LPUSHX PFADD PFCOUNT RPUSHX SADD SCARD SDIFFSTORE SETBIT '
+ 'SETRANGE SINTERSTORE SREM STRLEN SUNIONSTORE ZADD ZCARD '
+ 'ZLEXCOUNT ZREM ZREMRANGEBYLEX ZREMRANGEBYRANK ZREMRANGEBYSCORE',
int
),
string_keys_to_dict('INCRBYFLOAT HINCRBYFLOAT', float),
@@ -392,18 +392,28 @@ class StrictRedis(object):
db=0, password=None, socket_timeout=None,
socket_connect_timeout=None,
socket_keepalive=None, socket_keepalive_options=None,
- connection_pool=None, charset='utf-8', errors='strict',
+ connection_pool=None, unix_socket_path=None,
+ encoding='utf-8', encoding_errors='strict',
+ charset=None, errors=None,
decode_responses=False, retry_on_timeout=False,
- unix_socket_path=None,
ssl=False, ssl_keyfile=None, ssl_certfile=None,
ssl_cert_reqs=None, ssl_ca_certs=None):
if not connection_pool:
+ if charset is not None:
+ warnings.warn(DeprecationWarning(
+ '"charset" is deprecated. Use "encoding" instead'))
+ encoding = charset
+ if errors is not None:
+ warnings.warn(DeprecationWarning(
+ '"errors" is deprecated. Use "encoding_errors" instead'))
+ encoding_errors = errors
+
kwargs = {
'db': db,
'password': password,
'socket_timeout': socket_timeout,
- 'encoding': charset,
- 'encoding_errors': errors,
+ 'encoding': encoding,
+ 'encoding_errors': encoding_errors,
'decode_responses': decode_responses,
'retry_on_timeout': retry_on_timeout
}
@@ -782,6 +792,26 @@ class StrictRedis(object):
"""
return self.execute_command('BITOP', operation, dest, *keys)
+ def bitpos(self, key, bit, start=None, end=None):
+ """
+ Return the position of the first bit set to 1 or 0 in a string.
+ ``start`` and ``end`` difines search range. The range is interpreted
+ as a range of bytes and not a range of bits, so start=0 and end=2
+ means to look at the first three bytes.
+ """
+ if bit not in (0, 1):
+ raise RedisError('bit must be 0 or 1')
+ params = [key, bit]
+
+ start is not None and params.append(start)
+
+ if start is not None and end is not None:
+ params.append(end)
+ elif start is None and end is not None:
+ raise RedisError("start argument is not set, "
+ "when end is specified")
+ return self.execute_command('BITPOS', *params)
+
def decr(self, name, amount=1):
"""
Decrements the value of ``key`` by ``amount``. If no key exists,
diff --git a/redis/connection.py b/redis/connection.py
index 7018051..864a9f8 100755
--- a/redis/connection.py
+++ b/redis/connection.py
@@ -110,7 +110,7 @@ class SocketBuffer(object):
while True:
data = self._sock.recv(socket_read_size)
# an empty string indicates the server shutdown the socket
- if isinstance(data, str) and len(data) == 0:
+ if isinstance(data, bytes) and len(data) == 0:
raise socket.error("Connection closed by remote server.")
buf.write(data)
data_length = len(data)
@@ -329,7 +329,7 @@ server.")
else:
buffer = self._sock.recv(socket_read_size)
# an empty string indicates the server shutdown the socket
- if isinstance(buffer, str) and len(buffer) == 0:
+ if isinstance(buffer, bytes) and len(buffer) == 0:
raise socket.error("Connection closed by remote \
server.")
except socket.timeout:
@@ -623,19 +623,23 @@ class Connection(object):
def pack_commands(self, commands):
"Pack multiple commands into the Redis protocol"
+ output = []
pieces = []
- buff = SYM_EMPTY
+ buffer_length = 0
for cmd in commands:
packed = self.pack_command(*cmd)[0]
- buff = SYM_EMPTY.join((buff, packed))
- if len(buff) > 6000:
- pieces.append(buff)
- buff = SYM_EMPTY
+ pieces.append(packed)
+ buffer_length += len(packed)
+
+ if buffer_length > 6000:
+ output.append(SYM_EMPTY.join(pieces))
+ buffer_length = 0
+ pieces = []
- if buff:
- pieces.append(buff)
- return pieces
+ if pieces:
+ output.append(SYM_EMPTY.join(pieces))
+ return output
class SSLConnection(Connection):
@@ -802,6 +806,17 @@ class ConnectionPool(object):
# update the arguments from the URL values
kwargs.update(url_options)
+
+ # backwards compatability
+ if 'charset' in kwargs:
+ warnings.warn(DeprecationWarning(
+ '"charset" is deprecated. Use "encoding" instead'))
+ kwargs['encoding'] = kwargs.pop('charset')
+ if 'errors' in kwargs:
+ warnings.warn(DeprecationWarning(
+ '"errors" is deprecated. Use "encoding_errors" instead'))
+ kwargs['encoding_errors'] = kwargs.pop('errors')
+
return cls(**kwargs)
def __init__(self, connection_class=Connection, max_connections=None,
diff --git a/tests/test_commands.py b/tests/test_commands.py
index aaff22e..286ea04 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -230,6 +230,28 @@ class TestRedisCommands(object):
assert int(binascii.hexlify(r['res2']), 16) == 0x0102FFFF
assert int(binascii.hexlify(r['res3']), 16) == 0x000000FF
+ @skip_if_server_version_lt('2.8.7')
+ def test_bitpos(self, r):
+ key = 'key:bitpos'
+ r.set(key, b('\xff\xf0\x00'))
+ assert r.bitpos(key, 0) == 12
+ assert r.bitpos(key, 0, 2, -1) == 16
+ assert r.bitpos(key, 0, -2, -1) == 12
+ r.set(key, b('\x00\xff\xf0'))
+ assert r.bitpos(key, 1, 0) == 8
+ assert r.bitpos(key, 1, 1) == 8
+ r.set(key, b('\x00\x00\x00'))
+ assert r.bitpos(key, 1) == -1
+
+ @skip_if_server_version_lt('2.8.7')
+ def test_bitpos_wrong_arguments(self, r):
+ key = 'key:bitpos:wrong:args'
+ r.set(key, b('\xff\xf0\x00'))
+ with pytest.raises(exceptions.RedisError):
+ r.bitpos(key, 0, end=1) == 12
+ with pytest.raises(exceptions.RedisError):
+ r.bitpos(key, 7) == 12
+
def test_decr(self, r):
assert r.decr('a') == -1
assert r['a'] == b('-1')