Skip to content

Commit 4b49657

Browse files
author
James William Pye
committed
Update db.notify()'s API.
Implement notify() to accept variable arguments and keywords for NOTIFY'ing multiple channels with or without payloads.
1 parent bce7101 commit 4b49657

9 files changed

Lines changed: 136 additions & 13 deletions

File tree

postgresql/api.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -963,7 +963,16 @@ def notify(self, *channels, **channel_and_payload) -> int:
963963
NOTIFY the channels with the given payload.
964964
965965
Equivalent to issuing "NOTIFY <channel>" or "NOTIFY <channel>, <payload>"
966-
if a payload is given.
966+
for each item in `channels` and `channel_and_payload`. All NOTIFYs issued
967+
*must* occur in the same transaction.
968+
969+
The items in `channels` can either be a string or a tuple. If a string,
970+
no payload is given, but if an item is a `builtins.tuple`, the second item
971+
will be given as the payload. `channels` offers a means to issue NOTIFYs
972+
in guaranteed order.
973+
974+
The items in `channel_and_payload` are all payloaded NOTIFYs where the
975+
keys are the channels and the values are the payloads. Order is undefined.
967976
"""
968977

969978
@abstractmethod

postgresql/documentation/driver.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,33 @@ The methods and properties on the connection object are ready for use:
380380
Return an iterator producing the channel names that are currently being
381381
listened to.
382382

383+
``Connection.notify(*channels, **channel_and_payload)``
384+
NOTIFY the channels with the given payload. Sends a batch of ``NOTIFY``
385+
statements to the server.
386+
387+
Equivalent to issuing "NOTIFY <channel>" or "NOTIFY <channel>, <payload>"
388+
for each item in `channels` and `channel_and_payload`. All NOTIFYs issued
389+
will occur in the same transaction, regardless of auto-commit.
390+
391+
The items in `channels` can either be a string or a tuple. If a string,
392+
no payload is given, but if an item is a `builtins.tuple`, the second item
393+
in the pair will be given as the payload, and the first as the channel.
394+
`channels` offers a means to issue NOTIFYs in guaranteed order::
395+
396+
>>> db.notify('channel1', ('different_channel', 'payload'))
397+
398+
In the above, ``NOTIFY "channel1";`` will be issued first, followed by
399+
``NOTIFY "different_channel", 'payload';``.
400+
401+
The items in `channel_and_payload` are all payloaded NOTIFYs where the
402+
keys are the channels and the values are the payloads. Order is undefined::
403+
404+
>>> db.notify(channel_name = 'payload_data')
405+
406+
`channels` and `channels_and_payload` can be used together. In such cases all
407+
NOTIFY statements generated from `channels_and_payload` will follow those in
408+
`channels`.
409+
383410
``Connection.iternotifies(timeout = None)``
384411
Return an iterator to the NOTIFYs received on the connection. The iterator
385412
will yield notification triples consisting of ``(channel, payload, pid)``.

postgresql/documentation/html/_sources/driver.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,33 @@ The methods and properties on the connection object are ready for use:
380380
Return an iterator producing the channel names that are currently being
381381
listened to.
382382

383+
``Connection.notify(*channels, **channel_and_payload)``
384+
NOTIFY the channels with the given payload. Sends a batch of ``NOTIFY``
385+
statements to the server.
386+
387+
Equivalent to issuing "NOTIFY <channel>" or "NOTIFY <channel>, <payload>"
388+
for each item in `channels` and `channel_and_payload`. All NOTIFYs issued
389+
will occur in the same transaction, regardless of auto-commit.
390+
391+
The items in `channels` can either be a string or a tuple. If a string,
392+
no payload is given, but if an item is a `builtins.tuple`, the second item
393+
in the pair will be given as the payload, and the first as the channel.
394+
`channels` offers a means to issue NOTIFYs in guaranteed order::
395+
396+
>>> db.notify('channel1', ('different_channel', 'payload'))
397+
398+
In the above, ``NOTIFY "channel1";`` will be issued first, followed by
399+
``NOTIFY "different_channel", 'payload';``.
400+
401+
The items in `channel_and_payload` are all payloaded NOTIFYs where the
402+
keys are the channels and the values are the payloads. Order is undefined::
403+
404+
>>> db.notify(channel_name = 'payload_data')
405+
406+
`channels` and `channels_and_payload` can be used together. In such cases all
407+
NOTIFY statements generated from `channels_and_payload` will follow those in
408+
`channels`.
409+
383410
``Connection.iternotifies(timeout = None)``
384411
Return an iterator to the NOTIFYs received on the connection. The iterator
385412
will yield notification triples consisting of ``(channel, payload, pid)``.
8.38 KB
Binary file not shown.
0 Bytes
Binary file not shown.

postgresql/documentation/html/driver.html

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,30 @@ <h3>Database Interface Points<a class="headerlink" href="#database-interface-poi
379379
<dt><tt class="docutils literal"><span class="pre">Connection.listening_channels()</span></tt></dt>
380380
<dd>Return an iterator producing the channel names that are currently being
381381
listened to.</dd>
382+
<dt><tt class="docutils literal"><span class="pre">Connection.notify(*channels,</span> <span class="pre">**channel_and_payload)</span></tt></dt>
383+
<dd><p class="first">NOTIFY the channels with the given payload. Sends a batch of <tt class="docutils literal"><span class="pre">NOTIFY</span></tt>
384+
statements to the server.</p>
385+
<p>Equivalent to issuing &#8220;NOTIFY &lt;channel&gt;&#8221; or &#8220;NOTIFY &lt;channel&gt;, &lt;payload&gt;&#8221;
386+
for each item in <cite>channels</cite> and <cite>channel_and_payload</cite>. All NOTIFYs issued
387+
will occur in the same transaction, regardless of auto-commit.</p>
388+
<p>The items in <cite>channels</cite> can either be a string or a tuple. If a string,
389+
no payload is given, but if an item is a <cite>builtins.tuple</cite>, the second item
390+
in the pair will be given as the payload, and the first as the channel.
391+
<cite>channels</cite> offers a means to issue NOTIFYs in guaranteed order:</p>
392+
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">db</span><span class="o">.</span><span class="n">notify</span><span class="p">(</span><span class="s">&#39;channel1&#39;</span><span class="p">,</span> <span class="p">(</span><span class="s">&#39;different_channel&#39;</span><span class="p">,</span> <span class="s">&#39;payload&#39;</span><span class="p">))</span>
393+
</pre></div>
394+
</div>
395+
<p>In the above, <tt class="docutils literal"><span class="pre">NOTIFY</span> <span class="pre">&quot;channel1&quot;;</span></tt> will be issued first, followed by
396+
<tt class="docutils literal"><span class="pre">NOTIFY</span> <span class="pre">&quot;different_channel&quot;,</span> <span class="pre">'payload';</span></tt>.</p>
397+
<p>The items in <cite>channel_and_payload</cite> are all payloaded NOTIFYs where the
398+
keys are the channels and the values are the payloads. Order is undefined:</p>
399+
<div class="highlight-python"><div class="highlight"><pre><span class="gp">&gt;&gt;&gt; </span><span class="n">db</span><span class="o">.</span><span class="n">notify</span><span class="p">(</span><span class="n">channel_name</span> <span class="o">=</span> <span class="s">&#39;payload_data&#39;</span><span class="p">)</span>
400+
</pre></div>
401+
</div>
402+
<p class="last"><cite>channels</cite> and <cite>channels_and_payload</cite> can be used together. In such cases all
403+
NOTIFY statements generated from <cite>channels_and_payload</cite> will follow those in
404+
<cite>channels</cite>.</p>
405+
</dd>
382406
<dt><tt class="docutils literal"><span class="pre">Connection.iternotifies(timeout</span> <span class="pre">=</span> <span class="pre">None)</span></tt></dt>
383407
<dd>Return an iterator to the NOTIFYs received on the connection. The iterator
384408
will yield notification triples consisting of <tt class="docutils literal"><span class="pre">(channel,</span> <span class="pre">payload,</span> <span class="pre">pid)</span></tt>.

postgresql/documentation/html/searchindex.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

postgresql/driver/pq3.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2576,19 +2576,27 @@ def clone(self, *args, **kw):
25762576
c.connect()
25772577
return c
25782578

2579-
def notify(self, channel, payload = None,
2580-
notify_with_payload = "NOTIFY \"{0}\", '{1}'".format,
2581-
notify_without_payload = "NOTIFY \"{0}\"".format,
2582-
):
2583-
if payload is not None:
2584-
return self.execute(notify_with_payload(
2585-
channel.replace('"', '""'),
2586-
payload.replace("'", "''"),
2579+
def notify(self, *channels, **channel_and_payload):
2580+
notifies = ""
2581+
if channels:
2582+
notifies += ';'.join((
2583+
'NOTIFY "' + x.replace('"', '""') + '"' # str() case
2584+
if x.__class__ is not tuple else (
2585+
# tuple() case
2586+
'NOTIFY "' + x[0].replace('"', '""') + """",'""" + \
2587+
x[1].replace("'", "''") + "'"
2588+
)
2589+
for x in channels
25872590
))
2588-
else:
2589-
return self.execute(notify_without_payload(
2590-
channel.replace('"', '""'),
2591+
notifies += ';'
2592+
if channel_and_payload:
2593+
notifies += ';'.join((
2594+
'NOTIFY "' + channel.replace('"', '""') + """",'""" + \
2595+
payload.replace("'", "''") + "'"
2596+
for channel, payload in channel_and_payload.items()
25912597
))
2598+
notifies += ';'
2599+
return self.execute(notifies)
25922600

25932601
def listening_channels(self):
25942602
if self.version_info[:2] > (8,4):

postgresql/test/test_driver.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,6 +1627,34 @@ def testNotify(self):
16271627
self.failUnlessRaises(Exception, db.listen, 'doesntexist', 'x'*64)
16281628
self.failUnless('doesntexist' not in db.listening_channels())
16291629

1630+
@pg_tmp
1631+
def testPayloads(self):
1632+
if db.version_info[:2] >= (9,0):
1633+
db.listen('foo')
1634+
db.notify(foo = 'bar')
1635+
self.failUnlessEqual(('foo', 'bar', db.backend_id), list(db.iternotifies(0))[0])
1636+
db.notify(('foo', 'barred'))
1637+
self.failUnlessEqual(('foo', 'barred', db.backend_id), list(db.iternotifies(0))[0])
1638+
# mixed
1639+
db.notify(('foo', 'barred'), 'foo', ('foo', 'bleh'), foo = 'kw')
1640+
self.failUnlessEqual([
1641+
('foo', 'barred', db.backend_id),
1642+
('foo', '', db.backend_id),
1643+
('foo', 'bleh', db.backend_id),
1644+
# Keywords are appened.
1645+
('foo', 'kw', db.backend_id),
1646+
], list(db.iternotifies(0))
1647+
)
1648+
# multiple keywords
1649+
expect = [
1650+
('foo', 'meh', db.backend_id),
1651+
('bar', 'foo', db.backend_id),
1652+
]
1653+
rexpect = list(reversed(expect))
1654+
db.listen('bar')
1655+
db.notify(foo = 'meh', bar = 'foo')
1656+
self.failUnless(list(db.iternotifies(0)) in [expect, rexpect])
1657+
16301658
@pg_tmp
16311659
def testMessageHook(self):
16321660
create = db.prepare('CREATE TEMP TABLE msghook (i INT PRIMARY KEY)')

0 commit comments

Comments
 (0)