In this notebook, we will go thourhg a similar set of commands as those described in the Redis Data Types introduction but using the redis-py Python client from a Jupyter notebook.
Remember that Redis is a server, and it can be access in a distributed way by multiple clients in an Enterprise System. This notebook acts as a single client, and is just for educative purposes. The full power of Redis comes when used in an enterprise architecture!
While working with Redis with Python, you will notice that many operations on Redis data types are also available for the Python data types that we get as a result of some operations (e.g. lists). However we have to keep in mind that they operate at very different levels. Using the Redis server operations, and not the local Python equivalents, is the way to go for enterprise applications in order to keep our system scalability and availability (i.e. large sets, concurrent access, etc).
In [1]:
import redis
Then we can obtain a reference to our server. Asuming that we are running our Redis server and our Jupyter notebook server in the same host, with the default Redis server port, we can do as follows.
In [2]:
r = redis.StrictRedis(host='localhost', port=6379, db=0)
Now we can use r
to send Redis commands. For example, we can SET the value of the key my.key
as follows.
In [3]:
r.set('my.key', 'value1')
Out[3]:
In order to check our recently set key, we can use GET and pass the name of the key.
In [4]:
r.get('my.key')
Out[4]:
We can also check for the existence of a given key.
In [5]:
r.exists('my.key')
Out[5]:
In [6]:
r.exists('some.other.key')
Out[6]:
If we want to set multiple keys at once, we can use MSET and pass a Python dictionary as follows.
In [7]:
r.mset({'my.key':'value2', 'some.other.key':123})
Out[7]:
In [8]:
r.get('my.key')
Out[8]:
In [9]:
r.get('some.other.key')
Out[9]:
We can also increment the value of a given key in an atomic way.
In [10]:
r.incrby('some.other.key',10)
Out[10]:
Notice how the resulting type has been changed to integer!
With redis-py we can also set keys with limited time to live.
In [11]:
r.expire('some.other.key',1)
r.exists('some.other.key')
Out[11]:
Let's wait for a couple of seconds for the key to expire and check again.
In [12]:
from time import sleep
sleep(2)
r.exists('some.other.key')
Out[12]:
Finally, del is a reserved keyword in the Python syntax. Therefore redis-py uses 'delete' instead.
In [13]:
r.delete('my.key')
Out[13]:
Redis lists are linked lists of keys. We can insert and remove elements from both ends.
The LPUSH command adds a new element into a list, on the left.
In [14]:
r.lpush('my.list', 'elem1')
Out[14]:
The RPUSH command adds a new element into a list, on the right.
In [15]:
r.rpush('my.list', 'elem2')
Out[15]:
Finally the LRANGE command extracts ranges of elements from lists.
In [16]:
r.lrange('my.list',0,-1)
Out[16]:
In [17]:
r.lpush('my.list', 'elem0')
Out[17]:
In [18]:
r.lrange('my.list',0,-1)
Out[18]:
The result is returned as a Python list. We can use LLEN to check a Redis list lenght without requiring to store the result of lrange
and then use Python's len
.
In [19]:
r.llen('my.list')
Out[19]:
We can push multiple elements with a single call to push.
In [20]:
r.rpush('my.list','elem3','elem4')
Out[20]:
In [21]:
r.lrange('my.list',0,-1)
Out[21]:
Finally, we have the equivalent pop operations for both, right and left ends.
In [22]:
r.lpop('my.list')
Out[22]:
In [23]:
r.lrange('my.list',0,-1)
Out[23]:
In [24]:
r.rpop('my.list')
Out[24]:
In [25]:
r.lrange('my.list',0,-1)
Out[25]:
We can also TRIM Redis lists with redis-py. We need to pass three arguments: the name of the list, and the start and stop indexes.
In [26]:
r.lpush('my.list','elem0')
r.ltrim('my.list',0,2)
Out[26]:
In [27]:
r.lrange('my.list',0,-1)
Out[27]:
Notice as the last element has been dropped when triming the list. The lpush
/ltrim
sequence is a common pattern when inserting in a list that we want to keep size-fized.
In [28]:
r.delete('my.list')
Out[28]:
The equivalent of Python dictionaries are Redis hashes, with field-value pairs. We use the command HMSET.
In [29]:
r.hmset('my.hash', {'field1':'value1',
'field2': 1234})
Out[29]:
We can also set individual fields.
In [30]:
r.hset('my.hash','field3',True)
Out[30]:
We have methods to get individual and multiple fields from a hash.
In [31]:
r.hget('my.hash','field2')
Out[31]:
In [32]:
r.hmget('my.hash','field1','field2','field3')
Out[32]:
The result is returned as a list of values.
Increment operations are also available for hash fields.
In [33]:
r.hincrby('my.hash','field2',10)
Out[33]:
Redis Sets are unordered collections of strings. We can easily add multiple elements to a Redis set in redis-py as follows by using its implementation of SADD.
In [34]:
r.sadd('my.set', 1, 2, 3)
Out[34]:
As a result, we get the size of the set. If we want to check the elements within a set, we can use SMEMBERS.
In [35]:
r.smembers('my.set')
Out[35]:
In [36]:
type(r.smembers('my.set'))
Out[36]:
Notice that we get a Python set as a result. That opens the door to all sort of Python set operations. However, we can operate directly within the Redis server space, and still do things as checking an element membership using SISMEMBER. This is the way to go for enterprise applications in order to keep our system scalability and availability (i.e. large sets, concurrent access, etc).
In [37]:
r.sismember('my.set', 4)
Out[37]:
In [38]:
r.sismember('my.set', 1)
Out[38]:
The SPOP command extracts a random element (and we can use SRANDMEMBER to get one or more random elements without extraction).
In [39]:
elem = r.spop('my.set')
In [40]:
r.smembers('my.set')
Out[40]:
In [41]:
r.sadd('my.set',elem)
Out[41]:
In [42]:
r.smembers('my.set')
Out[42]:
Or if we want to be specific, we can just use SREM.
In [43]:
r.srem('my.set',2)
Out[43]:
In [44]:
r.smembers('my.set')
Out[44]:
In order to obtain the intersection between two sets, we can use SINTER.
In [45]:
r.sadd('my.other.set', 'A','B',1)
Out[45]:
In [46]:
r.smembers('my.other.set')
Out[46]:
In [47]:
r.sinter('my.set','my.other.set')
Out[47]:
That we get as a Python set. Alternatively, we can directly store the result as a new Redis set by using SINTERSTORE.
In [48]:
r.sinterstore('my.intersection','my.set','my.other.set')
Out[48]:
In [49]:
r.smembers('my.intersection')
Out[49]:
Similar operations are available for union and difference. Moreover, they can be applied to more than two sets. For example, let's create a union set with all the previous and store it in a new Redis set.
In [50]:
r.sadd('my.intersection','batman')
r.sunionstore('my.union','my.set','my.other.set','my.intersection')
Out[50]:
In [51]:
r.smembers('my.union')
Out[51]:
Finally, the number of elements of a given Redis set can be obtained with SCARD.
In [52]:
r.scard('my.union')
Out[52]:
Let's clean our server before leaving this section.
In [53]:
r.delete('my.set','my.other.set','my.intersection','my.union')
Out[53]:
In a Redis sorted set, every element is associated with a floating point value, called the score. Elements within the set are then ordered according to these scores. We add values to a sorted set by using the oepration ZADD.
In [54]:
r.zadd('my.sorted.set', 1, 'first')
r.zadd('my.sorted.set', 3, 'third')
r.zadd('my.sorted.set', 2, 'second')
r.zadd('my.sorted.set', 4, 'fourth')
r.zadd('my.sorted.set', 6, 'sixth')
Out[54]:
Sorted sets' scores can be updated at any time. Just calling ZADD against an element already included in the sorted set will update its score (and position).
It doesn't matter the order in which we insert the elements. When retrieving them using ZRANGE, they will be returned as a Python list ordered by score.
In [55]:
r.zrange('my.sorted.set',0,-1)
Out[55]:
And if we want also them in reverse order, we can call ZREVRANGE.
In [56]:
r.zrevrange('my.sorted.set',0,-1)
Out[56]:
Even more, we can slice the range by score by using ZRANGEBYSCORE.
In [57]:
r.zrangebyscore('my.sorted.set',2,4)
Out[57]:
A similar schema can be used to remove elements from the sorted set by score using ZREMRANGEBYSCORE.
In [58]:
r.zremrangebyscore('my.sorted.set',6,'inf')
r.zrange('my.sorted.set',0,-1)
Out[58]:
In [59]:
r.zrank('my.sorted.set','third')
Out[59]:
Remember that ranks and scores have the same order but different values! If what we want is the score, we can use ZSCORE.
In [60]:
r.zscore('my.sorted.set','third')
Out[60]:
Finally, there also a series of operations that operate on a sorted set in a lexicographical basis. They work when all the elements in the ser are inserted with the same value. For example, we can list the elements in the set sliced by its inital with ZRANGEBYLEX.