summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md65
-rw-r--r--redis/__init__.py5
-rw-r--r--redis/client.py109
-rw-r--r--tests/server_commands.py35
4 files changed, 139 insertions, 75 deletions
diff --git a/README.md b/README.md
index ceb5a20..5cab32d 100644
--- a/README.md
+++ b/README.md
@@ -18,12 +18,46 @@ From source:
## Getting Started
>>> import redis
- >>> r = redis.Redis(host='localhost', port=6379, db=0)
+ >>> r = redis.StrictRedis(host='localhost', port=6379, db=0)
>>> r.set('foo', 'bar')
True
>>> r.get('foo')
'bar'
+## API Reference
+
+The official Redis documentation does a great job of explaining each command in
+detail (http://redis.io/commands). redis-py exposes two client classes that
+implement these commands. The StrictRedis class attempts to adhere to the
+official official command syntax. There are a few exceptions:
+
+* SELECT: Not implemented. See the explanation in the Thread Safety section
+ below.
+* DEL: 'del' is a reserved keyword in the Python syntax. Therefore redis-py
+ uses 'delete' instead.
+* CONFIG GET|SET: These are implemented separately as config_get or config_set.
+* MULTI/EXEC: These are implemented as part of the Pipeline class. Calling
+ the pipeline method and specifying use_transaction=True will cause the
+ pipeline to be wrapped with the MULTI and EXEC statements when it is executed.
+ See more about Pipelines below.
+* SUBSCRIBE/LISTEN: Similar to pipelines, PubSub is implemented as a separate
+ class as it places the underlying connection in a state where it can't
+ execute non-pubsub commands. Calling the pubsub method from the Redis client
+ will return a PubSub instance where you can subscribe to channels and listen
+ for messages. You can call PUBLISH from both classes.
+
+In addition to the changes above, the Redis class, a subclass of StrictRedis,
+overrides several other commands to provide backwards compatibility with older
+versions of redis-py:
+
+* LREM: Order of 'num' and 'value' arguments reversed such that 'num' can
+ provide a default value of zero.
+* ZADD: Redis specifies the 'score' argument before 'value'. These were swapped
+ accidentally when being implemented and not discovered until after people
+ were already using it. The Redis class expects *args in the form of:
+ name1, score1, name2, score2, ...
+
+
## More Detail
### Connection Pools
@@ -232,35 +266,6 @@ which is much easier to read:
>>> r.transaction(client_side_incr, 'OUR-SEQUENCE-KEY')
[True]
-
-## API Reference
-
-The official Redis documentation does a great job of explaining each command in
-detail (http://redis.io/commands). In most cases, redis-py uses the same
-arguments as the official spec. There are a few exceptions noted here:
-
-* SELECT: Not implemented. See the explanation in the Thread Safety section
- above.
-* ZADD: Redis specifies the 'score' argument before 'value'. These were swapped
- accidentally when being implemented and not discovered until after people
- were already using it. As of Redis 2.4, ZADD will start supporting variable
- arguments. redis-py implements these as python keyword arguments where the
- name is the 'value' and the value is the 'score'.
-* DEL: 'del' is a reserved keyword in the Python syntax. Therefore redis-py
- uses 'delete' instead.
-* CONFIG GET|SET: These are implemented separately as config_get or config_set.
-* MULTI/EXEC: These are implemented as part of the Pipeline class. Calling
- the pipeline method and specifying use_transaction=True will cause the
- pipeline to be wrapped with the MULTI and EXEC statements when it is executed.
- See more about Pipelines above.
-* SUBSCRIBE/LISTEN: Similar to pipelines, PubSub is implemented as a separate
- class as it places the underlying connection in a state where it can't
- execute non-pubsub commands. Calling the pubsub method from the Redis client
- will return a PubSub instance where you can subscribe to channels and listen
- for messages. You can call PUBLISH from both classes.
-* LREM: Order of 'num' and 'value' arguments reversed such that 'num' can
- provide a default value of zero.
-
## Versioning scheme
redis-py is versioned after Redis. For example, redis-py 2.0.0 should
diff --git a/redis/__init__.py b/redis/__init__.py
index 48b7ddd..8f94044 100644
--- a/redis/__init__.py
+++ b/redis/__init__.py
@@ -1,4 +1,4 @@
-from redis.client import Redis
+from redis.client import Redis, StrictRedis
from redis.connection import (
ConnectionPool,
Connection,
@@ -20,7 +20,8 @@ __version__ = '2.4.9'
VERSION = tuple(map(int, __version__.split('.')))
__all__ = [
- 'Redis', 'ConnectionPool', 'Connection', 'UnixDomainSocketConnection',
+ 'Redis', 'StrictRedis', 'ConnectionPool',
+ 'Connection', 'UnixDomainSocketConnection',
'RedisError', 'ConnectionError', 'ResponseError', 'AuthenticationError',
'InvalidResponse', 'DataError', 'PubSubError', 'WatchError',
]
diff --git a/redis/client.py b/redis/client.py
index 24f8d81..7412d62 100644
--- a/redis/client.py
+++ b/redis/client.py
@@ -102,7 +102,7 @@ def parse_config(response, **options):
return response and pairs_to_dict(response) or {}
return response == 'OK'
-class Redis(object):
+class StrictRedis(object):
"""
Implementation of the Redis protocol.
@@ -628,13 +628,17 @@ class Redis(object):
"""
return self.execute_command('LRANGE', name, start, end)
- def lrem(self, name, value, num=0):
+ def lrem(self, name, count, value):
"""
- Remove the first ``num`` occurrences of ``value`` from list ``name``
+ Remove the first ``count`` occurrences of elements equal to ``value``
+ from the list stored at ``name``.
- If ``num`` is 0, then all occurrences will be removed
+ The count argument influences the operation in the following ways:
+ count > 0: Remove elements equal to value moving from head to tail.
+ count < 0: Remove elements equal to value moving from tail to head.
+ count = 0: Remove all elements equal to value.
"""
- return self.execute_command('LREM', name, num, value)
+ return self.execute_command('LREM', name, count, value)
def lset(self, name, index, value):
"Set ``position`` of list ``name`` to ``value``"
@@ -798,27 +802,27 @@ class Redis(object):
#### SORTED SET COMMANDS ####
- def zadd(self, name, value=None, score=None, **pairs):
- """
- For each kwarg in ``pairs``, add that item and it's score to the
- sorted set ``name``.
-
- The ``value`` and ``score`` arguments are deprecated.
- """
- all_pairs = []
- if value is not None or score is not None:
- if value is None or score is None:
- raise RedisError("Both 'value' and 'score' must be specified " \
- "to ZADD")
- warnings.warn(DeprecationWarning(
- "Passing 'value' and 'score' has been deprecated. " \
- "Please pass via kwargs instead."))
- all_pairs.append(score)
- all_pairs.append(value)
- for pair in pairs.iteritems():
- all_pairs.append(pair[1])
- all_pairs.append(pair[0])
- return self.execute_command('ZADD', name, *all_pairs)
+ def zadd(self, name, *args, **kwargs):
+ """
+ Set any number of score, element-name pairs to the key ``name``. Pairs
+ can be specified in two ways:
+
+ As *args, in the form of: score1, name1, score2, name2, ...
+ or as **kwargs, in the form of: name1=score1, name2=score2, ...
+
+ The following example would add four values to the 'my-key' key:
+ redis.zadd('my-key', 1.1, 'name1', 2.2, 'name2', name3=3.3, name4=4.4)
+ """
+ pieces = []
+ if args:
+ if len(args) % 2 != 0:
+ raise RedisError("ZADD requires an equal number of "
+ "values and scores")
+ pieces.extend(args)
+ for pair in kwargs.iteritems():
+ pieces.append(pair[1])
+ pieces.append(pair[0])
+ return self.execute_command('ZADD', name, *pieces)
def zcard(self, name):
"Return the number of elements in the sorted set ``name``"
@@ -1063,6 +1067,59 @@ class Redis(object):
return self.execute_command('PUBLISH', channel, message)
+class Redis(StrictRedis):
+ """
+ Provides backwards compatibility with older versions of redis-py that
+ changed arguemnts to some commands to be more Pythonic, sane, or
+ accident.
+ """
+
+ def lrem(self, name, value, num=0):
+ """
+ Remove the first ``num`` occurrences of elements equal to ``value``
+ from the list stored at ``name``.
+
+ The ``num`` argument influences the operation in the following ways:
+ num > 0: Remove elements equal to value moving from head to tail.
+ num < 0: Remove elements equal to value moving from tail to head.
+ num = 0: Remove all elements equal to value.
+ """
+ return self.execute_command('LREM', name, num, value)
+
+ def zadd(self, name, *args, **kwargs):
+ """
+ NOTE: The order of arguments differs from that of the official ZADD
+ command. For backwards compatability, this method accepts arguments
+ in the form of name1, score1, name2, score2, while the official Redis
+ documents expects score1, name1, score2, name2.
+
+ If you're looking to use the standard syntax, consider using the
+ StrictRedis class. See the API Reference section of the docs for more
+ information.
+
+ Set any number of element-name, score pairs to the key ``name``. Pairs
+ can be specified in two ways:
+
+ As *args, in the form of: name1, score1, name2, score2, ...
+ or as **kwargs, in the form of: name1=score1, name2=score2, ...
+
+ The following example would add four values to the 'my-key' key:
+ redis.zadd('my-key', 'name1', 1.1, 'name2', 2.2, name3=3.3, name4=4.4)
+ """
+ pieces = []
+ if args:
+ if len(args) % 2 != 0:
+ raise RedisError("ZADD requires an equal number of "
+ "values and scores")
+ temp_args = args
+ temp_args.reverse()
+ pieces.extend(temp_args)
+ for pair in kwargs.iteritems():
+ pieces.append(pair[1])
+ pieces.append(pair[0])
+ return self.execute_command('ZADD', name, *pieces)
+
+
class PubSub(object):
"""
PubSub provides publish, subscribe and listen support to Redis channels.
diff --git a/tests/server_commands.py b/tests/server_commands.py
index 9e3375d..e1f90d3 100644
--- a/tests/server_commands.py
+++ b/tests/server_commands.py
@@ -7,8 +7,8 @@ from redis.client import parse_info
class ServerCommandsTestCase(unittest.TestCase):
- def get_client(self):
- return redis.Redis(host='localhost', port=6379, db=9)
+ def get_client(self, cls=redis.Redis):
+ return cls(host='localhost', port=6379, db=9)
def setUp(self):
self.client = self.get_client()
@@ -438,20 +438,6 @@ class ServerCommandsTestCase(unittest.TestCase):
self.assert_(self.client.ltrim('a', 0, 1))
self.assertEquals(self.client.lrange('a', 0, 5), ['a', 'b'])
- def test_lpop(self):
- # no key
- self.assertEquals(self.client.lpop('a'), None)
- # key is not a list
- self.client['a'] = 'b'
- self.assertRaises(redis.ResponseError, self.client.lpop, 'a')
- del self.client['a']
- # real logic
- self.make_list('a', 'abc')
- self.assertEquals(self.client.lpop('a'), 'a')
- self.assertEquals(self.client.lpop('a'), 'b')
- self.assertEquals(self.client.lpop('a'), 'c')
- self.assertEquals(self.client.lpop('a'), None)
-
def test_rpop(self):
# no key
self.assertEquals(self.client.rpop('a'), None)
@@ -875,7 +861,7 @@ class ServerCommandsTestCase(unittest.TestCase):
# a non existant key should return empty list
self.assertEquals(self.client.zrange('b', 0, 1, withscores=True), [])
- def test_zrangebyscore(self):
+ def test_zrevrangebyscore(self):
# key is not a zset
self.client['a'] = 'a'
self.assertRaises(redis.ResponseError, self.client.zrevrangebyscore,
@@ -1193,6 +1179,21 @@ class ServerCommandsTestCase(unittest.TestCase):
self.assertEquals(self.client.lrange('sorted', 0, 10),
['vodka', 'milk', 'gin', 'apple juice'])
+ def test_strict_zadd(self):
+ client = self.get_client(redis.StrictRedis)
+ client.zadd('a', 1.0, 'a1', 2.0, 'a2', a3=3.0)
+ self.assertEquals(client.zrange('a', 0, 3, withscores=True),
+ [('a1', 1.0), ('a2', 2.0), ('a3', 3.0)])
+
+ def test_strict_lrem(self):
+ client = self.get_client(redis.StrictRedis)
+ client.rpush('a', 'a1')
+ client.rpush('a', 'a2')
+ client.rpush('a', 'a3')
+ client.rpush('a', 'a1')
+ client.lrem('a', 0, 'a1')
+ self.assertEquals(client.lrange('a', 0, -1), ['a2', 'a3'])
+
## BINARY SAFE
# TODO add more tests
def test_binary_get_set(self):