venerdì 4 luglio 2008

LISTEN/NOTIFY con SQLAlchemy su Postgresql

In questo periodo sto sviluppando una semplice applicazione wxPython basata su SQLAlchemy che ha il compito di gestire gli ordini di una piccola sagra. Una delle richieste è stata quella di visualizzare su una label il totale degli ordini complessivi fatti in un certo momento. Le installazioni previste per questo programma sono più d'una e volevo fare in modo che la label si aggiornasse automaticamente a ogni nuovo ordine.

Visto che come database uso il (portentoso) postgresql ho pensato di avvalermi di una delle feature presenti in questo DBMS: il LISTEN/NOTIFY. In questo modo riesco a essere notificato degli eventi (CRUD) che coinvolgono una certa tabella.

Ovviamente SQLAlchemy non permette l'accesso diretto a queste funzioni, bisogna passare attraverso psycopg2, ma la procedura è abbastanza semplice. Quando l'applicazione viene eseguita viene fatto partire un thread che si mette in ascolto di una certa tabella:

def thread_notifica_ordini(aggiorna_componente):
conn = pg_db.connect()
conn.detach()
conn.connection.set_isolation_level(0)
curs = conn.connection.cursor()

#mi metto in attesa sulla tabella ordini
curs.execute("listen ordini")

print "In attesa di un NOTIFY"
while continua_thread:
if select.select([curs],[],[],None)==([],[],[]):
print "Timeout"
else:
if curs.isready() and continua_thread:
wx.CallAfter(aggiorna_componente)
print "Ricevuta notifica: %s" % str(curs.connection.notifies.pop())


la variabile "continua_thread" ha sempre valore True a meno che l'utente non decida di chiudere l'applicazione. La select.select scritta in quel modo si mette in attesa per un tempo indefinito, il listening sulla tabella ordini non va mai in timeout.
Alla funzione viene passata un'altra funzione "aggiorna_componente" che si limita ad aggiornare il componente grafico (la label) che visualizza il totale ordini complessivo. Devo usare la chiamata a wx.CallAfter perchè quando si lavora con i thread le chiamate a wx devono "tornare" al thread principale.


La funzione di notifica vera e propria è la seguente (che viene eseguita quando salvo un ordine):


def notifyordine():
""" apro una connessione per eseguire la notifica """
conn = pg_db.connect()

conn.detach()
conn.connection.set_isolation_level(0)
curs = conn.connection.cursor()

curs.execute("notify ordine")
conn.close()


Ovviamente usando l'intera procedura ci si lega a postgresql ma questo nel mio caso non è un problema, non ho nessuna legacy da dover supportare e mi è stata data ampia libertà in merito :)