A convenient Python interface to Postgres's LISTEN and NOTIFY features.
Project description
# pgpubsub
pgpubsub provides convenient access to the [event notification
system](http://www.postgresql.org/docs/9.4/static/sql-notify.html) built into
the [PostgreSQL](http://www.postgresql.org/) database. This provides a
real-time Pub/Sub system similar to the one in
[Redis](http://redis.io/topics/pubsub).
## Usage
First you need to make a connection.
import pgpubsub
pubsub = pgpubsub.connect(user='postgres', database='test')
The arguments accepted by the pgpubsub.connect() function are identical to those
supported by the
[psycopg2.connect()](http://initd.org/psycopg/docs/module.html#psycopg2.connect)
function.
### Sending Events
To send an event, use the pubsub.notify() method:
pubsub.notify('test_channel', 'some message')
### Receiving Events
To receive events, you must first subscribe to a specific channel with the
pubsub.listen() method:
pubsub.listen('test')
You can call pubsub.listen() multiple times to receive events from multiple
channels.
pubsub.listen('chan1')
pubsub.listen('chan2')
Note that channel names must be a valid SQL
[identifiers](http://www.postgresql.org/docs/9.4/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS).
To quote from the Postgres docs:
> SQL identifiers and key words must begin with a letter (a-z, but also letters
> with diacritical marks and non-Latin letters) or an underscore (_). Subsequent
> characters in an identifier or key word can be letters, underscores, digits
> (0-9), or dollar signs ($).
Once you have subscribed to one or more channels, you can choose to receive
events either by iterating over pubsub.events(), or by repeatedly calling the
pubsub.get_event() method.
#### WARNING
Because channels are SQL identifiers rather than strings, they can't be
quoted/escaped by Psycopg2 like strings can. It is not safe to build a channel
name from untrusted user input.
DO NOT DO THIS:
channel = 'events_' + username
pubsub.listen(channel)
If you do, then your whole database could be destroyed by someone with the
username "; DROP TABLE users;". [Mandatory XKCD](https://xkcd.com/327/).
#### pubsub.events()
This is a generator over the stream of events coming on the pubsub. It lets you
loop over the events just as you would a list.
for e in pubsub.events():
print e.payload
Behind the scenes, the pubsub is blocking on the standard library's
[select.select](https://docs.python.org/2/library/select.html#select.select)
function. You can provide two additional arguments to pubsub.events() to
control how timeouts are handled when waiting on select.select:
- select_timeout: The number of seconds to wait on select.select before giving
up and trying again. Defaults to 5.
- yield_timeouts: This defaults to False. If set to True, then pubsub.events()
will yield a None each time you go for select_timeout seconds before receiving
an event. This is useful for things like WebSockets where you may want to
send a keepalive message even if no new data has been received.
Example:
for e in pubsub.events(yield_timeouts=True):
if e is None:
send_websocket_ping()
else:
send_websocket_message(e.payload)
#### pubsub.get_event()
This method always returns immediately. If an event has been received, it will
return that event. If no event has been received, it will return None.
If multiple events have been received and are waiting in the queue, then
repeated get_event() calls will keep returning the next event until there aren't
any left and it returns None. Example:
>>> pubsub.listen('test')
>>> pubsub.get_event() # Nothing delivered yet, so returns None
>>> pubsub.notify('test', 'message 1')
>>> pubsub.notify('test', 'message 2')
>>> pubsub.get_event()
Notify(9425, 'test', 'message 1')
>>> pubsub.get_event()
Notify(9425, 'test', 'message 2')
>>> pubsub.get_event() # No more messages, so returns None
The pubsub.get_event() method is intended for integration into event loops where
blocking on pubsub.events() would cause problems.
### Unsubscribing
If you want to stop receiving events on one of the channels you're currently
subscribed to, you can call pubsub.unlisten():
pubsub.unlisten('channel2')
### Event objects
The event objects returned by pubsub.events() and pubsub.get_event() are
instances of psycopg2's
[Notify](http://initd.org/psycopg/docs/extensions.html?highlight=notify#psycopg2.extensions.Notify)
class. They have three possibly-interesting attributes:
- payload: A string containing the actual message.
- channel: The name of the channel to which the event was sent.
- pid: The pid of the process on the Postgres server that's handling the
sender's connection. This can be useful to prevent an endless loop in a
program that both sends and receives events.
Example:
my_pid = pubsub.conn.get_backend_pid()
pubsub.listen('echo')
for e in pubsub.events():
sender_pid = e.pid
if sender_pid != my_pid:
pubsub.notify('echo', e.payload)
# Q & A
**Is it safe to pass pubsub objects between threads?**
No.
**Why use the verbs 'notify' and 'listen' instead of 'publish' and
'subscribe'?**
The methods in pgpubsub are designed to look as much as possible like the actual
SQL commands in Postgres, which are
[NOTIFY](http://www.postgresql.org/docs/9.4/static/sql-notify.html) and
[LISTEN](http://www.postgresql.org/docs/9.4/static/sql-listen.html). The
Postgres docs also refer to 'notification events' rather than 'messages', so
pgpubsub uses the same term.
**Why is there no callback-style interface?**
Someday there might be, if there's demand for it and a well-reasoned spec.
pgpubsub provides convenient access to the [event notification
system](http://www.postgresql.org/docs/9.4/static/sql-notify.html) built into
the [PostgreSQL](http://www.postgresql.org/) database. This provides a
real-time Pub/Sub system similar to the one in
[Redis](http://redis.io/topics/pubsub).
## Usage
First you need to make a connection.
import pgpubsub
pubsub = pgpubsub.connect(user='postgres', database='test')
The arguments accepted by the pgpubsub.connect() function are identical to those
supported by the
[psycopg2.connect()](http://initd.org/psycopg/docs/module.html#psycopg2.connect)
function.
### Sending Events
To send an event, use the pubsub.notify() method:
pubsub.notify('test_channel', 'some message')
### Receiving Events
To receive events, you must first subscribe to a specific channel with the
pubsub.listen() method:
pubsub.listen('test')
You can call pubsub.listen() multiple times to receive events from multiple
channels.
pubsub.listen('chan1')
pubsub.listen('chan2')
Note that channel names must be a valid SQL
[identifiers](http://www.postgresql.org/docs/9.4/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS).
To quote from the Postgres docs:
> SQL identifiers and key words must begin with a letter (a-z, but also letters
> with diacritical marks and non-Latin letters) or an underscore (_). Subsequent
> characters in an identifier or key word can be letters, underscores, digits
> (0-9), or dollar signs ($).
Once you have subscribed to one or more channels, you can choose to receive
events either by iterating over pubsub.events(), or by repeatedly calling the
pubsub.get_event() method.
#### WARNING
Because channels are SQL identifiers rather than strings, they can't be
quoted/escaped by Psycopg2 like strings can. It is not safe to build a channel
name from untrusted user input.
DO NOT DO THIS:
channel = 'events_' + username
pubsub.listen(channel)
If you do, then your whole database could be destroyed by someone with the
username "; DROP TABLE users;". [Mandatory XKCD](https://xkcd.com/327/).
#### pubsub.events()
This is a generator over the stream of events coming on the pubsub. It lets you
loop over the events just as you would a list.
for e in pubsub.events():
print e.payload
Behind the scenes, the pubsub is blocking on the standard library's
[select.select](https://docs.python.org/2/library/select.html#select.select)
function. You can provide two additional arguments to pubsub.events() to
control how timeouts are handled when waiting on select.select:
- select_timeout: The number of seconds to wait on select.select before giving
up and trying again. Defaults to 5.
- yield_timeouts: This defaults to False. If set to True, then pubsub.events()
will yield a None each time you go for select_timeout seconds before receiving
an event. This is useful for things like WebSockets where you may want to
send a keepalive message even if no new data has been received.
Example:
for e in pubsub.events(yield_timeouts=True):
if e is None:
send_websocket_ping()
else:
send_websocket_message(e.payload)
#### pubsub.get_event()
This method always returns immediately. If an event has been received, it will
return that event. If no event has been received, it will return None.
If multiple events have been received and are waiting in the queue, then
repeated get_event() calls will keep returning the next event until there aren't
any left and it returns None. Example:
>>> pubsub.listen('test')
>>> pubsub.get_event() # Nothing delivered yet, so returns None
>>> pubsub.notify('test', 'message 1')
>>> pubsub.notify('test', 'message 2')
>>> pubsub.get_event()
Notify(9425, 'test', 'message 1')
>>> pubsub.get_event()
Notify(9425, 'test', 'message 2')
>>> pubsub.get_event() # No more messages, so returns None
The pubsub.get_event() method is intended for integration into event loops where
blocking on pubsub.events() would cause problems.
### Unsubscribing
If you want to stop receiving events on one of the channels you're currently
subscribed to, you can call pubsub.unlisten():
pubsub.unlisten('channel2')
### Event objects
The event objects returned by pubsub.events() and pubsub.get_event() are
instances of psycopg2's
[Notify](http://initd.org/psycopg/docs/extensions.html?highlight=notify#psycopg2.extensions.Notify)
class. They have three possibly-interesting attributes:
- payload: A string containing the actual message.
- channel: The name of the channel to which the event was sent.
- pid: The pid of the process on the Postgres server that's handling the
sender's connection. This can be useful to prevent an endless loop in a
program that both sends and receives events.
Example:
my_pid = pubsub.conn.get_backend_pid()
pubsub.listen('echo')
for e in pubsub.events():
sender_pid = e.pid
if sender_pid != my_pid:
pubsub.notify('echo', e.payload)
# Q & A
**Is it safe to pass pubsub objects between threads?**
No.
**Why use the verbs 'notify' and 'listen' instead of 'publish' and
'subscribe'?**
The methods in pgpubsub are designed to look as much as possible like the actual
SQL commands in Postgres, which are
[NOTIFY](http://www.postgresql.org/docs/9.4/static/sql-notify.html) and
[LISTEN](http://www.postgresql.org/docs/9.4/static/sql-listen.html). The
Postgres docs also refer to 'notification events' rather than 'messages', so
pgpubsub uses the same term.
**Why is there no callback-style interface?**
Someday there might be, if there's demand for it and a well-reasoned spec.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
pgpubsub-0.0.2.tar.gz
(3.8 kB
view details)
File details
Details for the file pgpubsub-0.0.2.tar.gz
.
File metadata
- Download URL: pgpubsub-0.0.2.tar.gz
- Upload date:
- Size: 3.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 738b9c0ae28acd7fc0016117aa9828ab2b56e66758dde17cb3dbbd0f59c8adcc |
|
MD5 | 6b6d67cc9141cf8ddae08991b5976c4f |
|
BLAKE2b-256 | 86ac3f9280c863c9a67c4644c4260e396efb67c0c2ae3938068fb1b4c0df99d0 |