Per interagire con Redis in linguaggio Python, esistono diversi client, ma solo uno è
raccomandato dal sito ufficiale: redis-py. Possiamo installarlo mediante pip come segue:
pip install redis
In alternativa, possiamo utilizzare lo script presente tra i sorgenti scaricabili tramite il link su menzionato, come segue:
python setup.py install
Sarà poi possibile integrarlo nei propri programmi con:
import redis
Per avviare l'interazione, istanziamo un oggetto StrictRedis
al quale possiamo passare, come
argomenti, l'indirizzo IP e la porta TCP su cui il server Redis in esecuzione è raggiungibile, nonchè il numero di
database che vogliamo utilizzare e l'eventuale password per l'autenticazione. Ad esempio, il server
che abbiamo utilizzato per le nostre prove sfrutta l'indirizzo 192.168.56.3 e la porta 5555, la
password da fornire è "secret":
import redis
client = redis.StrictRedis(host='192.168.56.3', port=5555, password='secret',db=0)
Da questo momento in poi, non dovremo far altro che invocare i metodi dell'oggetto client
.
Gestione delle stringhe
I metodi che invocheremo richiameranno i comandi di Redis da riga di comando. Potremo
manipolare stringhe nel seguente modo:
> client.set('nome','Augusto')
True
> client.get('nome')
b'Augusto'
Si noti che il valore restituito con la seconda invocazione ha una "b" come prefisso che
denota un tipo di dato byte anzichè stringa. Ciò vale se utilizziamo Python 3,
mentre la versione per Python 2 restitusce oggetti di tipo stringa: a seconda della piattaforma con cui si lavora, le conversioni
necessarie spettano al programmatore.
Si può anche fissare una scadenza per la chiave usando i metodi setex
(impostazione del valore),
get
(lettura del valore, identico all'esempio precedente) e ttl
(verifica del tempo rimanente prima
della cancellazione). Nell'esempio che segue, impostiamo un valore che rimarrà
disponibile per 30 secondi. Durante questo lasso di tempo, invocheremo più volte ttl
che segnalerà un valore sempre minore del time-to-live
ma la stringa sarà intanto recuperabile con get
. Alla scadenza, la chiave risulterà non più esistente ed il
metodo ttl
restituirà -2
, che significa "timout scaduto", mentre -1
indica che la chiave esiste ma non ha
scadenza associata:
> client.setex('session_id', 30, 'ABCDEF123456')
True
> client.ttl('session_id')
25
> client.get('session_id')
b'ABCDEF123456'
> client.ttl('session_id')
14
> client.ttl('session_id')
-2
> client.get('session_id')
Utilizzare strutture dati
Si può arrivare intuitivamente a qualsiasi funzione di cui abbiamo bisogno in quanto i nomi dei metodi del
client ricalcano quelli delle funzioni da console. Vediamo, ad esempio, quali operazioni possono essere
svolte su una lista:
> client.lpush('animali', 'tigre')
1
> client.lpush('animali', 'leone','gorilla', 'giraffa')
4
> client.llen('animali')
4
> client.lindex('animali', 2)
b'leone'
> client.lpop('animali')
b'giraffa'
> client.lrange('animali', 0, -1)
[b'gorilla', b'leone', b'tigre']
> client.rpush('animali', 'ghepardo')
4
> client.rpush('animali', 'iena')
5
> client.lrange('animali', 0, -1)
[b'gorilla', b'leone', b'tigre', b'ghepardo', b'iena']
> client.rpop('animali')
b'iena'
> client.lrange('animali', 0, -1)
[b'gorilla', b'leone', b'tigre', b'ghepardo']
Abbiamo utilizzato alcuni metodi che riproducono i comportamenti più comuni:
- con
lpush
erpush
abbiamo inserito elementi, rispettivamente, in testa e in coda alla lista. Ognuna di tali
operazioni ha restituito il numero attuale di elementi; llen
viene utilizzato per conoscere la dimensione corrente della lista;lpop
erpop
hanno consentito di cancellare e leggere il valore in testa
e quello in coda;lrange
ha estratto tutti gli elementi di un segmento di lista che, in questo caso,
con -1 come argomento ha coinciso con la lista stessa;lindex
ha estratto un solo elemento (senza rimuoverlo) in una data posizione della lista
conteggiando gli indici a partire da zero
La lettura della documentazione degli omonimi comandi da console permetterà di sfruttare a pieno le funzionalità messe
a disposizione da redis-py.
Le pipeline
Questa libreria permette di eseguire più operazioni alla volta, accodandole in una pipeline. Con il metodo pipeline
si ottiene un oggetto su cui invocare le funzionalità la cui esecuzione sarà avviata alla chiamata di execute
. Il risultato di quest'ultima
sarà una lista di tutti gli oggetti restituiti dai metodi legati alla pipeline:
pipe=client.pipeline()
pipe.set('numero', 100)
pipe.set('altro_numero', 200)
pipe.get('numero')
pipe.execute()
Il risultato di execute
sarà la sequenza [True, True, b'100']
dove i primi due valori True
sono restituiti dai set
mentre il valore 100 sarà il numero estratto dalla get
.
Per fare uso della pipeline si può anche accodare tutte le invocazioni trasformandole in un'unica espressione:
client.pipeline().set('citta', 'Milano').set('altra_citta', 'Roma').lpush('nomi', 'Sandro', 'Noemi', 'Elena', 'Ivan').llen('nomi').get('altra_citta').execute()
[True, True, 4, 4, b'Roma']
Scambio di messaggi
Altra caratteristica di Redis supportata da questo client, è lo scambio di messaggi mediante modello
Publisher/Subscriber: da un lato, è presente del codice che inoltra dei messaggi in uno specifico canale,
dall'altro uno o più oggetti leggono da detto canale i messaggi inoltrati e Redis costituisce la piattaforma
di scambio di tutta questa attività.
Per realizzare tutto ciò con la libreria redis-py sarà innanzitutto indispensabile istituire dei subscriber
(definiti mediante il metodo pubsub
) da porre in ascolto di uno o più canali.
import redis
client = redis.StrictRedis(host='192.168.56.3', port=5555, db=0, password='secret')
subscriber = client.pubsub()
subscriber.subscribe('notizie-sport')
subscriber.subscribe('notizie-cinema')
A questo punto, avremo definito l'oggetto subscriber che ha sottoscritto due canali ('notizie-sport' e 'notizie-cinema')
dai quali attenderà aggiornamenti. Per l'inoltro dei messaggi attraverso i canali, dovremo semplicemente invocare il metodo
publish
sull'oggetto client e passargli come argomenti il nome del canale ed i dati che a questo sono affidati:
client.publish('notizie-sport', 'Milan-Ascoli 2-1')
client.publish('notizie-sport', 'Roma-Chievo 1-0')
I messaggi inoltrati sono due ed il subcriber sarà pronto a riceverli mediante il metodo
get_message
:
msg=subscriber.get_message()
dove msg sarà ora un oggetto dizionario le cui informazioni saranno legate ognuna ad una chiave:
{'type': 'message',
'pattern': None,
'channel': b'notizie-sport',
'data': b'Milan-Ascoli 2-1'}
Come si vede dall'altro "lato" del canale è stato estratto il messaggio "Milan-Ascoli 2-1" inviato dal
publisher. Questo è legato alla chiave "data" e potrà pertanto essere estratto ed utilizzato con l'espressione msg['data']
.
La proprietà "channel" ricorda da quale canale il messaggio è stato estratto (in quanto il subscriber potrebbe averne
sottoscritto più di uno) mentre "type" ci dice che si tratta proprio di un messaggio dato che con get_messagge
si estraggono anche altri tipi di informazioni.