Author: darcy Date: Sat Jan 5 10:19:44 2013 New Revision: 487 Log: Add pgnotify method.
Modified: trunk/module/pg.py Modified: trunk/module/pg.py ============================================================================== --- trunk/module/pg.py Sat Jan 5 10:19:07 2013 (r486) +++ trunk/module/pg.py Sat Jan 5 10:19:44 2013 (r487) @@ -18,6 +18,8 @@ """ +import select + from _pg import * try: frozenset @@ -126,9 +128,79 @@ """Returns ProgrammingError.""" return _db_error(msg, ProgrammingError) +class pgnotify(object): + """A PostgreSQL client-side asynchronous notification handler.""" -# The PostGreSQL database connection interface: + def __init__(self, pgconn, event, callback, arg_dict={}, timeout=None): + """ + pgconn - PostgreSQL connection object. + event - Event to LISTEN for. + callback - Event callback. + arg_dict - A dictionary passed as the argument to the callback. + timeout - Timeout in seconds; a floating point number denotes + fractions of seconds. If it is absent or None, the + callers will never time out.""" + + self.pgconn = pgconn + self.event = event + self.stop = 'stop_%s' % event + self.callback = callback + self.arg_dict = arg_dict + self.timeout = timeout + def __del__(self): + try: + self.pgconn.query('unlisten "%s"' % self.event) + self.pgconn.query('unlisten "%s"' % self.stop) + except pg.DatabaseError: + pass + + def __call__(self): + """ + Invoke the handler. + The handler actually LISTENs for two NOTIFY messages: + + <event> and stop_<event>. + + When either of these NOTIFY messages are received, its associated + 'pid' and 'event' are inserted into <arg_dict>, and the callback is + invoked with <arg_dict>. If the NOTIFY message is stop_<event>, the + handler UNLISTENs both <event> and stop_<event> and exits.""" + + self.pgconn.query('listen "%s"' % self.event) + self.pgconn.query('listen "%s"' % self.stop) + _ilist = [self.pgconn.fileno()] + + while 1: + ilist, olist, elist = select.select(_ilist, [], [], self.timeout) + if ilist == []: + # We timed out. + self.pgconn.query('unlisten "%s"' % self.event) + self.pgconn.query('unlisten "%s"' % self.stop) + self.callback(None) + return + else: + notice = self.pgconn.getnotify() + if notice is None: + continue + event, pid, extra = notice + if event in (self.event, self.stop): + self.arg_dict['pid'] = pid + self.arg_dict['event'] = event + self.callback(self.arg_dict) + if event == self.stop: + self.pgconn.query('unlisten "%s"' % self.event) + self.pgconn.query('unlisten "%s"' % self.stop) + return + else: + self.pgconn.query('unlisten "%s"' % self.event) + self.pgconn.query('unlisten "%s"' % self.stop) + raise pgnotifyError, \ + 'listening for ("%s", "%s") but notified of "%s"' \ + % (self.event, self.stop, event) + + +# The PostGreSQL database connection interface: class DB(object): """Wrapper class for the _pg connection type.""" @@ -838,6 +910,8 @@ self._do_debug(q + " %% %r" % (params,)) return int(self.db.query(q, params)) + def pgnotify(self, event, callback, arg_dict={}, timeout=None): + return pgnotify(self.db, event, callback, arg_dict, timeout) # if run as script, print some information _______________________________________________ PyGreSQL mailing list PyGreSQL@Vex.Net https://mail.vex.net/mailman/listinfo.cgi/pygresql