Author: cito
Date: Sat Aug 20 07:12:14 2011
New Revision: 435
Log:
Support custom notice receiver callback functions
(similar to the patch provided by Michael Filonenko in ticket #37).
Modified:
trunk/docs/pg.txt
trunk/module/pgmodule.c
trunk/module/test_inserttable.py
trunk/module/test_pg.py
Modified: trunk/docs/pg.txt
==============================================================================
--- trunk/docs/pg.txt Fri Aug 19 15:18:32 2011 (r434)
+++ trunk/docs/pg.txt Sat Aug 20 07:12:14 2011 (r435)
@@ -435,7 +435,7 @@
:TypeError: bad argument type, or too many arguments
:ValueError: empty SQL query or lost connection
:pg.ProgrammingError: error in query
- :pg.InternalError': error during query processing
+ :pg.InternalError: error during query processing
Description:
This method simply sends a SQL query to the database. If the query is an
@@ -575,6 +575,56 @@
This method doesn't typecheck the fields according to the table definition;
it just look whether or not it knows how to handle such types.
+set_notice_receiver - set a custom notice receiver
+--------------------------------------------------
+Syntax::
+
+ set_notice_receiver(proc)
+
+Parameters:
+ :proc: the custom notice receiver callback function
+
+Return type:
+ None
+
+Exceptions raised:
+ :TypeError: the specified notice receiver is not callable
+
+Description:
+ This method allows setting a custom notice receiver callback function.
+ When a notice or warning message is received from the server,
+ or generated internally by libpq, and the message level is below
+ the one set with `client_min_messages`, the specified notice receiver
+ function will be called. This function must take one parameter,
+ the `pgnotice` object, which provides the following read-only attributes:
+
+ :pgcnx: the connection
+ :message: the full message with a trailing newline
+ :severity: the level of the message, e.g. 'NOTICE' or 'WARNING'
+ :primary: the primary human-readable error message
+ :detail: an optional secondary error message
+ :hint: an optional suggestion what to do about the problem
+
+get_notice_receiver - get the current notice receiver
+-----------------------------------------------------
+Syntax::
+
+ get_notice_receiver()
+
+Parameters:
+ None
+
+Return type:
+ :callable, None: the current notice receiver callable
+
+Exceptions raised:
+ :TypeError: too many (any) arguments
+
+Description:
+ This method gets the custom notice receiver callback function that has
+ been set with `set_notice_receiver()`, or `None` if no custom notice
+ receiver has ever been set on the connection.
+
putline - writes a line to the server socket [DA]
-------------------------------------------------
Syntax::
@@ -911,7 +961,7 @@
bigger. If this happens and it is a table with OID but no primary key
you can overcome this problem by simply adding an index onto the OID of
any table that you think may get large over time. You may also consider
- using the inserttable() method described in section 3.
+ using the inserttable() method described in section 3.
Note: With PostgreSQL versions before 8.2 the table being inserted to
must have a primary key or an OID to use this method properly. If not
Modified: trunk/module/pgmodule.c
==============================================================================
--- trunk/module/pgmodule.c Fri Aug 19 15:18:32 2011 (r434)
+++ trunk/module/pgmodule.c Sat Aug 20 07:12:14 2011 (r435)
@@ -126,9 +126,10 @@
typedef struct
{
PyObject_HEAD
- int valid; /* validity flag */
- PGconn *cnx; /* PostGres connection handle */
- PGresult *last_result; /* last result content */
+ int valid; /* validity
flag */
+ PGconn *cnx; /* PostGres connection
handle */
+ PGresult *last_result; /* last result content */
+ PyObject *notice_receiver; /* current notice receiver */
} pgobject;
staticforward PyTypeObject PgType;
@@ -146,9 +147,24 @@
pgobj->valid = 1;
pgobj->last_result = NULL;
pgobj->cnx = NULL;
+ pgobj->notice_receiver = NULL;
+
return (PyObject *) pgobj;
}
+/* pg notice result object */
+
+typedef struct
+{
+ PyObject_HEAD
+ pgobject *pgcnx; /* parent connection object */
+ PGresult const *res; /* an error or warning */
+} pgnoticeobject;
+
+staticforward PyTypeObject PgNoticeType;
+
+#define is_pgnoticeobject(v) ((v)->ob_type == &PgNoticeType)
+
/* pg query object */
typedef struct
@@ -1510,6 +1526,7 @@
return Py_None;
}
+
/* large object methods */
static struct PyMethodDef pglarge_methods[] = {
{"open", (PyCFunction) pglarge_open, METH_VARARGS, pglarge_open__doc__},
@@ -1709,6 +1726,32 @@
return (PyObject *) npgobj;
}
+/* internal wrapper for the notice receiver callback */
+void notice_receiver(void *arg, const PGresult *res)
+{
+ PyGILState_STATE gstate = PyGILState_Ensure();
+ pgobject *self = (pgobject*) arg;
+ PyObject *proc = self->notice_receiver;
+ if (proc && PyCallable_Check(proc)) {
+ pgnoticeobject *notice = PyObject_NEW(pgnoticeobject,
&PgNoticeType);
+ if (notice)
+ {
+ notice->pgcnx = arg;
+ notice->res = res;
+ }
+ else
+ {
+ Py_INCREF(Py_None);
+ notice = (pgnoticeobject *)Py_None;
+ }
+ PyObject *args = Py_BuildValue("(O)", notice);
+ PyObject *ret = PyObject_CallObject(proc, args);
+ Py_XDECREF(ret);
+ Py_DECREF(args);
+ }
+ PyGILState_Release(gstate);
+}
+
/* pgobject methods */
/* destructor */
@@ -1721,6 +1764,9 @@
PQfinish(self->cnx);
Py_END_ALLOW_THREADS
}
+ if (self->notice_receiver) {
+ Py_DECREF(self->notice_receiver);
+ }
PyObject_Del(self);
}
@@ -1847,6 +1893,55 @@
#endif
}
+/* set notice receiver callback function */
+static char pg_set_notice_receiver__doc__[] =
+"set_notice_receiver() -- set the current notice receiver.";
+
+static PyObject *
+pg_set_notice_receiver(pgobject * self, PyObject * args)
+{
+ PyObject *ret = NULL;
+ PyObject *proc;
+
+ if (PyArg_ParseTuple(args, "O", &proc))
+ {
+ if (PyCallable_Check(proc))
+ {
+ Py_XINCREF(proc);
+ self->notice_receiver = proc;
+ PQsetNoticeReceiver(self->cnx, notice_receiver, self);
+ Py_INCREF(Py_None); ret = Py_None;
+ }
+ else
+ PyErr_SetString(PyExc_TypeError, "notice receiver must
be callable");
+ }
+ return ret;
+}
+
+/* get notice receiver callback function */
+static char pg_get_notice_receiver__doc__[] =
+"get_notice_receiver() -- get the current notice receiver.";
+
+static PyObject *
+pg_get_notice_receiver(pgobject * self, PyObject * args)
+{
+ PyObject *ret = NULL;
+
+ if (PyArg_ParseTuple(args, ""))
+ {
+ ret = self->notice_receiver;
+ if (!ret)
+ ret = Py_None;
+ Py_INCREF(ret);
+ }
+ else
+ {
+ PyErr_SetString(PyExc_TypeError,
+ "method get_notice_receiver() takes no parameters.");
+ }
+ return ret;
+}
+
/* get number of rows */
static char pgquery_ntuples__doc__[] =
"ntuples() -- returns number of tuples returned by query.";
@@ -2981,6 +3076,7 @@
}
#endif /* LARGE_OBJECTS */
+
/* connection object methods */
static struct PyMethodDef pgobj_methods[] = {
{"source", (PyCFunction) pg_source, METH_VARARGS, pg_source__doc__},
@@ -2989,6 +3085,10 @@
{"cancel", (PyCFunction) pg_cancel, METH_VARARGS, pg_cancel__doc__},
{"close", (PyCFunction) pg_close, METH_VARARGS, pg_close__doc__},
{"fileno", (PyCFunction) pg_fileno, METH_VARARGS, pg_fileno__doc__},
+ {"get_notice_receiver", (PyCFunction) pg_get_notice_receiver,
METH_VARARGS,
+ pg_get_notice_receiver__doc__},
+ {"set_notice_receiver", (PyCFunction) pg_set_notice_receiver,
METH_VARARGS,
+ pg_set_notice_receiver__doc__},
{"getnotify", (PyCFunction) pg_getnotify, METH_VARARGS,
pg_getnotify__doc__},
{"inserttable", (PyCFunction) pg_inserttable, METH_VARARGS,
@@ -3038,14 +3138,15 @@
return NULL;
}
- /* list postgreSQL connection fields */
+ /* list PostgreSQL connection fields */
/* postmaster host */
if (!strcmp(name, "host"))
{
char *r = PQhost(self->cnx);
-
- return r ? PyString_FromString(r) :
PyString_FromString("localhost");
+ if (!r)
+ r = "localhost";
+ return PyString_FromString(r);
}
/* postmaster port */
@@ -3134,6 +3235,108 @@
};
+/* get attribute */
+static PyObject *
+pgnotice_getattr(pgnoticeobject *self, char *name)
+{
+ PGresult const *res = self->res;
+
+ if (!res)
+ {
+ PyErr_SetString(PyExc_TypeError, "Cannot get current notice.");
+ return NULL;
+ }
+
+ /* pg connection object */
+ if (!strcmp(name, "pgcnx"))
+ {
+ if (self->pgcnx && check_cnx_obj(self->pgcnx))
+ {
+ Py_INCREF(self->pgcnx);
+ return self->pgcnx;
+ }
+ else
+ {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ }
+
+ /* full message */
+ if (!strcmp(name, "message"))
+ return PyString_FromString(PQresultErrorMessage(res));
+
+ /* other possible fields */
+ int fieldcode = 0;
+ if (!strcmp(name, "severity"))
+ fieldcode = PG_DIAG_SEVERITY;
+ else if (!strcmp(name, "primary"))
+ fieldcode = PG_DIAG_MESSAGE_PRIMARY;
+ else if (!strcmp(name, "detail"))
+ fieldcode = PG_DIAG_MESSAGE_DETAIL;
+ else if (!strcmp(name, "hint"))
+ fieldcode = PG_DIAG_MESSAGE_HINT;
+ if (fieldcode)
+ {
+ char *s = PQresultErrorField(res, fieldcode);
+ if (s)
+ return PyString_FromString(s);
+ else
+ {
+ Py_INCREF(Py_None); return Py_None;
+ }
+ }
+
+ /* attributes list */
+ if (!strcmp(name, "__members__"))
+ {
+ PyObject *list = PyList_New(6);
+ if (list)
+ {
+ PyList_SET_ITEM(list, 0, PyString_FromString("pgcnx"));
+ PyList_SET_ITEM(list, 1,
PyString_FromString("severity"));
+ PyList_SET_ITEM(list, 2,
PyString_FromString("message"));
+ PyList_SET_ITEM(list, 3,
PyString_FromString("primary"));
+ PyList_SET_ITEM(list, 4, PyString_FromString("detail"));
+ PyList_SET_ITEM(list, 5, PyString_FromString("hint"));
+ }
+ return list;
+ }
+
+ PyErr_Format(PyExc_AttributeError,
+ "'pgnoticeobject' has no attribute %s", name);
+ return NULL;
+}
+
+static PyObject *
+pgnotice_str(pgnoticeobject *self)
+{
+ return pgnotice_getattr(self, "message");
+}
+
+/* object type definition */
+staticforward PyTypeObject PgNoticeType = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+ "pgnoticeobject", /* tp_name */
+ sizeof(pgnoticeobject), /* tp_basicsize */
+ 0, /* tp_itemsize
*/
+ /* methods */
+ 0, /*
tp_dealloc */
+ 0, /*
tp_print */
+ (getattrfunc) pgnotice_getattr, /* tp_getattr */
+ 0, /*
tp_setattr */
+ 0, /*
tp_compare */
+ 0, /*
tp_repr */
+ 0, /*
tp_as_number */
+ 0, /*
tp_as_sequence */
+ 0, /*
tp_as_mapping */
+ 0, /*
tp_hash */
+ 0, /*
tp_call */
+ (reprfunc) pgnotice_str /* tp_str */
+};
+
+
/* query object methods */
static struct PyMethodDef pgquery_methods[] = {
{"getresult", (PyCFunction) pgquery_getresult, METH_VARARGS,
@@ -3675,7 +3878,8 @@
*v;
/* Initialize here because some WIN platforms get confused otherwise */
- PgType.ob_type = PgQueryType.ob_type = PgSourceType.ob_type =
&PyType_Type;
+ PgType.ob_type = PgNoticeType.ob_type =
+ PgQueryType.ob_type = PgSourceType.ob_type = &PyType_Type;
#ifdef LARGE_OBJECTS
PglargeType.ob_type = &PyType_Type;
#endif
Modified: trunk/module/test_inserttable.py
==============================================================================
--- trunk/module/test_inserttable.py Fri Aug 19 15:18:32 2011 (r434)
+++ trunk/module/test_inserttable.py Sat Aug 20 07:12:14 2011 (r435)
@@ -11,11 +11,17 @@
print "PygreSQL inserttable() test."
# Verify inserttable() works with German locale as well:
-import locale
+german = True
try:
- locale.setlocale(locale.LC_ALL, 'de_DE')
-except locale.Error:
- locale.setlocale(locale.LC_ALL, '')
+ import locale
+ locale.setlocale(locale.LC_ALL, ('de', 'latin1'))
+except Exception:
+ try:
+ locale.setlocale(locale.LC_ALL, 'german')
+ except Exception:
+ import warning
+ warning.warn('Cannot set German locale.')
+ german = False
db = DB('test')
Modified: trunk/module/test_pg.py
==============================================================================
--- trunk/module/test_pg.py Fri Aug 19 15:18:32 2011 (r434)
+++ trunk/module/test_pg.py Sat Aug 20 07:12:14 2011 (r435)
@@ -13,7 +13,7 @@
There are a few drawbacks:
* A local PostgreSQL database must be up and running, and
- the user who is running the tests must be a trusted superuser.
+ the database user who is running the tests must be a trusted superuser.
* The performance of the API is not tested.
* Connecting to a remote host is not tested.
* Passing user, password and options is not tested.
@@ -28,10 +28,10 @@
import pg
import unittest
-debug = 0
+debug = False
-# Try to load german locale for Umlaut tests
-german = 1
+# Try to load German locale for umlaut tests
+german = True
try:
import locale
locale.setlocale(locale.LC_ALL, ('de', 'latin1'))
@@ -39,7 +39,9 @@
try:
locale.setlocale(locale.LC_ALL, 'german')
except Exception:
- german = 0
+ import warning
+ warning.warn('Cannot set German locale.')
+ german = False
try:
frozenset
@@ -365,8 +367,9 @@
def testAllConnectMethods(self):
methods = '''cancel close endcopy
escape_bytea escape_identifier escape_literal escape_string
- fileno getline getlo getnotify inserttable locreate loimport
- parameter putline query reset source transaction'''.split()
+ fileno get_notice_receiver getline getlo getnotify
+ inserttable locreate loimport parameter putline query reset
+ set_notice_receiver source transaction'''.split()
connection_methods = [a for a in dir(self.connection)
if callable(eval("self.connection." + a))]
self.assertEqual(methods, connection_methods)
@@ -681,6 +684,64 @@
self.assertEqual(r, data)
+class TestNoticeReceiver(unittest.TestCase):
+ """"Test notice receiver support."""
+
+ # Test database needed: must be run as a DBTestSuite.
+
+ def setUp(self):
+ self.dbname = DBTestSuite.dbname
+
+ def testGetNoticeReceiver(self):
+ c = pg.connect(self.dbname)
+ try:
+ self.assert_(c.get_notice_receiver() is None)
+ finally:
+ c.close()
+
+ def testSetNoticeReceiver(self):
+ c = pg.connect(self.dbname)
+ try:
+ self.assertRaises(TypeError, c.set_notice_receiver, None)
+ self.assertRaises(TypeError, c.set_notice_receiver, 42)
+ self.assert_(c.set_notice_receiver(lambda notice: None) is None)
+ finally:
+ c.close()
+
+ def testSetandGetNoticeReceiver(self):
+ c = pg.connect(self.dbname)
+ try:
+ r = lambda notice: None
+ self.assert_(c.set_notice_receiver(r) is None)
+ self.assert_(c.get_notice_receiver() is r)
+ finally:
+ c.close()
+
+ def testNoticeReceiver(self):
+ c = pg.connect(self.dbname)
+ try:
+ c.query('''create function bilbo_notice() returns void AS $$
+ begin
+ raise warning 'Bilbo was here!';
+ end;
+ $$ language plpgsql''')
+ try:
+ received = {}
+ def notice_receiver(notice):
+ for attr in dir(notice):
+ received[attr] = getattr(notice, attr)
+ c.set_notice_receiver(notice_receiver)
+ c.query('''select bilbo_notice()''')
+ self.assertEqual(received, dict(
+ pgcnx=c, message='WARNING: Bilbo was here!\n',
+ severity='WARNING', primary='Bilbo was here!',
+ detail=None, hint=None))
+ finally:
+ c.query('''drop function bilbo_notice();''')
+ finally:
+ c.close()
+
+
class TestDBClassBasic(unittest.TestCase):
""""Test existence of the DB class wrapped pg connection methods."""
@@ -695,11 +756,12 @@
def testAllDBAttributes(self):
attributes = '''cancel clear close db dbname debug delete endcopy
error escape_bytea escape_identifier escape_literal escape_string
- fileno get get_attnames get_databases get_relations get_tables
- getline getlo getnotify has_table_privilege host insert inserttable
- locreate loimport options parameter pkey port protocol_version
- putline query reopen reset server_version source status transaction
- tty unescape_bytea update user'''.split()
+ fileno get get_attnames get_databases get_notice_receiver
+ get_relations get_tables getline getlo getnotify
+ has_table_privilege host insert inserttable locreate loimport
+ options parameter pkey port protocol_version putline query
+ reopen reset server_version set_notice_receiver source status
+ transaction tty unescape_bytea update user'''.split()
db_attributes = [a for a in dir(self.db)
if not a.startswith('_')]
self.assertEqual(attributes, db_attributes)
@@ -1491,6 +1553,7 @@
c.query("create database " + dbname
+ " template=template0")
for s in ('client_min_messages = warning',
+ 'lc_messages = C',
'default_with_oids = on',
'standard_conforming_strings = off',
'escape_string_warning = off'):
@@ -1548,6 +1611,7 @@
# All tests that need a test database:
TestSuite2 = DBTestSuite((
unittest.makeSuite(TestInserttable),
+ unittest.makeSuite(TestNoticeReceiver),
unittest.makeSuite(TestDBClass),
unittest.makeSuite(TestSchemas),
))
@@ -1555,7 +1619,7 @@
# All tests together in one test suite:
TestSuite = unittest.TestSuite((
TestSuite1,
- TestSuite2
+ TestSuite2,
))
unittest.TextTestRunner(verbosity=2).run(TestSuite)
_______________________________________________
PyGreSQL mailing list
[email protected]
http://mailman.vex.net/mailman/listinfo/pygresql