Changeset 762 for trunk


Ignore:
Timestamp:
Jan 17, 2016, 11:32:18 AM (4 years ago)
Author:
cito
Message:

Docs and 100% test coverage for NotificationHandler?

Location:
trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/docs/contents/changelog.rst

    r748 r762  
    5757- Fix notification handler (Thanks Patrick TJ McPhee).
    5858- Fix a small issue with large objects.
     59- Minor improvements of the NotificationHandler.
     60- Converted documentation to Sphinx and added many missing parts.
    5961- The tutorial files have become a chapter in the documentation.
    60 - Greatly improve unit testing, tests run with Python 2.4 to 2.7 again.
     62- Greatly improved unit testing, tests run with Python 2.4 to 2.7 again.
    6163
    6264Version 4.1.1 (2013-01-08)
    6365--------------------------
    64 - Add WhenNotified class and method.  Replaces need for third party pgnotify.
     66- Add NotificationHandler class and method.  Replaces need for pgnotify.
    6567- Sharpen test for inserting current_timestamp.
    6668- Add more quote tests.  False and 0 should evaluate to NULL.
  • trunk/docs/contents/pg/db_wrapper.rst

    r749 r762  
    606606
    607607.. versionadded:: 4.1
     608
     609notification_handler -- create a notification handler
     610-----------------------------------------------------
     611
     612.. class:: DB.notification_handler(event, callback, [arg_dict], [timeout], [stop_event])
     613
     614    Create a notification handler instance
     615
     616    :param str event: the name of an event to listen for
     617    :param callback: a callback function
     618    :param dict arg_dict: an optional dictionary for passing arguments
     619    :param timeout: the time-out when waiting for notifications
     620    :type timeout: int, float or None
     621    :param str stop_event: an optional different name to be used as stop event
     622
     623This method creates a :class:`pg.NotificationHandler` object using the
     624:class:`DB` connection as explained under :doc:`notification`.
     625
     626.. versionadded:: 4.1.1
  • trunk/docs/contents/pg/index.rst

    r710 r762  
    1515    query
    1616    large_objects
     17    notification
  • trunk/pg.py

    r748 r762  
    106106    """A PostgreSQL client-side asynchronous notification handler."""
    107107
    108     def __init__(self, db, event, callback, arg_dict=None, timeout=None):
     108    def __init__(self, db, event, callback=None,
     109            arg_dict=None, timeout=None, stop_event=None):
    109110        """Initialize the notification handler.
    110111
    111         db       - PostgreSQL connection object.
    112         event    - Event (notification channel) to LISTEN for.
    113         callback - Event callback function.
    114         arg_dict - A dictionary passed as the argument to the callback.
    115         timeout  - Timeout in seconds; a floating point number denotes
    116                    fractions of seconds. If it is absent or None, the
    117                    callers will never time out.
    118         """
    119         if isinstance(db, DB):
    120             db = db.db
     112        You must pass a PyGreSQL database connection, the name of an
     113        event (notification channel) to listen for and a callback function.
     114
     115        You can also specify a dictionary arg_dict that will be passed as
     116        the single argument to the callback function, and a timeout value
     117        in seconds (a floating point number denotes fractions of seconds).
     118        If it is absent or None, the callers will never time out.  If the
     119        timeout is reached, the callback function will be called with a
     120        single argument that is None.  If you set the timeout to zero,
     121        the handler will poll notifications synchronously and return.
     122
     123        You can specify the name of the event that will be used to signal
     124        the handler to stop listening as stop_event. By default, it will
     125        be the event name prefixed with 'stop_'.
     126        """
    121127        self.db = db
    122128        self.event = event
    123         self.stop_event = 'stop_%s' % event
     129        self.stop_event = stop_event or 'stop_%s' % event
    124130        self.listening = False
    125131        self.callback = callback
     
    130136
    131137    def __del__(self):
    132         self.close()
     138        self.unlisten()
    133139
    134140    def close(self):
     
    156162        """Generate a notification.
    157163
    158         Note: If the main loop is running in another thread, you must pass
    159         a different database connection to avoid a collision.
    160         """
    161         if not db:
    162             db = self.db
     164        Optionally, you can pass a payload with the notification.
     165
     166        If you set the stop flag, a stop notification will be sent that
     167        will cause the handler to stop listening.
     168
     169        Note: If the notification handler is running in another thread, you
     170        must pass a different database connection since PyGreSQL database
     171        connections are not thread-safe.
     172        """
    163173        if self.listening:
     174            if not db:
     175                db = self.db
    164176            q = 'notify "%s"' % (self.stop_event if stop else self.event)
    165177            if payload:
     
    167179            return db.query(q)
    168180
    169     def __call__(self, close=False):
     181    def __call__(self):
    170182        """Invoke the notification handler.
    171183
    172         The handler is a loop that actually LISTENs for two NOTIFY messages:
    173 
    174         <event> and stop_<event>.
    175 
    176         When either of these NOTIFY messages are received, its associated
    177         'pid' and 'event' are inserted into <arg_dict>, and the callback is
    178         invoked with <arg_dict>. If the NOTIFY message is stop_<event>, the
    179         handler UNLISTENs both <event> and stop_<event> and exits.
     184        The handler is a loop that listens for notifications on the event
     185        and stop event channels.  When either of these notifications are
     186        received, its associated 'pid', 'event' and 'extra' (the payload
     187        passed with the notification) are inserted into its arg_dict
     188        dictionary and the callback is invoked with this dictionary as
     189        a single argument.  When the handler receives a stop event, it
     190        stops listening to both events and return.
     191
     192        In the special case that the timeout of the handler has been set
     193        to zero, the handler will poll all events synchronously and return.
     194        If will keep listening until it receives a stop event.
    180195
    181196        Note: If you run this loop in another thread, don't use the same
     
    183198        """
    184199        self.listen()
    185         _ilist = [self.db.fileno()]
    186 
     200        poll = self.timeout == 0
     201        if not poll:
     202            rlist = [self.db.fileno()]
    187203        while self.listening:
    188             ilist, _olist, _elist = select.select(_ilist, [], [], self.timeout)
    189             if ilist:
     204            if poll or select.select(rlist, [], [], self.timeout)[0]:
    190205                while self.listening:
    191206                    notice = self.db.getnotify()
     
    200215                    if event == self.stop_event:
    201216                        self.unlisten()
    202                     self.arg_dict['pid'] = pid
    203                     self.arg_dict['event'] = event
    204                     self.arg_dict['extra'] = extra
     217                    self.arg_dict.update(pid=pid, event=event, extra=extra)
    205218                    self.callback(self.arg_dict)
     219                if poll:
     220                    break
    206221            else:   # we timed out
    207222                self.unlisten()
     
    11441159        return self.query(q)
    11451160
    1146     def notification_handler(self, event, callback, arg_dict={}, timeout=None):
     1161    def notification_handler(self,
     1162            event, callback, arg_dict=None, timeout=None, stop_event=None):
    11471163        """Get notification handler that will run the given callback."""
    1148         return NotificationHandler(self.db, event, callback, arg_dict, timeout)
     1164        return NotificationHandler(self,
     1165            event, callback, arg_dict, timeout, stop_event)
    11491166
    11501167
  • trunk/tests/test_classic.py

    r730 r762  
    232232                sleep(0.01)
    233233            self.assertTrue(target.listening)
    234             self.assertTrue(thread.isAlive())
     234            self.assertTrue(thread.is_alive())
    235235            # Open another connection for sending notifications.
    236236            db2 = opendb()
     
    260260            self.assertFalse(self.notify_timeout)
    261261            arg_dict['called'] = False
    262             self.assertTrue(thread.isAlive())
     262            self.assertTrue(thread.is_alive())
    263263            # Generate stop notification.
    264264            if call_notify:
     
    279279            self.assertFalse(self.notify_timeout)
    280280            thread.join(5)
    281             self.assertFalse(thread.isAlive())
     281            self.assertFalse(thread.is_alive())
    282282            self.assertFalse(target.listening)
    283283            target.close()
     
    315315            self.assertFalse(arg_dict.get('called'))
    316316            self.assertTrue(self.notify_timeout)
    317             self.assertFalse(thread.isAlive())
     317            self.assertFalse(thread.is_alive())
    318318            self.assertFalse(target.listening)
    319319            target.close()
  • trunk/tests/test_classic_dbwrapper.py

    r760 r762  
    239239        self.db.query("select 1+1")
    240240        self.db.close()
     241        self.assertRaises(pg.InternalError, self.db.reset)
    241242
    242243    def testMethodReopen(self):
    243244        con = self.db.db
     245        self.db.reopen()
     246        self.assertIsNot(self.db.db, con)
     247        con = self.db.db
     248        self.db.query("select 1+1")
     249        self.db.close()
    244250        self.db.reopen()
    245251        self.assertIsNot(self.db.db, con)
     
    19201926        self.assertEqual(r, s)
    19211927
     1928    def testNotificationHandler(self):
     1929        # the notification handler itself is tested separately
     1930        f = self.db.notification_handler
     1931        callback = lambda arg_dict: None
     1932        handler = f('test', callback)
     1933        self.assertIsInstance(handler, pg.NotificationHandler)
     1934        self.assertIs(handler.db, self.db)
     1935        self.assertEqual(handler.event, 'test')
     1936        self.assertEqual(handler.stop_event, 'stop_test')
     1937        self.assertIs(handler.callback, callback)
     1938        self.assertIsInstance(handler.arg_dict, dict)
     1939        self.assertEqual(handler.arg_dict, {})
     1940        self.assertIsNone(handler.timeout)
     1941        self.assertFalse(handler.listening)
     1942        handler.close()
     1943        self.assertIsNone(handler.db)
     1944        self.db.reopen()
     1945        self.assertIsNone(handler.db)
     1946        handler = f('test2', callback, timeout=2)
     1947        self.assertIsInstance(handler, pg.NotificationHandler)
     1948        self.assertIs(handler.db, self.db)
     1949        self.assertEqual(handler.event, 'test2')
     1950        self.assertEqual(handler.stop_event, 'stop_test2')
     1951        self.assertIs(handler.callback, callback)
     1952        self.assertIsInstance(handler.arg_dict, dict)
     1953        self.assertEqual(handler.arg_dict, {})
     1954        self.assertEqual(handler.timeout, 2)
     1955        self.assertFalse(handler.listening)
     1956        handler.close()
     1957        self.assertIsNone(handler.db)
     1958        self.db.reopen()
     1959        self.assertIsNone(handler.db)
     1960        arg_dict = {'testing': 3}
     1961        handler = f('test3', callback, arg_dict=arg_dict)
     1962        self.assertIsInstance(handler, pg.NotificationHandler)
     1963        self.assertIs(handler.db, self.db)
     1964        self.assertEqual(handler.event, 'test3')
     1965        self.assertEqual(handler.stop_event, 'stop_test3')
     1966        self.assertIs(handler.callback, callback)
     1967        self.assertIs(handler.arg_dict, arg_dict)
     1968        self.assertEqual(arg_dict['testing'], 3)
     1969        self.assertIsNone(handler.timeout)
     1970        self.assertFalse(handler.listening)
     1971        handler.close()
     1972        self.assertIsNone(handler.db)
     1973        self.db.reopen()
     1974        self.assertIsNone(handler.db)
     1975        handler = f('test4', callback, stop_event='stop4')
     1976        self.assertIsInstance(handler, pg.NotificationHandler)
     1977        self.assertIs(handler.db, self.db)
     1978        self.assertEqual(handler.event, 'test4')
     1979        self.assertEqual(handler.stop_event, 'stop4')
     1980        self.assertIs(handler.callback, callback)
     1981        self.assertIsInstance(handler.arg_dict, dict)
     1982        self.assertEqual(handler.arg_dict, {})
     1983        self.assertIsNone(handler.timeout)
     1984        self.assertFalse(handler.listening)
     1985        handler.close()
     1986        self.assertIsNone(handler.db)
     1987        self.db.reopen()
     1988        self.assertIsNone(handler.db)
     1989        arg_dict = {'testing': 5}
     1990        handler = f('test5', callback, arg_dict, 1.5, 'stop5')
     1991        self.assertIsInstance(handler, pg.NotificationHandler)
     1992        self.assertIs(handler.db, self.db)
     1993        self.assertEqual(handler.event, 'test5')
     1994        self.assertEqual(handler.stop_event, 'stop5')
     1995        self.assertIs(handler.callback, callback)
     1996        self.assertIs(handler.arg_dict, arg_dict)
     1997        self.assertEqual(arg_dict['testing'], 5)
     1998        self.assertEqual(handler.timeout, 1.5)
     1999        self.assertFalse(handler.listening)
     2000        handler.close()
     2001        self.assertIsNone(handler.db)
     2002        self.db.reopen()
     2003        self.assertIsNone(handler.db)
     2004
    19222005
    19232006class TestDBClassNonStdOpts(TestDBClass):
     
    19582041        db = DB()
    19592042        query = db.query
    1960         query("set client_min_messages=warning")
    19612043        for num_schema in range(5):
    19622044            if num_schema:
     
    19832065        db = DB()
    19842066        query = db.query
    1985         query("set client_min_messages=warning")
    19862067        for num_schema in range(5):
    19872068            if num_schema:
     
    19962077    def setUp(self):
    19972078        self.db = DB()
    1998         self.db.query("set client_min_messages=warning")
    19992079
    20002080    def tearDown(self):
Note: See TracChangeset for help on using the changeset viewer.