From 5360a7e81bca68e26d703a5baacea870ca2014ba Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 20 Nov 2015 10:44:08 +0000 Subject: [PATCH 001/144] unittest2 is needed for Python 2.6 as well --- module/TEST_PyGreSQL_classic_connection.py | 2 +- module/TEST_PyGreSQL_classic_dbwrapper.py | 2 +- module/TEST_PyGreSQL_classic_functions.py | 2 +- module/TEST_PyGreSQL_classic_largeobj.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/module/TEST_PyGreSQL_classic_connection.py b/module/TEST_PyGreSQL_classic_connection.py index 0e0d8566..162cfc76 100755 --- a/module/TEST_PyGreSQL_classic_connection.py +++ b/module/TEST_PyGreSQL_classic_connection.py @@ -12,7 +12,7 @@ """ try: - import unittest2 as unittest # for Python < 2.6 + import unittest2 as unittest # for Python < 2.7 except ImportError: import unittest import sys diff --git a/module/TEST_PyGreSQL_classic_dbwrapper.py b/module/TEST_PyGreSQL_classic_dbwrapper.py index be0aac64..3b3c7233 100755 --- a/module/TEST_PyGreSQL_classic_dbwrapper.py +++ b/module/TEST_PyGreSQL_classic_dbwrapper.py @@ -14,7 +14,7 @@ from __future__ import with_statement try: - import unittest2 as unittest # for Python < 2.6 + import unittest2 as unittest # for Python < 2.7 except ImportError: import unittest diff --git a/module/TEST_PyGreSQL_classic_functions.py b/module/TEST_PyGreSQL_classic_functions.py index 530e6d14..7d9e7dcf 100755 --- a/module/TEST_PyGreSQL_classic_functions.py +++ b/module/TEST_PyGreSQL_classic_functions.py @@ -13,7 +13,7 @@ try: - import unittest2 as unittest # for Python < 2.6 + import unittest2 as unittest # for Python < 2.7 except ImportError: import unittest diff --git a/module/TEST_PyGreSQL_classic_largeobj.py b/module/TEST_PyGreSQL_classic_largeobj.py index 6d536a9e..86776c46 100755 --- a/module/TEST_PyGreSQL_classic_largeobj.py +++ b/module/TEST_PyGreSQL_classic_largeobj.py @@ -12,7 +12,7 @@ """ try: - import unittest2 as unittest # for Python < 2.6 + import unittest2 as unittest # for Python < 2.7 except ImportError: import unittest import tempfile From 7ef7d7d50e837f66959cf088a725843652832607 Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Fri, 20 Nov 2015 11:32:39 +0000 Subject: [PATCH 002/144] The current source compiles without warnings. Let's keep it that way. --- module/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/setup.py b/module/setup.py index e65a21d7..274e7547 100755 --- a/module/setup.py +++ b/module/setup.py @@ -87,7 +87,7 @@ def pg_version(): library_dirs = [get_python_lib(), pg_config('libdir')] define_macros = [('PYGRESQL_VERSION', version)] undef_macros = [] -extra_compile_args = ['-O2'] +extra_compile_args = ['-O2', '-Wall', '-Werror'] class build_pg_ext(build_ext): From 1cb7f1ffd66f492df179d12872092e77d2c5a36b Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 20 Nov 2015 15:04:44 +0000 Subject: [PATCH 003/144] Proper testing of the version number --- module/TEST_PyGreSQL_classic_functions.py | 17 ++++++++++++----- module/setup.py | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/module/TEST_PyGreSQL_classic_functions.py b/module/TEST_PyGreSQL_classic_functions.py index 7d9e7dcf..a4d154da 100755 --- a/module/TEST_PyGreSQL_classic_functions.py +++ b/module/TEST_PyGreSQL_classic_functions.py @@ -17,6 +17,8 @@ except ImportError: import unittest +import re + import pg # the module under test @@ -311,11 +313,16 @@ class TestModuleConstants(unittest.TestCase): def testVersion(self): v = pg.version self.assertIsInstance(v, str) - v = v.split('.') - self.assertTrue(2 <= len(v) <= 3) - for w in v: - self.assertTrue(1 <= len(w) <= 2) - self.assertTrue(w.isdigit()) + # make sure the version conforms to PEP440 + re_version = r"""^ + (\d[\.\d]*(?<= \d)) + ((?:[abc]|rc)\d+)? + (?:(\.post\d+))? + (?:(\.dev\d+))? + (?:(\+(?![.])[a-zA-Z0-9\.]*[a-zA-Z0-9]))? + $""" + match = re.match(re_version, v, re.X) + self.assertIsNotNone(match) if __name__ == '__main__': diff --git a/module/setup.py b/module/setup.py index 274e7547..2f2d5cb8 100755 --- a/module/setup.py +++ b/module/setup.py @@ -35,7 +35,7 @@ """ -version = '4.1.1' +version = '4.1.2a1' import sys From f04e4389ba1a007a5b547fd366c9efd93b6213fe Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 20 Nov 2015 16:09:28 +0000 Subject: [PATCH 004/144] Add test for printing large objects Again, note that the print function does not work with StringIO. --- module/TEST_PyGreSQL_classic_connection.py | 12 +++++------ module/TEST_PyGreSQL_classic_largeobj.py | 23 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/module/TEST_PyGreSQL_classic_connection.py b/module/TEST_PyGreSQL_classic_connection.py index 162cfc76..b8969aac 100755 --- a/module/TEST_PyGreSQL_classic_connection.py +++ b/module/TEST_PyGreSQL_classic_connection.py @@ -16,6 +16,7 @@ except ImportError: import unittest import sys +import tempfile import threading import time @@ -27,8 +28,6 @@ except ImportError: # Python < 2.6 namedtuple = None -from StringIO import StringIO - # We need a database to test against. If LOCAL_PyGreSQL.py exists we will # get our information from that. Otherwise we use the defaults. dbname = 'unittest' @@ -472,16 +471,17 @@ def testPrint(self): q = ("select 1 as a, 'hello' as h, 'w' as world" " union select 2, 'xyz', 'uvw'") r = self.c.query(q) - s = StringIO() - stdout, sys.stdout = sys.stdout, s + f = tempfile.TemporaryFile() + stdout, sys.stdout = sys.stdout, f try: print r except Exception: pass finally: sys.stdout = stdout - r = s.getvalue() - s.close() + f.seek(0) + r = f.read() + f.close() self.assertEqual(r, 'a| h |world\n' '-+-----+-----\n' diff --git a/module/TEST_PyGreSQL_classic_largeobj.py b/module/TEST_PyGreSQL_classic_largeobj.py index 86776c46..3cde2861 100755 --- a/module/TEST_PyGreSQL_classic_largeobj.py +++ b/module/TEST_PyGreSQL_classic_largeobj.py @@ -15,6 +15,7 @@ import unittest2 as unittest # for Python < 2.7 except ImportError: import unittest +import sys import tempfile import pg # the module under test @@ -365,6 +366,28 @@ def testExport(self): f.close() self.assertEqual(r, data) + def testPrint(self): + self.obj.open(pg.INV_WRITE) + data = 'some object to be printed' + self.obj.write(data) + f = tempfile.TemporaryFile() + stdout, sys.stdout = sys.stdout, f + try: + print self.obj + self.obj.close() + print self.obj + except Exception: + pass + finally: + sys.stdout = stdout + f.seek(0) + r = f.read() + f.close() + oid = self.obj.oid + self.assertEqual(r, + 'Opened large object, oid %d\n' + 'Closed large object, oid %d\n' % (oid, oid)) + if __name__ == '__main__': unittest.main() From bac74ac5034da4761836d47c94884d55f07cbede Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 20 Nov 2015 18:45:01 +0000 Subject: [PATCH 005/144] Small corrections in the docs --- docs/pg.txt | 2 +- module/pg.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pg.txt b/docs/pg.txt index cde55f33..444bb22f 100644 --- a/docs/pg.txt +++ b/docs/pg.txt @@ -457,7 +457,7 @@ Description: insert statement that inserted exactly one row into a table that has OIDs, the return value is the OID of the newly inserted row. If the query is an update or delete statement, or an insert statement that did not insert exactly one - row in a table with OIDs, then the numer of rows affected is returned as a + row in a table with OIDs, then the number of rows affected is returned as a string. If it is a statement that returns rows as a result (usually a select statement, but maybe also an "insert/update ... returning" statement), this method returns a `pgqueryobject` that can be accessed via the `getresult()`, diff --git a/module/pg.py b/module/pg.py index 1c579c48..2af1cc44 100644 --- a/module/pg.py +++ b/module/pg.py @@ -47,7 +47,7 @@ namedtuple = None -# Auxiliary functions which are independent from a DB connection: +# Auxiliary functions that are independent of a DB connection: def _is_quoted(s): """Check whether this string is a quoted identifier.""" From 4ef66ef6995acbd7e2fc0de646526e68860e1210 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 20 Nov 2015 21:44:55 +0000 Subject: [PATCH 006/144] Using a better locale name for tests In the test we need to force Postgres to use a money format that does not use a dot as decimal point. Maybe we should try several common locale names to get this test running on different platforms? --- module/TEST_PyGreSQL_classic_connection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/TEST_PyGreSQL_classic_connection.py b/module/TEST_PyGreSQL_classic_connection.py index b8969aac..dc9f7c7e 100755 --- a/module/TEST_PyGreSQL_classic_connection.py +++ b/module/TEST_PyGreSQL_classic_connection.py @@ -918,7 +918,7 @@ def testSetDecimalPoint(self): # check that money values can be interpreted correctly # if and only if the decimal point is set appropriately # for the current lc_monetary setting - query("set lc_monetary='en_US'") + query("set lc_monetary='en_US.UTF-8'") pg.set_decimal_point('.') r = query("select '34.25'::money").getresult()[0][0] self.assertIsInstance(r, d) @@ -926,7 +926,7 @@ def testSetDecimalPoint(self): pg.set_decimal_point(',') r = query("select '34.25'::money").getresult()[0][0] self.assertNotEqual(r, d('34.25')) - query("set lc_monetary='de_DE'") + query("set lc_monetary='de_DE.UTF-8'") pg.set_decimal_point(',') r = query("select '34,25'::money").getresult()[0][0] self.assertIsInstance(r, d) From 8f80625bd672f8777026c57a4a98133d42423c39 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 21 Nov 2015 13:20:28 +0000 Subject: [PATCH 007/144] Don't break named tuple result function in test After running the test, set the function back to its default, so that other tests will not break if you use unittest to run multiple test modules at once, since in that case they use the same interpreter. --- module/TEST_PyGreSQL_classic_functions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/module/TEST_PyGreSQL_classic_functions.py b/module/TEST_PyGreSQL_classic_functions.py index a4d154da..52d3296c 100755 --- a/module/TEST_PyGreSQL_classic_functions.py +++ b/module/TEST_PyGreSQL_classic_functions.py @@ -304,7 +304,8 @@ def testSetDecimal(self): pg.set_decimal(decimal_class) def testSetNamedresult(self): - pg.set_namedresult(tuple) + pg.set_namedresult(lambda q: q.getresult()) + pg.set_namedresult(pg._namedresult) class TestModuleConstants(unittest.TestCase): From c101dcc7364708c1b71d53cdaee790d4a45c9375 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 21 Nov 2015 14:16:44 +0000 Subject: [PATCH 008/144] Improve tests for long ints under Python 2 PyGreSQL returns longs for Postgres bigints, even though theoretically it could return ints in the case of a 64bit Python. But that's ok because it makes the behavior more consistent and because the int/long split becomes irrelevant in Python 3 anyway. --- module/TEST_PyGreSQL_classic_connection.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/module/TEST_PyGreSQL_classic_connection.py b/module/TEST_PyGreSQL_classic_connection.py index dc9f7c7e..d65b9ec5 100755 --- a/module/TEST_PyGreSQL_classic_connection.py +++ b/module/TEST_PyGreSQL_classic_connection.py @@ -253,12 +253,19 @@ def testGetresult(self): self.assertEqual(r, result) def testGetresultLong(self): - q = "select 1234567890123456790" - result = 1234567890123456790L + q = "select 9876543210" + result = 9876543210L v = self.c.query(q).getresult()[0][0] self.assertIsInstance(v, long) self.assertEqual(v, result) + def testGetresultDecimal(self): + q = "select 98765432109876543210" + result = Decimal(98765432109876543210L) + v = self.c.query(q).getresult()[0][0] + self.assertIsInstance(v, Decimal) + self.assertEqual(v, result) + def testGetresultString(self): result = 'Hello, world!' q = "select '%s'" % result @@ -277,12 +284,19 @@ def testDictresult(self): self.assertEqual(r, result) def testDictresultLong(self): - q = "select 1234567890123456790 as longjohnsilver" - result = 1234567890123456790L + q = "select 9876543210 as longjohnsilver" + result = 9876543210L v = self.c.query(q).dictresult()[0]['longjohnsilver'] self.assertIsInstance(v, long) self.assertEqual(v, result) + def testDictresultDecimal(self): + q = "select 98765432109876543210 as longjohnsilver" + result = Decimal(98765432109876543210L) + v = self.c.query(q).dictresult()[0]['longjohnsilver'] + self.assertIsInstance(v, Decimal) + self.assertEqual(v, result) + def testDictresultString(self): result = 'Hello, world!' q = "select '%s' as greeting" % result From e4561ffb288c3970d288417825832d6db6d4817e Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 21 Nov 2015 17:41:04 +0000 Subject: [PATCH 009/144] Set lc_monetary before testing inserttable() --- module/TEST_PyGreSQL_classic_connection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/module/TEST_PyGreSQL_classic_connection.py b/module/TEST_PyGreSQL_classic_connection.py index d65b9ec5..87905309 100755 --- a/module/TEST_PyGreSQL_classic_connection.py +++ b/module/TEST_PyGreSQL_classic_connection.py @@ -655,6 +655,7 @@ def tearDownClass(cls): def setUp(self): self.c = connect() + self.c.query("set lc_monetary='C'") self.c.query("set datestyle='ISO,YMD'") def tearDown(self): From 0740946725281e2d2387e8e857a324677cc7e463 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 22 Nov 2015 04:19:45 +0000 Subject: [PATCH 010/144] Fix bad name of a test function --- module/TEST_PyGreSQL_classic_connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/TEST_PyGreSQL_classic_connection.py b/module/TEST_PyGreSQL_classic_connection.py index 87905309..67bd17f1 100755 --- a/module/TEST_PyGreSQL_classic_connection.py +++ b/module/TEST_PyGreSQL_classic_connection.py @@ -797,7 +797,7 @@ def testPutline(self): r = query("select * from test").getresult() self.assertEqual(r, data) - def testPutline(self): + def testGetline(self): getline = self.c.getline query = self.c.query data = list(enumerate("apple banana pear plum strawberry".split())) From 350d563dde4df94a141a6b8037880101612f7622 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 22 Nov 2015 04:48:42 +0000 Subject: [PATCH 011/144] Fix debugging of DB() via open file --- module/pg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/pg.py b/module/pg.py index 2af1cc44..21c4f7cd 100644 --- a/module/pg.py +++ b/module/pg.py @@ -332,7 +332,7 @@ def _do_debug(self, s): if isinstance(self.debug, basestring): print(self.debug % s) elif isinstance(self.debug, file): - file.write(s + '\n') + self.debug.write(s + '\n') elif callable(self.debug): self.debug(s) else: From 81ef954fa482513eff27f46388b7b1ca66ea579d Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 25 Nov 2015 12:59:03 +0000 Subject: [PATCH 012/144] Backport decimal_point handling from trunk to 4.x This fixes some issues with the old implementation and also introduces the new feature to return money values formatted as string by setting decimal_point(None), which was not possible before. Everything is still totally backward compatible as decimal_point('.') is still the default. --- module/TEST_PyGreSQL_classic_connection.py | 43 ++++++++++++++++---- module/TEST_PyGreSQL_classic_dbwrapper.py | 8 ++-- module/pgmodule.c | 47 ++++++++++++++++------ module/setup.py | 2 +- 4 files changed, 77 insertions(+), 23 deletions(-) diff --git a/module/TEST_PyGreSQL_classic_connection.py b/module/TEST_PyGreSQL_classic_connection.py index 67bd17f1..33e8807f 100755 --- a/module/TEST_PyGreSQL_classic_connection.py +++ b/module/TEST_PyGreSQL_classic_connection.py @@ -934,22 +934,49 @@ def testSetDecimalPoint(self): # if and only if the decimal point is set appropriately # for the current lc_monetary setting query("set lc_monetary='en_US.UTF-8'") + pg.set_decimal_point(None) + try: + r = query("select '34.25'::money").getresult()[0][0] + finally: + pg.set_decimal_point(point) + self.assertIsInstance(r, str) + self.assertIn(r, ( + '$34.25', '$ 34.25', '34.25$', '34.25 $', '34.25 Dollar')) pg.set_decimal_point('.') - r = query("select '34.25'::money").getresult()[0][0] + try: + r = query("select '34.25'::money").getresult()[0][0] + finally: + pg.set_decimal_point(point) self.assertIsInstance(r, d) self.assertEqual(r, d('34.25')) pg.set_decimal_point(',') - r = query("select '34.25'::money").getresult()[0][0] + try: + r = query("select '34.25'::money").getresult()[0][0] + finally: + pg.set_decimal_point(point) self.assertNotEqual(r, d('34.25')) query("set lc_monetary='de_DE.UTF-8'") + pg.set_decimal_point(None) + try: + r = query("select '34,25'::money").getresult()[0][0] + finally: + pg.set_decimal_point(point) + self.assertIsInstance(r, str) + self.assertIn(r, ('34,25€', '34,25 €', '€34,25' '€ 34,25', + '34,25 EUR', '34,25 Euro', '34,25 DM')) pg.set_decimal_point(',') - r = query("select '34,25'::money").getresult()[0][0] + try: + r = query("select '34,25'::money").getresult()[0][0] + finally: + pg.set_decimal_point(point) self.assertIsInstance(r, d) self.assertEqual(r, d('34.25')) - pg.set_decimal_point('.') + try: + pg.set_decimal_point('.') + finally: + pg.set_decimal_point(point) r = query("select '34,25'::money").getresult()[0][0] self.assertNotEqual(r, d('34.25')) - pg.set_decimal_point(point) def testSetDecimal(self): d = pg.Decimal @@ -958,11 +985,13 @@ def testSetDecimal(self): self.assertIsInstance(r, d) self.assertEqual(r, d('3425')) pg.set_decimal(long) - r = query("select 3425::numeric").getresult()[0][0] + try: + r = query("select 3425::numeric").getresult()[0][0] + finally: + pg.set_decimal(d) self.assertNotIsInstance(r, d) self.assertIsInstance(r, long) self.assertEqual(r, 3425L) - pg.set_decimal(d) @unittest.skipUnless(namedtuple, 'Named tuples not available') def testSetNamedresult(self): diff --git a/module/TEST_PyGreSQL_classic_dbwrapper.py b/module/TEST_PyGreSQL_classic_dbwrapper.py index 3b3c7233..979bd813 100755 --- a/module/TEST_PyGreSQL_classic_dbwrapper.py +++ b/module/TEST_PyGreSQL_classic_dbwrapper.py @@ -283,9 +283,11 @@ def tearDownClass(cls): def setUp(self): self.db = DB() - self.db.query("set lc_monetary='C'") - self.db.query('set bytea_output=hex') - self.db.query('set standard_conforming_strings=on') + query = self.db.query + query('set client_encoding=utf8') + query('set standard_conforming_strings=on') + query('set bytea_output=hex') + query("set lc_monetary='C'") def tearDown(self): self.db.close() diff --git a/module/pgmodule.c b/module/pgmodule.c index 0fc4e30d..ebb0dd62 100644 --- a/module/pgmodule.c +++ b/module/pgmodule.c @@ -119,7 +119,7 @@ int *get_type_array(PGresult *result, int nfields); static PyObject *decimal = NULL, /* decimal type */ *namedresult = NULL; /* function for getting named results */ -static char *decimal_point = "."; /* decimal point used in money values */ +static char decimal_point = '.'; /* decimal point used in money values */ /* --------------------------------------------------------------------- */ @@ -2170,13 +2170,15 @@ pgquery_getresult(pgqueryobject *self, PyObject *args) break; case 5: /* money */ + /* convert to decimal only if decimal point is set */ + if (!decimal_point) goto default_case; for (k = 0; *s && k < sizeof(cashbuf) / sizeof(cashbuf[0]) - 1; s++) { - if (isdigit(*s)) + if (*s >= '0' && *s <= '9') cashbuf[k++] = *s; - else if (*s == *decimal_point) + else if (*s == decimal_point) cashbuf[k++] = '.'; else if (*s == '(' || *s == '-') cashbuf[k++] = '-'; @@ -2200,6 +2202,7 @@ pgquery_getresult(pgqueryobject *self, PyObject *args) break; default: + default_case: val = PyString_FromString(s); break; } @@ -2297,13 +2300,16 @@ pgquery_dictresult(pgqueryobject *self, PyObject *args) break; case 5: /* money */ + /* convert to decimal only if decimal point is set */ + if (!decimal_point) goto default_case; + for (k = 0; *s && k < sizeof(cashbuf) / sizeof(cashbuf[0]) - 1; s++) { - if (isdigit(*s)) + if (*s >= '0' && *s <= '9') cashbuf[k++] = *s; - else if (*s == *decimal_point) + else if (*s == decimal_point) cashbuf[k++] = '.'; else if (*s == '(' || *s == '-') cashbuf[k++] = '-'; @@ -2327,6 +2333,7 @@ pgquery_dictresult(pgqueryobject *self, PyObject *args) break; default: + default_case: val = PyString_FromString(s); break; } @@ -3701,13 +3708,24 @@ static PyObject * set_decimal_point(PyObject *self, PyObject * args) { PyObject *ret = NULL; - char *s; + char *s = NULL; - if (PyArg_ParseTuple(args, "s", &s)) - { - decimal_point = s; + /* gets arguments */ + if (PyArg_ParseTuple(args, "z", &s)) { + if (!s) + s = "\0"; + else if (*s && (*(s+1) || !strchr(".,;: '*/_`|", *s))) + s = NULL; + } + + if (s) { + decimal_point = *s; Py_INCREF(Py_None); ret = Py_None; + } else { + PyErr_SetString(PyExc_TypeError, + "set_decimal_point() expects a decimal mark character"); } + return ret; } @@ -3719,18 +3737,23 @@ static PyObject * get_decimal_point(PyObject *self, PyObject * args) { PyObject *ret = NULL; + char s[2]; if (PyArg_ParseTuple(args, "")) { - ret = PyString_FromString(decimal_point); + if (decimal_point) { + s[0] = decimal_point; s[1] = '\0'; + ret = PyString_FromString(s); + } else { + Py_INCREF(Py_None); ret = Py_None; + } } else { PyErr_SetString(PyExc_TypeError, - " get_decimal_point() takes no parameter"); + "get_decimal_point() takes no parameter"); } - return ret; } diff --git a/module/setup.py b/module/setup.py index 2f2d5cb8..ff3b2de9 100755 --- a/module/setup.py +++ b/module/setup.py @@ -87,7 +87,7 @@ def pg_version(): library_dirs = [get_python_lib(), pg_config('libdir')] define_macros = [('PYGRESQL_VERSION', version)] undef_macros = [] -extra_compile_args = ['-O2', '-Wall', '-Werror'] +extra_compile_args = ['-O2', '-Wall', '-Werror', '-funsigned-char'] class build_pg_ext(build_ext): From bcdf1826f505966aabdd1470de3d5418f4356e17 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 25 Nov 2015 13:40:18 +0000 Subject: [PATCH 013/144] Backport notification handler fix to 4.x The notificaton handler had been fixed in trunk so that it will not drop concurrent messages any more. --- module/TEST_PyGreSQL_classic.py | 144 +++++++++++++++++++------------- module/pg.py | 36 ++++---- 2 files changed, 102 insertions(+), 78 deletions(-) diff --git a/module/TEST_PyGreSQL_classic.py b/module/TEST_PyGreSQL_classic.py index ba9a058e..65fb4fe4 100755 --- a/module/TEST_PyGreSQL_classic.py +++ b/module/TEST_PyGreSQL_classic.py @@ -234,67 +234,93 @@ def notify_callback(self, arg_dict): else: self.notify_timeout = True - def test_notify(self): + def test_notify(self, options=None): + if not options: + options = {} + run_as_method = options.get('run_as_method') + call_notify = options.get('call_notify') + two_payloads = options.get('two_payloads') + db = opendb() + # Get function under test, can be standalone or DB method. + fut = db.notification_handler if run_as_method else partial( + NotificationHandler, db) + arg_dict = dict(event=None, called=False) + self.notify_timeout = False + # Listen for 'event_1'. + target = fut('event_1', self.notify_callback, arg_dict, 5) + thread = Thread(None, target) + thread.start() + try: + # Wait until the thread has started. + for n in range(500): + if target.listening: + break + sleep(0.01) + self.assertTrue(target.listening) + self.assertTrue(thread.isAlive()) + # Open another connection for sending notifications. + db2 = opendb() + # Generate notification from the other connection. + if two_payloads: + db2.begin() + if call_notify: + if two_payloads: + target.notify(db2, payload='payload 0') + target.notify(db2, payload='payload 1') + else: + if two_payloads: + db2.query("notify event_1, 'payload 0'") + db2.query("notify event_1, 'payload 1'") + if two_payloads: + db2.commit() + # Wait until the notification has been caught. + for n in range(500): + if arg_dict['called'] or self.notify_timeout: + break + sleep(0.01) + # Check that callback has been invoked. + self.assertTrue(arg_dict['called']) + self.assertEqual(arg_dict['event'], 'event_1') + self.assertEqual(arg_dict['extra'], 'payload 1') + self.assertTrue(isinstance(arg_dict['pid'], int)) + self.assertFalse(self.notify_timeout) + arg_dict['called'] = False + self.assertTrue(thread.isAlive()) + # Generate stop notification. + if call_notify: + target.notify(db2, stop=True, payload='payload 2') + else: + db2.query("notify stop_event_1, 'payload 2'") + db2.close() + # Wait until the notification has been caught. + for n in range(500): + if arg_dict['called'] or self.notify_timeout: + break + sleep(0.01) + # Check that callback has been invoked. + self.assertTrue(arg_dict['called']) + self.assertEqual(arg_dict['event'], 'stop_event_1') + self.assertEqual(arg_dict['extra'], 'payload 2') + self.assertTrue(isinstance(arg_dict['pid'], int)) + self.assertFalse(self.notify_timeout) + thread.join(5) + self.assertFalse(thread.isAlive()) + self.assertFalse(target.listening) + finally: + target.close() + if thread.is_alive(): + thread.join(5) + + def test_notify_other_options(self): for run_as_method in False, True: for call_notify in False, True: - db = opendb() - # Get function under test, can be standalone or DB method. - fut = db.notification_handler if run_as_method else partial( - NotificationHandler, db) - arg_dict = dict(event=None, called=False) - self.notify_timeout = False - # Listen for 'event_1'. - target = fut('event_1', self.notify_callback, arg_dict) - thread = Thread(None, target) - thread.start() - # Wait until the thread has started. - for n in xrange(500): - if target.listening: - break - sleep(0.01) - self.assertTrue(target.listening) - self.assertTrue(thread.isAlive()) - # Open another connection for sending notifications. - db2 = opendb() - # Generate notification from the other connection. - if call_notify: - target.notify(db2, payload='payload 1') - else: - db2.query("notify event_1, 'payload 1'") - # Wait until the notification has been caught. - for n in xrange(500): - if arg_dict['called'] or self.notify_timeout: - break - sleep(0.01) - # Check that callback has been invoked. - self.assertTrue(arg_dict['called']) - self.assertEqual(arg_dict['event'], 'event_1') - self.assertEqual(arg_dict['extra'], 'payload 1') - self.assertTrue(isinstance(arg_dict['pid'], int)) - self.assertFalse(self.notify_timeout) - arg_dict['called'] = False - self.assertTrue(thread.isAlive()) - # Generate stop notification. - if call_notify: - target.notify(db2, stop=True, payload='payload 2') - else: - db2.query("notify stop_event_1, 'payload 2'") - db2.close() - # Wait until the notification has been caught. - for n in xrange(500): - if arg_dict['called'] or self.notify_timeout: - break - sleep(0.01) - # Check that callback has been invoked. - self.assertTrue(arg_dict['called']) - self.assertEqual(arg_dict['event'], 'stop_event_1') - self.assertEqual(arg_dict['extra'], 'payload 2') - self.assertTrue(isinstance(arg_dict['pid'], int)) - self.assertFalse(self.notify_timeout) - thread.join(5) - self.assertFalse(thread.isAlive()) - self.assertFalse(target.listening) - target.close() + for two_payloads in False, True: + options = dict( + run_as_method=run_as_method, + call_notify=call_notify, + two_payloads=two_payloads) + if True in options.values(): + self.test_notify(options) def test_notify_timeout(self): for run_as_method in False, True: diff --git a/module/pg.py b/module/pg.py index 21c4f7cd..325a0d84 100644 --- a/module/pg.py +++ b/module/pg.py @@ -225,30 +225,28 @@ def __call__(self, close=False): self.listen() _ilist = [self.db.fileno()] - while True: + while self.listening: ilist, _olist, _elist = select.select(_ilist, [], [], self.timeout) - if ilist == []: # we timed out - self.unlisten() - self.callback(None) - break - else: - notice = self.db.getnotify() - if notice is None: - continue - event, pid, extra = notice - if event in (self.event, self.stop_event): + if ilist: + while self.listening: + notice = self.db.getnotify() + if not notice: # no more messages + break + event, pid, extra = notice + if event not in (self.event, self.stop_event): + self.unlisten() + raise _db_error( + 'listening for "%s" and "%s", but notified of "%s"' + % (self.event, self.stop_event, event)) + if event == self.stop_event: + self.unlisten() self.arg_dict['pid'] = pid self.arg_dict['event'] = event self.arg_dict['extra'] = extra self.callback(self.arg_dict) - if event == self.stop_event: - self.unlisten() - break - else: - self.unlisten() - raise _db_error( - 'listening for "%s" and "%s", but notified of "%s"' - % (self.event, self.stop_event, event)) + else: # we timed out + self.unlisten() + self.callback(None) def pgnotify(*args, **kw): From d3c5a6436eaa894bd0a781e782fc4e09a9daaf38 Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Thu, 26 Nov 2015 15:34:58 +0000 Subject: [PATCH 014/144] Start adding release notes for 4.2. --- docs/changelog.rst | 6 ++++++ docs/changelog.txt | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 3733bfb0..4393e432 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -3,6 +3,12 @@ ChangeLog Version 4.2 ----------- +- Set a better default for the user option "escaping-funcs". +- Greatly improve unit testing. +- Force build to compile with no errors. +- Fix decimal point handling +- Add option to return money as string +- Fix notification handler (Thanks Patrick TJ McPhee). Version 4.1.1 (2013-01-08) -------------------------- diff --git a/docs/changelog.txt b/docs/changelog.txt index dd1764ab..0c2b7513 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -5,6 +5,13 @@ PyGreSQL ChangeLog Version 4.2 ----------- +- Set a better default for the user option "escaping-funcs". +- Greatly improve unit testing. +- Force build to compile with no errors. +- Fix decimal point handling +- Add option to return money as string +- Fix notification handler (Thanks Patrick TJ McPhee). + Version 4.1.1 (2013-01-08) -------------------------- - Add WhenNotified class and method. Replaces need for third party pgnotify. From e733e58e1bd9fa3b01587f450b6795e48a8da3ee Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Thu, 26 Nov 2015 15:37:37 +0000 Subject: [PATCH 015/144] Prepare setup script for 4.2. --- module/setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/module/setup.py b/module/setup.py index ff3b2de9..93b4866c 100755 --- a/module/setup.py +++ b/module/setup.py @@ -1,7 +1,7 @@ #! /usr/bin/python # $Id$ -"""Setup script for PyGreSQL version 4.1 +"""Setup script for PyGreSQL version 4.2 PyGreSQL is an open-source Python module that interfaces to a PostgreSQL database. It embeds the PostgreSQL query library to allow @@ -21,7 +21,7 @@ * PostgreSQL pg_config tool (usually included in the devel package) (the Windows installer has it as part of the database server feature) -The supported versions are Python 2.5-2.7 and PostgreSQL 8.3-9.2. +The supported versions are Python 2.5-2.7 and PostgreSQL 8.3-9.4. Use as follows: python setup.py build # to build the module @@ -35,7 +35,7 @@ """ -version = '4.1.2a1' +version = '4.2' import sys From ec89f46bf313d1e8d744d12f3cdb82629608ee26 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 26 Nov 2015 19:23:44 +0000 Subject: [PATCH 016/144] Clean up the docs We had used .txt for the old docs and .rst for the new Sphinx docs, which unfortunately got out of sync. I have merged the duplicates, keeping the latest version of the text, and left them only as .txt files for the time being until we give Sphinx another go. The index file is not auto generated - the index.rst file is only a reminder that we want to move everything to Sphinx. --- docs/changelog.rst | 336 -------------------- docs/changelog.txt | 9 +- docs/{classic.rst => classic.txt} | 4 +- docs/{copyright.rst => copyright.txt} | 0 docs/{db_api.rst => db_api.txt} | 0 docs/{examples.rst => examples.txt} | 0 docs/future.rst | 57 ---- docs/future.txt | 8 +- docs/install.rst | 179 ----------- docs/install.txt | 15 +- docs/{interface.rst => interface.txt} | 0 docs/{introduction.rst => introduction.txt} | 0 docs/{mailinglist.rst => mailinglist.txt} | 0 docs/pg.txt | 4 +- docs/{pgdb.txt => pgdb.rst} | 0 docs/{svn.rst => svn.txt} | 0 16 files changed, 17 insertions(+), 595 deletions(-) delete mode 100644 docs/changelog.rst rename docs/{classic.rst => classic.txt} (99%) rename docs/{copyright.rst => copyright.txt} (100%) rename docs/{db_api.rst => db_api.txt} (100%) rename docs/{examples.rst => examples.txt} (100%) delete mode 100644 docs/future.rst delete mode 100644 docs/install.rst rename docs/{interface.rst => interface.txt} (100%) rename docs/{introduction.rst => introduction.txt} (100%) rename docs/{mailinglist.rst => mailinglist.txt} (100%) rename docs/{pgdb.txt => pgdb.rst} (100%) rename docs/{svn.rst => svn.txt} (100%) diff --git a/docs/changelog.rst b/docs/changelog.rst deleted file mode 100644 index 4393e432..00000000 --- a/docs/changelog.rst +++ /dev/null @@ -1,336 +0,0 @@ -ChangeLog -========= - -Version 4.2 ------------ -- Set a better default for the user option "escaping-funcs". -- Greatly improve unit testing. -- Force build to compile with no errors. -- Fix decimal point handling -- Add option to return money as string -- Fix notification handler (Thanks Patrick TJ McPhee). - -Version 4.1.1 (2013-01-08) --------------------------- -- Add WhenNotified class and method. Replaces need for third party pgnotify. -- Sharpen test for inserting current_timestamp. -- Add more quote tests. False and 0 should evaluate to NULL. -- More tests - Any number other than 0 is True. -- Do not use positional parameters internally. - This restores backward compatibility with version 4.0. -- Add methods for changing the decimal point. - -Version 4.1 (2013-01-01) ------------------------- -- Dropped support for Python below 2.5 and PostgreSQL below 8.3. -- Support the new PostgreSQL versions 9.0, 9.1 and 9.2. -- Particularly, support PQescapeLiteral() and PQescapeIdentifier(). -- The query method of the classic API now supports positional parameters. - This an effective way to pass arbitrary or unknown data without worrying - about SQL injection or syntax errors (contribution by Patrick TJ McPhee). -- The classic API now supports a method namedresult() in addition to - getresult() and dictresult(), which returns the rows of the result - as named tuples if these are supported (Python 2.6 or higher). -- The execute() and executemany() methods now return the cursor object, - so you can now write statements like "for row in cursor.execute(...)" - (as suggested by Adam Frederick). -- Binary objects are now automatically escaped and unescaped. -- Bug in money quoting fixed. Amounts of $0.00 handled correctly. -- Proper handling of date and time objects as input. -- Proper handling of floats with 'nan' or 'inf' values as input. -- Fixed the set_decimal() function. -- All DatabaseError instances now have a sqlstate attribute. -- The getnotify() method can now also return payload strings (#15). -- Better support for notice processing with the new methods - set_notice_receiver() and get_notice_receiver() - (as suggested by Michael Filonenko, see #37). -- Open transactions are rolled back when pgdb connections are closed - (as suggested by Peter Harris, see #46). -- Connections and cursors can now be used with the "with" statement - (as suggested by Peter Harris, see #46). -- New method use_regtypes() that can be called to let getattnames() - return regular type names instead of the simplified classic types (#44). - - -Version 4.0 (2009-01-01) ------------------------- -- Dropped support for Python below 2.3 and PostgreSQL below 7.4. -- Improved performance of fetchall() for large result sets - by speeding up the type casts (as suggested by Peter Schuller). -- Exposed exceptions as attributes of the connection object. -- Exposed connection as attribute of the cursor object. -- Cursors now support the iteration protocol. -- Added new method to get parameter settings. -- Added customizable row_factory as suggested by Simon Pamies. -- Separated between mandatory and additional type objects. -- Added keyword args to insert, update and delete methods. -- Added exception handling for direct copy. -- Start transactions only when necessary, not after every commit(). -- Release the GIL while making a connection - (as suggested by Peter Schuller). -- If available, use decimal.Decimal for numeric types. -- Allow DB wrapper to be used with DB-API 2 connections - (as suggested by Chris Hilton). -- Made private attributes of DB wrapper accessible. -- Dropped dependence on mx.DateTime module. -- Support for PQescapeStringConn() and PQescapeByteaConn(); - these are now also used by the internal _quote() functions. -- Added 'int8' to INTEGER types. New SMALLINT type. -- Added a way to find the number of rows affected by a query() - with the classic pg module by returning it as a string. - For single inserts, query() still returns the oid as an integer. - The pgdb module already provides the "rowcount" cursor attribute - for the same purpose. -- Improved getnotify() by calling PQconsumeInput() instead of - submitting an empty command. -- Removed compatibility code for old OID munging style. -- The insert() and update() methods now use the "returning" clause - if possible to get all changed values, and they also check in advance - whether a subsequent select is possible, so that ongoing transactions - won't break if there is no select privilege. -- Added "protocol_version" and "server_version" attributes. -- Revived the "user" attribute. -- The pg module now works correctly with composite primary keys; - these are represented as frozensets. -- Removed the undocumented and actually unnecessary "view" parameter - from the get() method. -- get() raises a nicer ProgrammingError instead of a KeyError - if no primary key was found. -- delete() now also works based on the primary key if no oid available - and returns whether the row existed or not. - - -Version 3.8.1 (2006-06-05) --------------------------- -- Use string methods instead of deprecated string functions. -- Only use SQL-standard way of escaping quotes. -- Added the functions escape_string() and escape/unescape_bytea() - (as suggested by Charlie Dyson and Kavous Bojnourdi a long time ago). -- Reverted code in clear() method that set date to current. -- Added code for backwards compatibility in OID munging code. -- Reorder attnames tests so that "interval" is checked for before "int." -- If caller supplies key dictionary, make sure that all has a namespace. - -Version 3.8 (2006-02-17) ------------------------- -- Installed new favicon.ico from Matthew Sporleder -- Replaced snprintf by PyOS_snprintf. -- Removed NO_SNPRINTF switch which is not needed any longer -- Clean up some variable names and namespace -- Add get_relations() method to get any type of relation -- Rewrite get_tables() to use get_relations() -- Use new method in get_attnames method to get attributes of views as well -- Add Binary type -- Number of rows is now -1 after executing no-result statements -- Fix some number handling -- Non-simple types do not raise an error any more -- Improvements to documentation framework -- Take into account that nowadays not every table must have an oid column -- Simplification and improvement of the inserttable() function -- Fix up unit tests -- The usual assortment of minor fixes and enhancements - -Version 3.7 (2005-09-07) ------------------------- -Improvement of pgdb module: - -- Use Python standard `datetime` if `mxDateTime` is not available - -Major improvements and clean-up in classic pg module: - -- All members of the underlying connection directly available in `DB` -- Fixes to quoting function -- Add checks for valid database connection to methods -- Improved namespace support, handle `search_path` correctly -- Removed old dust and unnessesary imports, added docstrings -- Internal sql statements as one-liners, smoothed out ugly code - -Version 3.6.2 (2005-02-23) --------------------------- -- Further fixes to namespace handling - -Version 3.6.1 (2005-01-11) --------------------------- -- Fixes to namespace handling - -Version 3.6 (2004-12-17) ------------------------- -- Better DB-API 2.0 compliance -- Exception hierarchy moved into C module and made available to both APIs -- Fix error in update method that caused false exceptions -- Moved to standard exception hierarchy in classic API -- Added new method to get transaction state -- Use proper Python constants where appropriate -- Use Python versions of strtol, etc. Allows Win32 build. -- Bug fixes and cleanups - -Version 3.5 (2004-08-29) ------------------------- -Fixes and enhancements: - -- Add interval to list of data types -- fix up method wrapping especially close() -- retry pkeys once if table missing in case it was just added -- wrap query method separately to handle debug better -- use isinstance instead of type -- fix free/PQfreemem issue - finally -- miscellaneous cleanups and formatting - -Version 3.4 (2004-06-02) ------------------------- -Some cleanups and fixes. -This is the first version where PyGreSQL is moved back out of the -PostgreSQL tree. A lot of the changes mentioned below were actually -made while in the PostgreSQL tree since their last release. - -- Allow for larger integer returns -- Return proper strings for true and false -- Cleanup convenience method creation -- Enhance debugging method -- Add reopen method -- Allow programs to preload field names for speedup -- Move OID handling so that it returns long instead of int -- Miscellaneous cleanups and formatting - -Version 3.3 (2001-12-03) ------------------------- -A few cleanups. Mostly there was some confusion about the latest version -and so I am bumping the number to keep it straight. - -- Added NUMERICOID to list of returned types. This fixes a bug when - returning aggregates in the latest version of PostgreSQL. - -Version 3.2 (2001-06-20) ------------------------- -Note that there are very few changes to PyGreSQL between 3.1 and 3.2. -The main reason for the release is the move into the PostgreSQL -development tree. Even the WIN32 changes are pretty minor. - -- Add Win32 support (gerhard@bigfoot.de) -- Fix some DB-API quoting problems (niall.smart@ebeon.com) -- Moved development into PostgreSQL development tree. - -Version 3.1 (2000-11-06) ------------------------- -- Fix some quoting functions. In particular handle NULLs better. -- Use a method to add primary key information rather than direct - manipulation of the class structures -- Break decimal out in `_quote` (in pg.py) and treat it as float -- Treat timestamp like date for quoting purposes -- Remove a redundant SELECT from the `get` method speeding it, - and `insert` (since it calls `get`) up a little. -- Add test for BOOL type in typecast method to `pgdbTypeCache` class - (tv@beamnet.de) -- Fix pgdb.py to send port as integer to lower level function - (dildog@l0pht.com) -- Change pg.py to speed up some operations -- Allow updates on tables with no primary keys - -Version 3.0 (2000-05-30) ------------------------- -- Remove strlen() call from pglarge_write() and get size from object - (Richard@Bouska.cz) -- Add a little more error checking to the quote function in the wrapper -- Add extra checking in `_quote` function -- Wrap query in pg.py for debugging -- Add DB-API 2.0 support to pgmodule.c (andre@via.ecp.fr) -- Add DB-API 2.0 wrapper pgdb.py (andre@via.ecp.fr) -- Correct keyword clash (temp) in tutorial -- Clean up layout of tutorial -- Return NULL values as None (rlawrence@lastfoot.com) - (WARNING: This will cause backwards compatibility issues) -- Change None to NULL in insert and update -- Change hash-bang lines to use /usr/bin/env -- Clearing date should be blank (NULL) not TODAY -- Quote backslashes in strings in `_quote` (brian@CSUA.Berkeley.EDU) -- Expanded and clarified build instructions (tbryan@starship.python.net) -- Make code thread safe (Jerome.Alet@unice.fr) -- Add README.distutils (mwa@gate.net & jeremy@cnri.reston.va.us) -- Many fixes and increased DB-API compliance by chifungfan@yahoo.com, - tony@printra.net, jeremy@alum.mit.edu and others to get the final - version ready to release. - -Version 2.4 (1999-06-15) ------------------------- -- Insert returns None if the user doesn't have select permissions - on the table. It can (and does) happen that one has insert but - not select permissions on a table. -- Added ntuples() method to query object (brit@druid.net) -- Corrected a bug related to getresult() and the money type -- Corrected a bug related to negative money amounts -- Allow update based on primary key if munged oid not available and - table has a primary key -- Add many __doc__ strings (andre@via.ecp.fr) -- Get method works with views if key specified - -Version 2.3 (1999-04-17) ------------------------- -- connect.host returns "localhost" when connected to Unix socket - (torppa@tuhnu.cutery.fi) -- Use `PyArg_ParseTupleAndKeywords` in connect() (torppa@tuhnu.cutery.fi) -- fixes and cleanups (torppa@tuhnu.cutery.fi) -- Fixed memory leak in dictresult() (terekhov@emc.com) -- Deprecated pgext.py - functionality now in pg.py -- More cleanups to the tutorial -- Added fileno() method - terekhov@emc.com (Mikhail Terekhov) -- added money type to quoting function -- Compiles cleanly with more warnings turned on -- Returns PostgreSQL error message on error -- Init accepts keywords (Jarkko Torppa) -- Convenience functions can be overridden (Jarkko Torppa) -- added close() method - -Version 2.2 (1998-12-21) ------------------------- -- Added user and password support thanks to Ng Pheng Siong (ngps@post1.com) -- Insert queries return the inserted oid -- Add new `pg` wrapper (C module renamed to _pg) -- Wrapped database connection in a class -- Cleaned up some of the tutorial. (More work needed.) -- Added `version` and `__version__`. - Thanks to thilo@eevolute.com for the suggestion. - -Version 2.1 (1998-03-07) ------------------------- -- return fields as proper Python objects for field type -- Cleaned up pgext.py -- Added dictresult method - -Version 2.0 (1997-12-23) -------------------------- -- Updated code for PostgreSQL 6.2.1 and Python 1.5 -- Reformatted code and converted to use full ANSI style prototypes -- Changed name to PyGreSQL (from PyGres95) -- Changed order of arguments to connect function -- Created new type `pgqueryobject` and moved certain methods to it -- Added a print function for pgqueryobject -- Various code changes - mostly stylistic - -Version 1.0b (1995-11-04) -------------------------- -- Keyword support for connect function moved from library file to C code - and taken away from library -- Rewrote documentation -- Bug fix in connect function -- Enhancements in large objects interface methods - -Version 1.0a (1995-10-30) -------------------------- -A limited release. - -- Module adapted to standard Python syntax -- Keyword support for connect function in library file -- Rewrote default parameters interface (internal use of strings) -- Fixed minor bugs in module interface -- Redefinition of error messages - -Version 0.9b (1995-10-10) -------------------------- -The first public release. - -- Large objects implementation -- Many bug fixes, enhancements, ... - -Version 0.1a (1995-10-07) -------------------------- -- Basic libpq functions (SQL access) diff --git a/docs/changelog.txt b/docs/changelog.txt index 0c2b7513..82b8692e 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -1,10 +1,8 @@ -================== -PyGreSQL ChangeLog -================== +ChangeLog +========= Version 4.2 ----------- - - Set a better default for the user option "escaping-funcs". - Greatly improve unit testing. - Force build to compile with no errors. @@ -49,7 +47,7 @@ Version 4.1 (2013-01-01) - The getnotify() method can now also return payload strings (#15). - Better support for notice processing with the new methods set_notice_receiver() and get_notice_receiver() - (as suggested by Michael Filonenko, see #12 and #37). + (as suggested by Michael Filonenko, see #37). - Open transactions are rolled back when pgdb connections are closed (as suggested by Peter Harris, see #46). - Connections and cursors can now be used with the "with" statement @@ -61,7 +59,6 @@ Version 4.1 (2013-01-01) Version 4.0 (2009-01-01) ------------------------ - Dropped support for Python below 2.3 and PostgreSQL below 7.4. -- Added support for Python up to 2.6 and PostgreSQL up to 8.3. - Improved performance of fetchall() for large result sets by speeding up the type casts (as suggested by Peter Schuller). - Exposed exceptions as attributes of the connection object. diff --git a/docs/classic.rst b/docs/classic.txt similarity index 99% rename from docs/classic.rst rename to docs/classic.txt index edbf58b4..0cd89872 100644 --- a/docs/classic.rst +++ b/docs/classic.txt @@ -589,7 +589,7 @@ Exceptions raised: :TypeError: invalid connection Description: - This methods try to get a notify from the server (from the SQL statement + This method tries to get a notify from the server (from the SQL statement NOTIFY). If the server returns no notify, the methods returns None. Otherwise, it returns a tuple (triplet) `(relname, pid, extra)`, where `relname` is the name of the notify, `pid` is the process id of the @@ -813,7 +813,7 @@ Exceptions raised: Description: This methods allows to create large objects in a very simple way. You just - give the name of a file containing the data to be use. + give the name of a file containing the data to be used. Object attributes ----------------- diff --git a/docs/copyright.rst b/docs/copyright.txt similarity index 100% rename from docs/copyright.rst rename to docs/copyright.txt diff --git a/docs/db_api.rst b/docs/db_api.txt similarity index 100% rename from docs/db_api.rst rename to docs/db_api.txt diff --git a/docs/examples.rst b/docs/examples.txt similarity index 100% rename from docs/examples.rst rename to docs/examples.txt diff --git a/docs/future.rst b/docs/future.rst deleted file mode 100644 index 26a3310c..00000000 --- a/docs/future.rst +++ /dev/null @@ -1,57 +0,0 @@ -PyGreSQL future directions -========================== - -This list has been closed since tasks are now managed with the PyGreSQL -tracker that can be found at http://trac.vex.net:8000/pgtracker. -(ticket numbers have been added below): - -To Do ------ - -- Add docs for the pgdb module (everything specific to PyGreSQL) (#5). -- The large object and direct access functions need much more attention (#6). -- The fetch method should use real cursors (#7). -- The C module needs to be cleaned up and redundant code merged, - and should get its own unit test module (#8). -- Clean up test_pg.py and merge it with TEST_PyGreSQL_classic.py (#9). -- The test suite for the classic module should also check that quoted - mixed-case identifiers can be used everywhere - currently they can't. - Improve pg.py accordingly, adding quotes etc. as needed (#10). -- What shall we do with the "tutorial" directory - it's rather a tutorial - for Postgres/SQL than for PyGreSQL, it's using only the query method from - the classic pg module and no other PyGreSQL functionality, it's rather - a demo than a tutorial (#11)? - -Proposed Patches ----------------- - -- Notice handling with PQsetNoticeReceiver and PQsetNoticeProcessor - (one possible implementation was already suggested by Dmitry Dvoinikov - https://mail.vex.net/mailman/private.cgi/pygresql/2005-November/001530.html). - Maybe also make notifications accessible via the optional cursor and - connection attribute "messages" proposed in the DB-API specs (#12). - -Wish List ---------- - -- Make SQLSTATE error codes available (#13). -- Support the new listen/notify infrastructure of PostgreSQL 9.0 (#15). -- Make use of PQexecParams() and PQprepare(). This could speed up - executemany() and allow retrieving binary data directly by setting - the resultFormat parameter to one (#16). -- Enhance cursor.description attribute, delivering more information - available with PQfmod() or PQftable() for instance (#17). -- Support optional "errorhandler" extension (#18). -- Support optional cursor and connection attribute "messages" (#19). -- Connection as context manager (see http://tinyurl.com/32bx6xo) (#20). -- Users should be able to register their own types with _pg (#21). -- Let pg and pgdb support namedtuples (as available in Py 2.6). - pg could get a new method namedresult(), and pgdb could provide - a row factory for namedtuples (similar to sqlite3) (#22). -- New methods in the classic module, similar to getresult() and - dictresult(), but returning dictionaries of rows instead of lists - of rows (with primary key or oids as keys) (#23). -- Make PyGreSQL thread-safe on the connection level (#24). -- The API documentation could be created with Epydoc or Sphinx (#4). -- Write a tutorial for beginners and advanced use (#11). -- More and better documented examples (#4, #5, #11). diff --git a/docs/future.txt b/docs/future.txt index 7cf5a917..1b293389 100644 --- a/docs/future.txt +++ b/docs/future.txt @@ -1,4 +1,3 @@ -========================== PyGreSQL future directions ========================== @@ -10,11 +9,9 @@ To Do ----- - Add docs for the pgdb module (everything specific to PyGreSQL) (#5). -- The large object and direct access functions need much more attention (#6). - The fetch method should use real cursors (#7). - The C module needs to be cleaned up and redundant code merged, and should get its own unit test module (#8). -- Clean up test_pg.py and merge it with TEST_PyGreSQL_classic.py (#9). - The test suite for the classic module should also check that quoted mixed-case identifiers can be used everywhere - currently they can't. Improve pg.py accordingly, adding quotes etc. as needed (#10). @@ -23,6 +20,11 @@ To Do the classic pg module and no other PyGreSQL functionality, it's rather a demo than a tutorial (#11)? +Proposed Patches +---------------- + +- Support for asynchronous command processing (#49). + Wish List --------- diff --git a/docs/install.rst b/docs/install.rst deleted file mode 100644 index cc7fb0fd..00000000 --- a/docs/install.rst +++ /dev/null @@ -1,179 +0,0 @@ -Installation -============ - -General -------- - -You must first have installed Python and PostgreSQL on your system. -If you want to access remote database only, you don't need to install -the full PostgreSQL server, but only the C interface (libpq). If you -are on Windows, make sure that the directory with libpq.dll is in your -``PATH`` environment variable. - -The current version of PyGreSQL has been tested with Python 2.7 and -PostGreSQL 9.2. Older version should work as well, but you will need -at least Python 2.5 and PostgreSQL 8.3. - -PyGreSQL will be installed as three modules, a dynamic module called -_pg.pyd, and two pure Python wrapper modules called pg.py and pgdb.py. -All three files will be installed directly into the Python site-packages -directory. To uninstall PyGreSQL, simply remove these three files again. - - -Installing from a Binary Distribution -------------------------------------- - -This is the easiest way to install PyGreSQL. - -You can currently download PyGreSQL as Linux RPM, NetBSD package and Windows -installer. Make sure the required Python version of the binary package matches -the Python version you have installed. - -Install the package as usual on your system. - -Note that the documentation is currently only included in the source package. - - -Installing from Source ----------------------- - -If you want to install PyGreSQL from Source, or there is no binary -package available for your platform, follow these instructions. - -Make sure the Python header files and PostgreSQL client and server header -files are installed. These come usually with the "devel" packages on Unix -systems and the installer executables on Windows systems. - -If you are using a precompiled PostgreSQL, you will also need the pg_config -tool. This is usually also part of the "devel" package on Unix, and will be -installed as part of the database server feature on Windows systems. - -Building and installing with Distutils --------------------------------------- - -You can build and install PyGreSQL using -`Distutils `_. - -Download and unpack the PyGreSQL source tarball if you haven't already done so. - -Type the following commands to build and install PyGreSQL:: - - python setup.py build - python setup.py install - -If you are using `MinGW `_ to build PyGreSQL under -Microsoft Windows, please note that Python newer version 2.3 is using msvcr71 -instead of msvcrt as its common runtime library. You can allow for that by -editing the file ``%MinGWpath%/lib/gcc/%MinGWversion%/specs`` and changing -the entry that reads ``-lmsvcrt`` to ``-lmsvcr71``. You may also need to copy -``libpq.lib`` to ``libpq.a`` in the PostgreSQL ``lib`` directory. Then use -the following command to build and install PyGreSQL:: - - python setup.py build -c mingw32 install - -Now you should be ready to use PyGreSQL. - -Compiling Manually ------------------- - -The source file for compiling the dynamic module is called pgmodule.c. -You have two options. You can compile PyGreSQL as a stand-alone module -or you can build it into the Python interpreter. - -Stand-Alone ------------ - -* In the directory containing ``pgmodule.c``, run the following command:: - - cc -fpic -shared -o _pg.so -I$PYINC -I$PGINC -I$PSINC -L$PGLIB -lpq pgmodule.c - - where you have to set:: - - PYINC = path to the Python include files - (usually something like /usr/include/python) - PGINC = path to the PostgreSQL client include files - (something like /usr/include/pgsql or /usr/include/postgresql) - PSINC = path to the PostgreSQL server include files - (like /usr/include/pgsql/server or /usr/include/postgresql/server) - PGLIB = path to the PostgreSQL object code libraries (usually /usr/lib) - - If you are not sure about the above paths, try something like:: - - PYINC=`find /usr -name Python.h` - PGINC=`find /usr -name libpq-fe.h` - PSINC=`find /usr -name postgres.h` - PGLIB=`find /usr -name libpq.so` - - If you have the ``pg_config`` tool installed, you can set:: - - PGINC=`pg_config --includedir` - PSINC=`pg_config --includedir-server` - PGLIB=`pg_config --libdir` - - Some options may be added to this line:: - - -DNO_DEF_VAR no default variables support - -DNO_DIRECT no direct access methods - -DNO_LARGE no large object support - -DNO_PQSOCKET if running an older PostgreSQL - - On some systems you may need to include ``-lcrypt`` in the list of libraries - to make it compile. - -* Test the new module. Something like the following should work:: - - $ python - - >>> import _pg - >>> db = _pg.connect('thilo','localhost') - >>> db.query("INSERT INTO test VALUES ('ping','pong')") - 18304 - >>> db.query("SELECT * FROM test") - eins|zwei - ----+---- - ping|pong - (1 row) - -* Finally, move the ``_pg.so``, ``pg.py``, and ``pgdb.py`` to a directory in - your ``PYTHONPATH``. A good place would be ``/usr/lib/python/site-packages`` - if your Python modules are in ``/usr/lib/python``. - -Built-in to Python interpreter ------------------------------- - -* Find the directory where your ``Setup`` file lives (usually in the ``Modules`` - subdirectory) in the Python source hierarchy and copy or symlink the - ``pgmodule.c`` file there. - -* Add the following line to your 'Setup' file:: - - _pg pgmodule.c -I$PGINC -I$PSINC -L$PGLIB -lpq - - where:: - - PGINC = path to the PostgreSQL client include files (see above) - PSINC = path to the PostgreSQL server include files (see above) - PGLIB = path to the PostgreSQL object code libraries (see above) - - Some options may be added to this line:: - - -DNO_DEF_VAR no default variables support - -DNO_DIRECT no direct access methods - -DNO_LARGE no large object support - -DNO_PQSOCKET if running an older PostgreSQL (see above) - - On some systems you may need to include ``-lcrypt`` in the list of libraries - to make it compile. - -* If you want a shared module, make sure that the ``shared`` keyword is - uncommented and add the above line below it. You used to need to install - your shared modules with ``make sharedinstall`` but this no longer seems - to be true. - -* Copy ``pg.py`` to the lib directory where the rest of your modules are. - For example, that's ``/usr/local/lib/Python`` on my system. - -* Rebuild Python from the root directory of the Python source hierarchy by - running ``make -f Makefile.pre.in boot`` and ``make && make install``. - -* For more details read the documentation at the top of ``Makefile.pre.in``. diff --git a/docs/install.txt b/docs/install.txt index eb85bf0a..cc7fb0fd 100644 --- a/docs/install.txt +++ b/docs/install.txt @@ -1,13 +1,8 @@ -===================== -PyGreSQL Installation -===================== - -.. sectnum:: -.. contents:: Contents - +Installation +============ General -======= +------- You must first have installed Python and PostgreSQL on your system. If you want to access remote database only, you don't need to install @@ -26,7 +21,7 @@ directory. To uninstall PyGreSQL, simply remove these three files again. Installing from a Binary Distribution -===================================== +------------------------------------- This is the easiest way to install PyGreSQL. @@ -40,7 +35,7 @@ Note that the documentation is currently only included in the source package. Installing from Source -====================== +---------------------- If you want to install PyGreSQL from Source, or there is no binary package available for your platform, follow these instructions. diff --git a/docs/interface.rst b/docs/interface.txt similarity index 100% rename from docs/interface.rst rename to docs/interface.txt diff --git a/docs/introduction.rst b/docs/introduction.txt similarity index 100% rename from docs/introduction.rst rename to docs/introduction.txt diff --git a/docs/mailinglist.rst b/docs/mailinglist.txt similarity index 100% rename from docs/mailinglist.rst rename to docs/mailinglist.txt diff --git a/docs/pg.txt b/docs/pg.txt index 444bb22f..fa130ca0 100644 --- a/docs/pg.txt +++ b/docs/pg.txt @@ -384,7 +384,7 @@ Description: This function can be used to specify the Python class that shall be used by PyGreSQL to hold PostgreSQL numeric values. The default class is decimal.Decimal if available, otherwise the float type is used. - + set_namedresult -- set a function that will convert to named tuples ------------------------------------------------------------------- Syntax:: @@ -396,7 +396,7 @@ Parameters: Description: You can use this if you want to create different kinds of named tuples. - + Module constants ---------------- diff --git a/docs/pgdb.txt b/docs/pgdb.rst similarity index 100% rename from docs/pgdb.txt rename to docs/pgdb.rst diff --git a/docs/svn.rst b/docs/svn.txt similarity index 100% rename from docs/svn.rst rename to docs/svn.txt From 6bcc4420bf8967ff590e141c48ba3be01d0a3609 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 26 Nov 2015 20:03:45 +0000 Subject: [PATCH 017/144] Update the documentation --- docs/changelog.txt | 4 +- docs/classic.txt | 124 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 124 insertions(+), 4 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 82b8692e..4799fae6 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -6,8 +6,8 @@ Version 4.2 - Set a better default for the user option "escaping-funcs". - Greatly improve unit testing. - Force build to compile with no errors. -- Fix decimal point handling -- Add option to return money as string +- Fix decimal point handling. +- Add option to return boolean values as bool objects. - Fix notification handler (Thanks Patrick TJ McPhee). Version 4.1.1 (2013-01-08) diff --git a/docs/classic.txt b/docs/classic.txt index 0cd89872..bd1f6855 100644 --- a/docs/classic.txt +++ b/docs/classic.txt @@ -368,6 +368,25 @@ Example:: "select img from pictures where name='Garfield'").getresult[0][0]) file('garfield.gif', 'wb').write(picture) +get_decimal +----------- +get the decimal type to be used for numeric values + +Syntax:: + + get_decimal() + +Parameters: + None + +Return type: + :cls: the Python class used for PostgreSQL numeric values + +Description: + This function returns the Python class that is used by PyGreSQL to hold + PostgreSQL numeric values. The default class is decimal.Decimal if + available, otherwise the float type is used. + set_decimal ----------- set a decimal type to be used for numeric values @@ -383,7 +402,107 @@ Description: This function can be used to specify the Python class that shall be used by PyGreSQL to hold PostgreSQL numeric values. The default class is decimal.Decimal if available, otherwise the float type is used. - + +get_decimal_point +----------------- +get the decimal mark used for monetary values + +Syntax:: + + get_decimal_point() + +Parameters: + None + +Return type: + :str: string with one character representing the decimal mark + +Description: + This function returns the decimal mark used by PyGreSQL to interpret + PostgreSQL monetary values when converting them to decimal numbers. + The default setting is '.' as a decimal point. This setting is not + adapted automatically to the locale used by PostGreSQL, but you can + use `set_decimal()` to set a different decimal mark manually. A return + value of `None` means monetary values are not interpreted as decimal + numbers, but returned as strings including the formatting and currency. + +set_decimal_point +----------------- +specify which decimal mark is used for interpreting monetary values + +Syntax:: + + set_decimal_point(str) + +Parameters: + :str: string with one character representing the decimal mark + +Description: + This function can be used to specify the decimal mark used by PyGreSQL + to interpret PostgreSQL monetary values. The default value is '.' as + a decimal point. This value is not adapted automatically to the locale + used by PostGreSQL, so if you are dealing with a database set to a + locale that uses a ',' instead of '.' as the decimal point, then you + need to call `set_decimal(',')` to have PyGreSQL interpret monetary + values correctly. If you don't want money values to be converted to + decimal numbers, then you can call `set_decimal(None)`, which will + cause PyGreSQL to return monetary values as strings including their + formatting and currency. + +get_bool +-------- +check whether boolean values are returned as bool objects + +Syntax:: + + get_bool() + +Parameters: + None + +Return type: + :bool: whether or not bool objects will be returned + +Description: + This function checks whether PyGreSQL returns PostgreSQL boolean + values converted to Python bool objects, or as 'f' and 't' strings + which are the values used internally by PostgreSQL. By default, + conversion to bool objects is not activated, but you can enable + this with the `set_bool()` method. + +set_bool +-------- +set whether boolean values are returned as bool objects + +Syntax:: + + set_bool(bool) + +Parameters: + :bool: whether or not bool objects shall be returned + +Description: + This function can be used to specify whether PyGreSQL shall return + PostgreSQL boolean values converted to Python bool objects, or as + 'f' and 't' strings which are the values used internally by PostgreSQL. + By default, conversion to bool objects is not activated, but you can + enable this by calling `set_bool(True)`. + +get_namedresult +--------------- +set a function that will convert to named tuples + +Syntax:: + + set_namedresult() + +Parameters: + None + +Description: + This function returns the function used by PyGreSQL to construct the + result of the `query.namedresult()` method. + set_namedresult --------------- set a function that will convert to named tuples @@ -396,7 +515,8 @@ Parameters: :func: the function to be used to convert results to named tuples Description: - You can use this if you want to create different kinds of named tuples. + You can use this if you want to create different kinds of named tuples + returned by the `query.namedresult()` method. Module constants From a89a46a190e487d4ec8e9392c5c2cf979fdfad96 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 26 Nov 2015 20:32:19 +0000 Subject: [PATCH 018/144] Backport of recently added features to 4.x Ported the support for bool objects and the additional getter functions back from the trunk to 4.x. The bool object support is disabled by support, so we stay fully backward compatible. The docs for these features have already been taken over. --- module/TEST_PyGreSQL_classic_connection.py | 46 +++-- module/TEST_PyGreSQL_dbapi20.py | 19 ++ module/pgmodule.c | 200 ++++++++++++++++----- 3 files changed, 205 insertions(+), 60 deletions(-) diff --git a/module/TEST_PyGreSQL_classic_connection.py b/module/TEST_PyGreSQL_classic_connection.py index 33e8807f..8bdd0387 100755 --- a/module/TEST_PyGreSQL_classic_connection.py +++ b/module/TEST_PyGreSQL_classic_connection.py @@ -519,26 +519,34 @@ def testQueryWithNoneParam(self): self.assertEqual(self.c.query("select $1::text", [None] ).getresult(), [(None,)]) - def testQueryWithBoolParams(self): + def testQueryWithBoolParams(self, use_bool=None): query = self.c.query - self.assertEqual(query("select false").getresult(), [('f',)]) - self.assertEqual(query("select true").getresult(), [('t',)]) - self.assertEqual(query("select $1::bool", (None,)).getresult(), - [(None,)]) - self.assertEqual(query("select $1::bool", ('f',)).getresult(), [('f',)]) - self.assertEqual(query("select $1::bool", ('t',)).getresult(), [('t',)]) - self.assertEqual(query("select $1::bool", ('false',)).getresult(), - [('f',)]) - self.assertEqual(query("select $1::bool", ('true',)).getresult(), - [('t',)]) - self.assertEqual(query("select $1::bool", ('n',)).getresult(), [('f',)]) - self.assertEqual(query("select $1::bool", ('y',)).getresult(), [('t',)]) - self.assertEqual(query("select $1::bool", (0,)).getresult(), [('f',)]) - self.assertEqual(query("select $1::bool", (1,)).getresult(), [('t',)]) - self.assertEqual(query("select $1::bool", (False,)).getresult(), - [('f',)]) - self.assertEqual(query("select $1::bool", (True,)).getresult(), - [('t',)]) + if use_bool is not None: + use_bool_default = pg.get_bool() + pg.set_bool(use_bool) + try: + v_false, v_true = (False, True) if use_bool else 'ft' + r_false, r_true = [(v_false,)], [(v_true,)] + self.assertEqual(query("select false").getresult(), r_false) + self.assertEqual(query("select true").getresult(), r_true) + q = "select $1::bool" + self.assertEqual(query(q, (None,)).getresult(), [(None,)]) + self.assertEqual(query(q, ('f',)).getresult(), r_false) + self.assertEqual(query(q, ('t',)).getresult(), r_true) + self.assertEqual(query(q, ('false',)).getresult(), r_false) + self.assertEqual(query(q, ('true',)).getresult(), r_true) + self.assertEqual(query(q, ('n',)).getresult(), r_false) + self.assertEqual(query(q, ('y',)).getresult(), r_true) + self.assertEqual(query(q, (0,)).getresult(), r_false) + self.assertEqual(query(q, (1,)).getresult(), r_true) + self.assertEqual(query(q, (False,)).getresult(), r_false) + self.assertEqual(query(q, (True,)).getresult(), r_true) + finally: + if use_bool is not None: + pg.set_bool(use_bool_default) + + def testQueryWithBoolParamsAndUseBool(self): + self.testQueryWithBoolParams(use_bool=True) def testQueryWithIntParams(self): query = self.c.query diff --git a/module/TEST_PyGreSQL_dbapi20.py b/module/TEST_PyGreSQL_dbapi20.py index 2d1ecc53..8c29ca63 100755 --- a/module/TEST_PyGreSQL_dbapi20.py +++ b/module/TEST_PyGreSQL_dbapi20.py @@ -156,6 +156,25 @@ def test_float(self): else: self.assertEqual(inval, outval) + def test_bool(self): + values = [False, True, None, 't', 'f', 'true', 'false'] + table = self.table_prefix + 'booze' + con = self._connect() + try: + cur = con.cursor() + cur.execute( + "create table %s (n smallint, booltest bool)" % table) + params = enumerate(values) + cur.executemany("insert into %s values (%%s,%%s)" % table, params) + cur.execute("select * from %s order by 1" % table) + rows = cur.fetchall() + finally: + con.close() + rows = [row[1] for row in rows] + values[3] = values[5] = True + values[4] = values[6] = False + self.assertEqual(rows, values) + def test_set_decimal_type(self): decimal_type = pgdb.decimal_type() self.assert_(decimal_type is not None and callable(decimal_type)) diff --git a/module/pgmodule.c b/module/pgmodule.c index ebb0dd62..9774300f 100644 --- a/module/pgmodule.c +++ b/module/pgmodule.c @@ -120,7 +120,7 @@ int *get_type_array(PGresult *result, int nfields); static PyObject *decimal = NULL, /* decimal type */ *namedresult = NULL; /* function for getting named results */ static char decimal_point = '.'; /* decimal point used in money values */ - +static int use_bool = 0; /* whether or not bool objects shall be returned */ /* --------------------------------------------------------------------- */ /* OBJECTS DECLARATION */ @@ -337,6 +337,16 @@ check_source_obj(pgsourceobject *self, int level) return 1; } +/* define internal types */ + +#define PYGRES_INT 1 +#define PYGRES_LONG 2 +#define PYGRES_FLOAT 3 +#define PYGRES_DECIMAL 4 +#define PYGRES_MONEY 5 +#define PYGRES_BOOL 6 +#define PYGRES_DEFAULT 7 + /* shared functions for converting PG types to Python types */ int * get_type_array(PGresult *result, int nfields) @@ -357,28 +367,32 @@ get_type_array(PGresult *result, int nfields) case INT2OID: case INT4OID: case OIDOID: - typ[j] = 1; + typ[j] = PYGRES_INT; break; case INT8OID: - typ[j] = 2; + typ[j] = PYGRES_LONG; break; case FLOAT4OID: case FLOAT8OID: - typ[j] = 3; + typ[j] = PYGRES_FLOAT; break; case NUMERICOID: - typ[j] = 4; + typ[j] = PYGRES_DECIMAL; break; case CASHOID: - typ[j] = 5; + typ[j] = PYGRES_MONEY; + break; + + case BOOLOID: + typ[j] = PYGRES_BOOL; break; default: - typ[j] = 6; + typ[j] = PYGRES_DEFAULT; break; } } @@ -2155,21 +2169,21 @@ pgquery_getresult(pgqueryobject *self, PyObject *args) else switch (typ[j]) { - case 1: /* int2/4 */ + case PYGRES_INT: val = PyInt_FromString(s, NULL, 10); break; - case 2: /* int8 */ + case PYGRES_LONG: val = PyLong_FromString(s, NULL, 10); break; - case 3: /* float/double */ + case PYGRES_FLOAT: tmp_obj = PyString_FromString(s); val = PyFloat_FromString(tmp_obj, NULL); Py_DECREF(tmp_obj); break; - case 5: /* money */ + case PYGRES_MONEY: /* convert to decimal only if decimal point is set */ if (!decimal_point) goto default_case; for (k = 0; @@ -2185,9 +2199,9 @@ pgquery_getresult(pgqueryobject *self, PyObject *args) } cashbuf[k] = 0; s = cashbuf; + /* FALLTHROUGH */ /* no break */ - /* FALLTHROUGH */ /* no break */ - case 4: /* numeric */ + case PYGRES_DECIMAL: if (decimal) { tmp_obj = Py_BuildValue("(s)", s); @@ -2201,6 +2215,16 @@ pgquery_getresult(pgqueryobject *self, PyObject *args) Py_DECREF(tmp_obj); break; + case PYGRES_BOOL: + /* convert to bool only if bool_type is set */ + if (use_bool) + { + val = *s == 't' ? Py_True : Py_False; + Py_INCREF(val); + break; + } + /* FALLTHROUGH */ /* no break */ + default: default_case: val = PyString_FromString(s); @@ -2285,21 +2309,21 @@ pgquery_dictresult(pgqueryobject *self, PyObject *args) else switch (typ[j]) { - case 1: /* int2/4 */ + case PYGRES_INT: val = PyInt_FromString(s, NULL, 10); break; - case 2: /* int8 */ + case PYGRES_LONG: val = PyLong_FromString(s, NULL, 10); break; - case 3: /* float/double */ + case PYGRES_FLOAT: tmp_obj = PyString_FromString(s); val = PyFloat_FromString(tmp_obj, NULL); Py_DECREF(tmp_obj); break; - case 5: /* money */ + case PYGRES_MONEY: /* convert to decimal only if decimal point is set */ if (!decimal_point) goto default_case; @@ -2316,9 +2340,9 @@ pgquery_dictresult(pgqueryobject *self, PyObject *args) } cashbuf[k] = 0; s = cashbuf; + /* FALLTHROUGH */ /* no break */ - /* FALLTHROUGH */ /* no break */ - case 4: /* numeric */ + case PYGRES_DECIMAL: if (decimal) { tmp_obj = Py_BuildValue("(s)", s); @@ -2332,6 +2356,16 @@ pgquery_dictresult(pgqueryobject *self, PyObject *args) Py_DECREF(tmp_obj); break; + case PYGRES_BOOL: + /* convert to bool only if bool_type is set */ + if (use_bool) + { + val = *s == 't' ? Py_True : Py_False; + Py_INCREF(val); + break; + } + /* FALLTHROUGH */ /* no break */ + default: default_case: val = PyString_FromString(s); @@ -3700,9 +3734,38 @@ static PyObject return ret; } +/* get decimal point */ +static char get_decimal_point__doc__[] = +"get_decimal_point() -- get decimal point to be used for money values."; + +static PyObject * +get_decimal_point(PyObject *self, PyObject * args) +{ + PyObject *ret = NULL; + char s[2]; + + if (PyArg_ParseTuple(args, "")) + { + if (decimal_point) + { + s[0] = decimal_point; s[1] = '\0'; + ret = PyString_FromString(s); + } else { + Py_INCREF(Py_None); ret = Py_None; + } + } + else + { + PyErr_SetString(PyExc_TypeError, + "get_decimal_point() takes no parameter"); + } + + return ret; +} + /* set decimal point */ static char set_decimal_point__doc__[] = -"set_decimal_point() -- set decimal point to be used for money values."; +"set_decimal_point(char) -- set decimal point to be used for money values."; static PyObject * set_decimal_point(PyObject *self, PyObject * args) @@ -3729,35 +3792,25 @@ set_decimal_point(PyObject *self, PyObject * args) return ret; } -/* get decimal point */ -static char get_decimal_point__doc__[] = -"get_decimal_point() -- get decimal point to be used for money values."; +/* get decimal type */ +static char get_decimal__doc__[] = +"get_decimal() -- set a decimal type to be used for numeric values."; static PyObject * -get_decimal_point(PyObject *self, PyObject * args) +get_decimal(PyObject *self, PyObject *args) { PyObject *ret = NULL; - char s[2]; if (PyArg_ParseTuple(args, "")) { - if (decimal_point) { - s[0] = decimal_point; s[1] = '\0'; - ret = PyString_FromString(s); - } else { - Py_INCREF(Py_None); ret = Py_None; - } - } - else - { - PyErr_SetString(PyExc_TypeError, - "get_decimal_point() takes no parameter"); + ret = decimal ? decimal : Py_None; + Py_INCREF(ret); } return ret; } -/* set decimal */ +/* set decimal type */ static char set_decimal__doc__[] = "set_decimal(cls) -- set a decimal type to be used for numeric values."; @@ -3780,12 +3833,70 @@ set_decimal(PyObject *self, PyObject *args) Py_INCREF(Py_None); ret = Py_None; } else - PyErr_SetString(PyExc_TypeError, "decimal type must be None or callable"); + PyErr_SetString(PyExc_TypeError, + "decimal type must be None or callable"); + } + + return ret; +} + +/* get usage of bool values */ +static char get_bool__doc__[] = +"get_bool() -- check whether boolean values are converted to bool."; + +static PyObject * +get_bool(PyObject *self, PyObject * args) +{ + PyObject *ret = NULL; + + if (PyArg_ParseTuple(args, "")) + { + ret = use_bool ? Py_True : Py_False; + Py_INCREF(ret); + } + + return ret; +} + +/* set usage of bool values */ +static char set_bool__doc__[] = +"set_bool(bool) -- set whether boolean values should be converted to bool."; + +static PyObject * +set_bool(PyObject *self, PyObject * args) +{ + PyObject *ret = NULL; + int i; + + /* gets arguments */ + if (PyArg_ParseTuple(args, "i", &i)) + { + use_bool = i ? 1 : 0; + Py_INCREF(Py_None); ret = Py_None; + } + + return ret; +} + +/* get named result factory */ +static char get_namedresult__doc__[] = +"get_namedresult(cls) -- get the function used for getting named results."; + +static PyObject * +get_namedresult(PyObject *self, PyObject *args) +{ + PyObject *ret = NULL; + + if (PyArg_ParseTuple(args, "")) + { + ret = namedresult ? namedresult : Py_None; + Py_INCREF(ret); } + return ret; } -/* set named result */ +/* set named result factory */ static char set_namedresult__doc__[] = "set_namedresult(cls) -- set a function to be used for getting named results."; @@ -3805,6 +3916,7 @@ set_namedresult(PyObject *self, PyObject *args) else PyErr_SetString(PyExc_TypeError, "parameter must be callable"); } + return ret; } @@ -4159,12 +4271,18 @@ static struct PyMethodDef pg_methods[] = { escape_bytea__doc__}, {"unescape_bytea", (PyCFunction) unescape_bytea, METH_VARARGS, unescape_bytea__doc__}, - {"set_decimal_point", (PyCFunction) set_decimal_point, METH_VARARGS, - set_decimal_point__doc__}, {"get_decimal_point", (PyCFunction) get_decimal_point, METH_VARARGS, get_decimal_point__doc__}, + {"set_decimal_point", (PyCFunction) set_decimal_point, METH_VARARGS, + set_decimal_point__doc__}, + {"get_decimal", (PyCFunction) get_decimal, METH_VARARGS, + get_decimal__doc__}, {"set_decimal", (PyCFunction) set_decimal, METH_VARARGS, set_decimal__doc__}, + {"get_bool", (PyCFunction) get_bool, METH_VARARGS, get_bool__doc__}, + {"set_bool", (PyCFunction) set_bool, METH_VARARGS, set_bool__doc__}, + {"get_namedresult", (PyCFunction) get_namedresult, METH_VARARGS, + get_namedresult__doc__}, {"set_namedresult", (PyCFunction) set_namedresult, METH_VARARGS, set_namedresult__doc__}, From e428c6d015f2693f81827ae03171cb2ae2ef5991 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 26 Nov 2015 22:27:55 +0000 Subject: [PATCH 019/144] Move all tests into a separate directory This makes the test modules a bit more clearly arranged and standard. You can run the whole test suite with "python setup.py test" or with "python -m unittest discover". New tests modules will be automatically found if they are saved as tests/test_*.py. You can run individual tests with one of these commands: python setup.py test -s tests.test_classic_functions python tests/test_classic_functions.py python -m unittest tests.test_classic_functions --- module/setup.py | 1 + module/tests/__init__.py | 13 +++++++++++++ module/{ => tests}/dbapi20.py | 0 .../test_classic.py} | 0 .../test_classic_connection.py} | 0 .../test_classic_dbwrapper.py} | 0 .../test_classic_functions.py} | 0 .../test_classic_largeobj.py} | 0 .../test_dbapi20.py} | 0 9 files changed, 14 insertions(+) create mode 100644 module/tests/__init__.py rename module/{ => tests}/dbapi20.py (100%) rename module/{TEST_PyGreSQL_classic.py => tests/test_classic.py} (100%) rename module/{TEST_PyGreSQL_classic_connection.py => tests/test_classic_connection.py} (100%) rename module/{TEST_PyGreSQL_classic_dbwrapper.py => tests/test_classic_dbwrapper.py} (100%) rename module/{TEST_PyGreSQL_classic_functions.py => tests/test_classic_functions.py} (100%) rename module/{TEST_PyGreSQL_classic_largeobj.py => tests/test_classic_largeobj.py} (100%) rename module/{TEST_PyGreSQL_dbapi20.py => tests/test_dbapi20.py} (100%) diff --git a/module/setup.py b/module/setup.py index 93b4866c..3c34a3fa 100755 --- a/module/setup.py +++ b/module/setup.py @@ -178,6 +178,7 @@ def finalize_options(self): libraries=libraries, extra_compile_args=extra_compile_args)], zip_safe=False, cmdclass=dict(build_ext=build_pg_ext), + test_suite='tests.discover', classifiers=[ "Development Status :: 6 - Mature", "Intended Audience :: Developers", diff --git a/module/tests/__init__.py b/module/tests/__init__.py new file mode 100644 index 00000000..9f86b1fb --- /dev/null +++ b/module/tests/__init__.py @@ -0,0 +1,13 @@ +"""PyGreSQL test suite. + +You can specify your local database settings in LOCAL_PyGreSQL.py. + +""" + +import unittest + + +def discover(): + loader = unittest.TestLoader() + suite = loader.discover('.') + return suite \ No newline at end of file diff --git a/module/dbapi20.py b/module/tests/dbapi20.py similarity index 100% rename from module/dbapi20.py rename to module/tests/dbapi20.py diff --git a/module/TEST_PyGreSQL_classic.py b/module/tests/test_classic.py similarity index 100% rename from module/TEST_PyGreSQL_classic.py rename to module/tests/test_classic.py diff --git a/module/TEST_PyGreSQL_classic_connection.py b/module/tests/test_classic_connection.py similarity index 100% rename from module/TEST_PyGreSQL_classic_connection.py rename to module/tests/test_classic_connection.py diff --git a/module/TEST_PyGreSQL_classic_dbwrapper.py b/module/tests/test_classic_dbwrapper.py similarity index 100% rename from module/TEST_PyGreSQL_classic_dbwrapper.py rename to module/tests/test_classic_dbwrapper.py diff --git a/module/TEST_PyGreSQL_classic_functions.py b/module/tests/test_classic_functions.py similarity index 100% rename from module/TEST_PyGreSQL_classic_functions.py rename to module/tests/test_classic_functions.py diff --git a/module/TEST_PyGreSQL_classic_largeobj.py b/module/tests/test_classic_largeobj.py similarity index 100% rename from module/TEST_PyGreSQL_classic_largeobj.py rename to module/tests/test_classic_largeobj.py diff --git a/module/TEST_PyGreSQL_dbapi20.py b/module/tests/test_dbapi20.py similarity index 100% rename from module/TEST_PyGreSQL_dbapi20.py rename to module/tests/test_dbapi20.py From 043a19f2839fa7fa9e3ed2f375a25d7a59c7a061 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 26 Nov 2015 22:43:29 +0000 Subject: [PATCH 020/144] Remove surplus quote marks in docstrings Also, backport reduction of some tests to the 4x. branch. These tests are dependent on the current state of the last opened database connection, so they cannot be tested without opening a database connection and resetting the state. --- module/tests/test_classic_connection.py | 12 ++++++------ module/tests/test_classic_dbwrapper.py | 6 +++--- module/tests/test_classic_functions.py | 18 +++++++++++------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/module/tests/test_classic_connection.py b/module/tests/test_classic_connection.py index 8bdd0387..e03448ae 100755 --- a/module/tests/test_classic_connection.py +++ b/module/tests/test_classic_connection.py @@ -62,7 +62,7 @@ def testCanConnect(self): class TestConnectObject(unittest.TestCase): - """"Test existence of basic pg connection methods.""" + """Test existence of basic pg connection methods.""" def setUp(self): self.connection = connect() @@ -222,7 +222,7 @@ def testMethodFileNo(self): class TestSimpleQueries(unittest.TestCase): - """"Test simple queries via a basic pg connection.""" + """Test simple queries via a basic pg connection.""" def setUp(self): self.c = connect() @@ -505,7 +505,7 @@ def testPrint(self): class TestParamQueries(unittest.TestCase): - """"Test queries with parameters via a basic pg connection.""" + """Test queries with parameters via a basic pg connection.""" def setUp(self): self.c = connect() @@ -643,7 +643,7 @@ def testUnicodeQuery(self): class TestInserttable(unittest.TestCase): - """"Test inserttable method.""" + """Test inserttable method.""" @classmethod def setUpClass(cls): @@ -768,7 +768,7 @@ def testInserttableMaxValues(self): class TestDirectSocketAccess(unittest.TestCase): - """"Test copy command with direct socket access.""" + """Test copy command with direct socket access.""" @classmethod def setUpClass(cls): @@ -834,7 +834,7 @@ def testParameterChecks(self): class TestNotificatons(unittest.TestCase): - """"Test notification support.""" + """Test notification support.""" def setUp(self): self.c = connect() diff --git a/module/tests/test_classic_dbwrapper.py b/module/tests/test_classic_dbwrapper.py index 979bd813..cb23269e 100755 --- a/module/tests/test_classic_dbwrapper.py +++ b/module/tests/test_classic_dbwrapper.py @@ -47,7 +47,7 @@ def DB(): class TestDBClassBasic(unittest.TestCase): - """"Test existence of the DB class wrapped pg connection methods.""" + """Test existence of the DB class wrapped pg connection methods.""" def setUp(self): self.db = DB() @@ -261,7 +261,7 @@ class DB2: class TestDBClass(unittest.TestCase): - """"Test the methods of the DB class wrapped pg connection.""" + """Test the methods of the DB class wrapped pg connection.""" @classmethod def setUpClass(cls): @@ -1023,7 +1023,7 @@ def testDebugWithCallable(self): class TestSchemas(unittest.TestCase): - """"Test correct handling of schemas (namespaces).""" + """Test correct handling of schemas (namespaces).""" @classmethod def setUpClass(cls): diff --git a/module/tests/test_classic_functions.py b/module/tests/test_classic_functions.py index 52d3296c..e5037d5a 100755 --- a/module/tests/test_classic_functions.py +++ b/module/tests/test_classic_functions.py @@ -255,25 +255,29 @@ def testDefBase(self): class TestEscapeFunctions(unittest.TestCase): - """"Test pg escape and unescape functions.""" + """Test pg escape and unescape functions. + + The libpq interface memorizes some parameters of the last opened + connection that influence the result of these functions. + Therefore we cannot do rigid tests of these functions here. + We leave this for the test module that runs with a database. + + """ def testEscapeString(self): f = pg.escape_string self.assertEqual(f('plain'), 'plain') - self.assertEqual(f("that's k\xe4se"), "that''s k\xe4se") - self.assertEqual(f(r"It's fine to have a \ inside."), - r"It''s fine to have a \\ inside.") + self.assertEqual(f("that's cheese"), "that''s cheese") def testEscapeBytea(self): f = pg.escape_bytea self.assertEqual(f('plain'), 'plain') - self.assertEqual(f("that's k\xe4se"), "that''s k\\\\344se") - self.assertEqual(f('O\x00ps\xff!'), r'O\\000ps\\377!') + self.assertEqual(f("that's cheese"), "that''s cheese") def testUnescapeBytea(self): f = pg.unescape_bytea self.assertEqual(f('plain'), 'plain') - self.assertEqual(f("that's k\\344se"), "that's k\xe4se") + self.assertEqual(f("das is' k\\303\\244se"), "das is' käse") self.assertEqual(f(r'O\000ps\377!'), 'O\x00ps\xff!') From 17019f7dd4371cdc2fd883391d8a3d6a4f65cf42 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 27 Nov 2015 12:39:21 +0000 Subject: [PATCH 021/144] Skip test if German locale cannot be set --- module/tests/test_classic_connection.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/module/tests/test_classic_connection.py b/module/tests/test_classic_connection.py index e03448ae..af9a9e84 100755 --- a/module/tests/test_classic_connection.py +++ b/module/tests/test_classic_connection.py @@ -941,7 +941,10 @@ def testSetDecimalPoint(self): # check that money values can be interpreted correctly # if and only if the decimal point is set appropriately # for the current lc_monetary setting - query("set lc_monetary='en_US.UTF-8'") + try: + query("set lc_monetary='en_US.UTF-8'") + except pg.ProgrammingError: + self.skipTest("cannot set English money locale") pg.set_decimal_point(None) try: r = query("select '34.25'::money").getresult()[0][0] @@ -963,7 +966,10 @@ def testSetDecimalPoint(self): finally: pg.set_decimal_point(point) self.assertNotEqual(r, d('34.25')) - query("set lc_monetary='de_DE.UTF-8'") + try: + query("set lc_monetary='de_DE.UTF-8'") + except pg.ProgrammingError: + self.skipTest("cannot set German money locale") pg.set_decimal_point(None) try: r = query("select '34,25'::money").getresult()[0][0] From 43e5a8205661bc9e649fd226bd00098fcbc6e273 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 27 Nov 2015 12:56:09 +0000 Subject: [PATCH 022/144] Fix and backport some issues in the config tests --- module/tests/test_classic_connection.py | 287 +++++++++++++++++++----- 1 file changed, 237 insertions(+), 50 deletions(-) diff --git a/module/tests/test_classic_connection.py b/module/tests/test_classic_connection.py index af9a9e84..75c9294c 100755 --- a/module/tests/test_classic_connection.py +++ b/module/tests/test_classic_connection.py @@ -931,97 +931,280 @@ def tearDown(self): def testGetDecimalPoint(self): point = pg.get_decimal_point() + # error if a parameter is passed + self.assertRaises(TypeError, pg.get_decimal_point, point) self.assertIsInstance(point, str) - self.assertEqual(point, '.') + self.assertEqual(point, '.') # the default setting + pg.set_decimal_point(',') + try: + r = pg.get_decimal_point() + finally: + pg.set_decimal_point(point) + self.assertIsInstance(r, str) + self.assertEqual(r, ',') + pg.set_decimal_point("'") + try: + r = pg.get_decimal_point() + finally: + pg.set_decimal_point(point) + self.assertIsInstance(r, str) + self.assertEqual(r, "'") + pg.set_decimal_point('') + try: + r = pg.get_decimal_point() + finally: + pg.set_decimal_point(point) + self.assertIsNone(r) + pg.set_decimal_point(None) + try: + r = pg.get_decimal_point() + finally: + pg.set_decimal_point(point) + self.assertIsNone(r) def testSetDecimalPoint(self): d = pg.Decimal point = pg.get_decimal_point() + self.assertRaises(TypeError, pg.set_decimal_point) + # error if decimal point is not a string + self.assertRaises(TypeError, pg.set_decimal_point, 0) + # error if more than one decimal point passed + self.assertRaises(TypeError, pg.set_decimal_point, '.', ',') + self.assertRaises(TypeError, pg.set_decimal_point, '.,') + # error if decimal point is not a punctuation character + self.assertRaises(TypeError, pg.set_decimal_point, '0') query = self.c.query - # check that money values can be interpreted correctly - # if and only if the decimal point is set appropriately - # for the current lc_monetary setting + # check that money values are interpreted as decimal values + # only if decimal_point is set, and that the result is correct + # only if it is set suitable for the current lc_monetary setting + select_money = "select '34.25'::money" + proper_money = d(34.25) + bad_money = d(3425) + en_locales = 'en', 'en_US', 'en_US.utf8', 'en_US.UTF-8' + en_money = '$34.25', '$ 34.25', '34.25$', '34.25 $', '34.25 Dollar' + de_locales = 'de', 'de_DE', 'de_DE.utf8', 'de_DE.UTF-8' + de_money = ('34,25€', '34,25 €', '€34,25' '€ 34,25', + '34,25 EUR', '34,25 Euro', '34,25 DM') + # first try with English localization (using the point) + for lc in en_locales: + try: + query("set lc_monetary='%s'" % lc) + except pg.ProgrammingError: + pass + else: + break + else: + self.skipTest("cannot set English money locale") try: - query("set lc_monetary='en_US.UTF-8'") + r = query(select_money) except pg.ProgrammingError: - self.skipTest("cannot set English money locale") + # this can happen if the currency signs cannot be + # converted using the encoding of the test database + self.skipTest("database does not support English money") pg.set_decimal_point(None) try: - r = query("select '34.25'::money").getresult()[0][0] + r = r.getresult()[0][0] + finally: + pg.set_decimal_point(point) + self.assertIsInstance(r, str) + self.assertIn(r, en_money) + r = query(select_money) + pg.set_decimal_point('') + try: + r = r.getresult()[0][0] finally: pg.set_decimal_point(point) self.assertIsInstance(r, str) - self.assertIn(r, ( - '$34.25', '$ 34.25', '34.25$', '34.25 $', '34.25 Dollar')) + self.assertIn(r, en_money) + r = query(select_money) pg.set_decimal_point('.') try: - r = query("select '34.25'::money").getresult()[0][0] + r = r.getresult()[0][0] finally: pg.set_decimal_point(point) self.assertIsInstance(r, d) - self.assertEqual(r, d('34.25')) + self.assertEqual(r, proper_money) + r = query(select_money) pg.set_decimal_point(',') try: - r = query("select '34.25'::money").getresult()[0][0] + r = r.getresult()[0][0] finally: pg.set_decimal_point(point) - self.assertNotEqual(r, d('34.25')) + self.assertIsInstance(r, d) + self.assertEqual(r, bad_money) + r = query(select_money) + pg.set_decimal_point("'") try: - query("set lc_monetary='de_DE.UTF-8'") - except pg.ProgrammingError: + r = r.getresult()[0][0] + finally: + pg.set_decimal_point(point) + self.assertIsInstance(r, d) + self.assertEqual(r, bad_money) + # then try with German localization (using the comma) + for lc in de_locales: + try: + query("set lc_monetary='%s'" % lc) + except pg.ProgrammingError: + pass + else: + break + else: self.skipTest("cannot set German money locale") + select_money = select_money.replace('.', ',') + try: + r = query(select_money) + except pg.ProgrammingError: + self.skipTest("database does not support English money") pg.set_decimal_point(None) try: - r = query("select '34,25'::money").getresult()[0][0] + r = r.getresult()[0][0] finally: pg.set_decimal_point(point) self.assertIsInstance(r, str) - self.assertIn(r, ('34,25€', '34,25 €', '€34,25' '€ 34,25', - '34,25 EUR', '34,25 Euro', '34,25 DM')) + self.assertIn(r, de_money) + r = query(select_money) + pg.set_decimal_point('') + try: + r = r.getresult()[0][0] + finally: + pg.set_decimal_point(point) + self.assertIsInstance(r, str) + self.assertIn(r, de_money) + r = query(select_money) pg.set_decimal_point(',') try: - r = query("select '34,25'::money").getresult()[0][0] + r = r.getresult()[0][0] finally: pg.set_decimal_point(point) self.assertIsInstance(r, d) - self.assertEqual(r, d('34.25')) + self.assertEqual(r, proper_money) + r = query(select_money) + pg.set_decimal_point('.') try: - pg.set_decimal_point('.') + r = r.getresult()[0][0] finally: pg.set_decimal_point(point) - r = query("select '34,25'::money").getresult()[0][0] - self.assertNotEqual(r, d('34.25')) + self.assertEqual(r, bad_money) + r = query(select_money) + pg.set_decimal_point("'") + try: + r = r.getresult()[0][0] + finally: + pg.set_decimal_point(point) + self.assertEqual(r, bad_money) + + def testGetDecimal(self): + decimal_class = pg.get_decimal() + # error if a parameter is passed + self.assertRaises(TypeError, pg.get_decimal, decimal_class) + self.assertIs(decimal_class, pg.Decimal) # the default setting + pg.set_decimal(int) + try: + r = pg.get_decimal() + finally: + pg.set_decimal(decimal_class) + self.assertIs(r, int) + r = pg.get_decimal() + self.assertIs(r, decimal_class) def testSetDecimal(self): - d = pg.Decimal + decimal_class = pg.get_decimal() + # error if no parameter is passed + self.assertRaises(TypeError, pg.set_decimal) query = self.c.query - r = query("select 3425::numeric").getresult()[0][0] - self.assertIsInstance(r, d) - self.assertEqual(r, d('3425')) - pg.set_decimal(long) try: - r = query("select 3425::numeric").getresult()[0][0] + r = query("select 3425::numeric") + except pg.ProgrammingError: + self.skipTest('database does not support numeric') + r = r.getresult()[0][0] + self.assertIsInstance(r, decimal_class) + self.assertEqual(r, decimal_class('3425')) + r = query("select 3425::numeric") + pg.set_decimal(int) + try: + r = r.getresult()[0][0] + finally: + pg.set_decimal(decimal_class) + self.assertNotIsInstance(r, decimal_class) + self.assertIsInstance(r, int) + self.assertEqual(r, int(3425)) + + def testGetBool(self): + use_bool = pg.get_bool() + # error if a parameter is passed + self.assertRaises(TypeError, pg.get_bool, use_bool) + self.assertIsInstance(use_bool, bool) + self.assertIs(use_bool, False) # the default setting + pg.set_bool(True) + try: + r = pg.get_bool() finally: - pg.set_decimal(d) - self.assertNotIsInstance(r, d) - self.assertIsInstance(r, long) - self.assertEqual(r, 3425L) + pg.set_bool(use_bool) + self.assertIsInstance(r, bool) + self.assertIs(r, True) + pg.set_bool(False) + try: + r = pg.get_bool() + finally: + pg.set_bool(use_bool) + self.assertIsInstance(r, bool) + self.assertIs(r, False) + pg.set_bool(1) + try: + r = pg.get_bool() + finally: + pg.set_bool(use_bool) + self.assertIsInstance(r, bool) + self.assertIs(r, True) + pg.set_bool(0) + try: + r = pg.get_bool() + finally: + pg.set_bool(use_bool) + self.assertIsInstance(r, bool) + self.assertIs(r, False) - @unittest.skipUnless(namedtuple, 'Named tuples not available') - def testSetNamedresult(self): + def testSetBool(self): + use_bool = pg.get_bool() + # error if no parameter is passed + self.assertRaises(TypeError, pg.set_bool) query = self.c.query + try: + r = query("select true::bool") + except pg.ProgrammingError: + self.skipTest('database does not support bool') + r = r.getresult()[0][0] + self.assertIsInstance(r, str) + self.assertEqual(r, 't') + r = query("select true::bool") + pg.set_bool(True) + try: + r = r.getresult()[0][0] + finally: + pg.set_bool(use_bool) + self.assertIsInstance(r, bool) + self.assertIs(r, True) + r = query("select true::bool") + pg.set_bool(False) + try: + r = r.getresult()[0][0] + finally: + pg.set_bool(use_bool) + self.assertIsInstance(r, str) + self.assertIs(r, 't') - r = query("select 1 as x, 2 as y").namedresult()[0] - self.assertIsInstance(r, tuple) - self.assertEqual(r, (1, 2)) - self.assertIsNot(type(r), tuple) - self.assertEqual(r._fields, ('x', 'y')) - self.assertEqual(r._asdict(), {'x': 1, 'y': 2}) - self.assertEqual(r.__class__.__name__, 'Row') + def testGetNamedresult(self): + namedresult = pg.get_namedresult() + # error if a parameter is passed + self.assertRaises(TypeError, pg.get_namedresult, namedresult) + self.assertIs(namedresult, pg._namedresult) # the default setting - _namedresult = pg._namedresult - self.assertTrue(callable(_namedresult)) - pg.set_namedresult(_namedresult) + @unittest.skipUnless(namedtuple, 'Named tuples not available') + def testSetNamedresult(self): + namedresult = pg.get_namedresult() + self.assertTrue(callable(namedresult)) + + query = self.c.query r = query("select 1 as x, 2 as y").namedresult()[0] self.assertIsInstance(r, tuple) @@ -1031,12 +1214,13 @@ def testSetNamedresult(self): self.assertEqual(r._asdict(), {'x': 1, 'y': 2}) self.assertEqual(r.__class__.__name__, 'Row') - def _listresult(q): - return map(list, q.getresult()) - - pg.set_namedresult(_listresult) + def listresult(q): + return [list(row) for row in q.getresult()] + pg.set_namedresult(listresult) try: + r = pg.get_namedresult() + self.assertIs(r, listresult) r = query("select 1 as x, 2 as y").namedresult()[0] self.assertIsInstance(r, list) self.assertEqual(r, [1, 2]) @@ -1044,7 +1228,10 @@ def _listresult(q): self.assertFalse(hasattr(r, '_fields')) self.assertNotEqual(r.__class__.__name__, 'Row') finally: - pg.set_namedresult(_namedresult) + pg.set_namedresult(namedresult) + + r = pg.get_namedresult() + self.assertIs(r, namedresult) if __name__ == '__main__': From 55858915a40d9b598399ed37db20fea680ef4333 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 27 Nov 2015 13:10:58 +0000 Subject: [PATCH 023/144] Make tests compatible with Python 2.6 --- module/tests/__init__.py | 5 ++++- module/tests/test_classic_connection.py | 4 ++-- module/tests/test_classic_dbwrapper.py | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/module/tests/__init__.py b/module/tests/__init__.py index 9f86b1fb..d9db87db 100644 --- a/module/tests/__init__.py +++ b/module/tests/__init__.py @@ -4,7 +4,10 @@ """ -import unittest +try: + import unittest2 as unittest # for Python < 2.7 +except ImportError: + import unittest def discover(): diff --git a/module/tests/test_classic_connection.py b/module/tests/test_classic_connection.py index 75c9294c..eab988b1 100755 --- a/module/tests/test_classic_connection.py +++ b/module/tests/test_classic_connection.py @@ -978,8 +978,8 @@ def testSetDecimalPoint(self): # only if decimal_point is set, and that the result is correct # only if it is set suitable for the current lc_monetary setting select_money = "select '34.25'::money" - proper_money = d(34.25) - bad_money = d(3425) + proper_money = d('34.25') + bad_money = d('3425') en_locales = 'en', 'en_US', 'en_US.utf8', 'en_US.UTF-8' en_money = '$34.25', '$ 34.25', '34.25$', '34.25 $', '34.25 Dollar' de_locales = 'de', 'de_DE', 'de_DE.utf8', 'de_DE.UTF-8' diff --git a/module/tests/test_classic_dbwrapper.py b/module/tests/test_classic_dbwrapper.py index cb23269e..d655b387 100755 --- a/module/tests/test_classic_dbwrapper.py +++ b/module/tests/test_classic_dbwrapper.py @@ -707,7 +707,8 @@ def testInsert(self): dict(d=Decimal(0)), (dict(d=0), dict(d=Decimal(0))), dict(f4=None, f8=None), dict(f4=0, f8=0), (dict(f4='', f8=''), dict(f4=None, f8=None)), - dict(d=1234.5, f4=1234.5, f8=1234.5), + (dict(d=1234.5, f4=1234.5, f8=1234.5), + dict(d=Decimal('1234.5'))), dict(d=Decimal('123.456789'), f4=12.375, f8=123.4921875), dict(d=Decimal('123456789.9876543212345678987654321')), dict(m=None), (dict(m=''), dict(m=None)), From b3ee692ff45d5eba313b35e83dc5eacc6d9bb0ab Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 27 Nov 2015 15:22:56 +0000 Subject: [PATCH 024/144] Fixed small issue with large object When an object was already unlinked, getting its attributes would return without error, but leave an error indicator set. Also fixed a similar issue with the destructor. --- module/pgmodule.c | 6 +-- module/tests/test_classic_largeobj.py | 66 +++++++++++++++++---------- 2 files changed, 46 insertions(+), 26 deletions(-) diff --git a/module/pgmodule.c b/module/pgmodule.c index 9774300f..b8d9ccf0 100644 --- a/module/pgmodule.c +++ b/module/pgmodule.c @@ -1245,7 +1245,7 @@ pglarge_new(pgobject *pgcnx, Oid oid) static void pglarge_dealloc(pglargeobject *self) { - if (self->lo_fd >= 0 && check_cnx_obj(self->pgcnx)) + if (self->lo_fd >= 0 && self->pgcnx->valid) lo_close(self->pgcnx->cnx, self->lo_fd); Py_XDECREF(self->pgcnx); @@ -1612,7 +1612,7 @@ pglarge_getattr(pglargeobject *self, char *name) Py_INCREF(self->pgcnx); return (PyObject *) (self->pgcnx); } - + PyErr_Clear(); Py_INCREF(Py_None); return Py_None; } @@ -1622,7 +1622,7 @@ pglarge_getattr(pglargeobject *self, char *name) { if (check_lo_obj(self, 0)) return PyInt_FromLong(self->lo_oid); - + PyErr_Clear(); Py_INCREF(Py_None); return Py_None; } diff --git a/module/tests/test_classic_largeobj.py b/module/tests/test_classic_largeobj.py index 3cde2861..60bb7396 100755 --- a/module/tests/test_classic_largeobj.py +++ b/module/tests/test_classic_largeobj.py @@ -15,8 +15,9 @@ import unittest2 as unittest # for Python < 2.7 except ImportError: import unittest -import sys import tempfile +import os +import sys import pg # the module under test @@ -31,6 +32,8 @@ except ImportError: pass +windows = os.name == 'nt' + def connect(): """Create a basic pg connection to the test database.""" @@ -60,7 +63,7 @@ def setUp(self): self.c.query('begin') def tearDown(self): - self.c.query('end') + self.c.query('rollback') self.c.close() def assertIsLargeObject(self, obj): @@ -110,17 +113,30 @@ def testGetLo(self): large_object.unlink() finally: del large_object + self.assertIsInstance(r, str) self.assertEqual(r, data) def testLoImport(self): - f = tempfile.NamedTemporaryFile() + if windows: + # NamedTemporaryFiles don't work well here + fname = 'temp_test_pg_largeobj_import.txt' + f = open(fname, 'wb') + else: + f = tempfile.NamedTemporaryFile() + fname = f.name data = 'some data to be imported' f.write(data) - f.flush() - f.seek(0) + if windows: + f.close() + f = open(fname, 'rb') + else: + f.flush() + f.seek(0) large_object = self.c.loimport(f.name) try: f.close() + if windows: + os.remove(fname) self.assertIsLargeObject(large_object) large_object.open(pg.INV_READ) large_object.seek(0, pg.SEEK_SET) @@ -148,14 +164,17 @@ def tearDown(self): if self.obj.oid: try: self.obj.close() - except IOError: + except (SystemError, IOError): pass try: self.obj.unlink() - except IOError: + except (SystemError, IOError): pass del self.obj - self.pgcnx.query('end') + try: + self.pgcnx.query('rollback') + except SystemError: + pass self.pgcnx.close() def testOid(self): @@ -245,18 +264,23 @@ def testSeek(self): self.obj.open(pg.INV_READ) seek(0, pg.SEEK_SET) r = self.obj.read(9) + self.assertIsInstance(r, str) self.assertEqual(r, 'some data') seek(4, pg.SEEK_CUR) r = self.obj.read(2) + self.assertIsInstance(r, str) self.assertEqual(r, 'be') seek(-10, pg.SEEK_CUR) r = self.obj.read(4) + self.assertIsInstance(r, str) self.assertEqual(r, 'data') seek(0, pg.SEEK_SET) r = self.obj.read(4) + self.assertIsInstance(r, str) self.assertEqual(r, 'some') seek(-6, pg.SEEK_END) r = self.obj.read(4) + self.assertIsInstance(r, str) self.assertEqual(r, 'seek') def testTell(self): @@ -287,23 +311,13 @@ def testUnlink(self): self.assertRaises(TypeError, unlink, 0) # unlinking when object is still open self.obj.open(pg.INV_WRITE) + self.assertIsNotNone(self.obj.oid) + self.assertNotEqual(0, self.obj.oid) self.assertRaises(IOError, unlink) data = 'some data to be sold' self.obj.write(data) self.obj.close() - oid = self.obj.oid - self.assertIsInstance(oid, int) - self.assertNotEqual(oid, 0) - obj = self.pgcnx.getlo(oid) - try: - self.assertIsNot(obj, self.obj) - self.assertEqual(obj.oid, oid) - obj.open(pg.INV_READ) - r = obj.read(80) - obj.close() - self.assertEqual(r, data) - finally: - del obj + # unlinking after object has been closed unlink() self.assertIsNone(self.obj.oid) @@ -354,14 +368,20 @@ def testExport(self): self.assertRaises(TypeError, export) self.assertRaises(TypeError, export, 0) self.assertRaises(TypeError, export, 'invalid', 0) - f = tempfile.NamedTemporaryFile() + if windows: + # NamedTemporaryFiles don't work well here + fname = 'temp_test_pg_largeobj_export.txt' + f = open(fname, 'wb') + else: + f = tempfile.NamedTemporaryFile() + fname = f.name data = 'some data to be exported' self.obj.open(pg.INV_WRITE) self.obj.write(data) # exporting when object is not yet closed self.assertRaises(IOError, export, f.name) self.obj.close() - export(f.name) + export(fname) r = f.read() f.close() self.assertEqual(r, data) From d1238a1347ba585c7dac76159d53728129b2f3f2 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 27 Nov 2015 15:35:07 +0000 Subject: [PATCH 025/144] Backport some more tests from trunk to 4.x --- module/tests/test_classic_functions.py | 50 +++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/module/tests/test_classic_functions.py b/module/tests/test_classic_functions.py index e5037d5a..6ad22958 100755 --- a/module/tests/test_classic_functions.py +++ b/module/tests/test_classic_functions.py @@ -298,18 +298,60 @@ def testSetDecimalPoint(self): point = pg.get_decimal_point() pg.set_decimal_point('*') r = pg.get_decimal_point() + pg.set_decimal_point(point) self.assertIsInstance(r, str) self.assertEqual(r, '*') - pg.set_decimal_point(point) + r = pg.get_decimal_point() + self.assertIsInstance(r, str) + self.assertEqual(r, point) + + def testGetDecimal(self): + r = pg.get_decimal() + self.assertIs(r, pg.Decimal) def testSetDecimal(self): decimal_class = pg.Decimal - pg.set_decimal(long) + pg.set_decimal(int) + r = pg.get_decimal() pg.set_decimal(decimal_class) + self.assertIs(r, int) + r = pg.get_decimal() + self.assertIs(r, decimal_class) + + def testGetBool(self): + r = pg.get_bool() + self.assertIsInstance(r, bool) + self.assertIs(r, False) + + def testSetBool(self): + use_bool = pg.get_bool() + pg.set_bool(True) + r = pg.get_bool() + pg.set_bool(use_bool) + self.assertIsInstance(r, bool) + self.assertIs(r, True) + pg.set_bool(False) + r = pg.get_bool() + pg.set_bool(use_bool) + self.assertIsInstance(r, bool) + self.assertIs(r, False) + r = pg.get_bool() + self.assertIsInstance(r, bool) + self.assertIs(r, use_bool) + + def testGetNamedresult(self): + r = pg.get_namedresult() + self.assertIs(r, pg._namedresult) def testSetNamedresult(self): - pg.set_namedresult(lambda q: q.getresult()) - pg.set_namedresult(pg._namedresult) + namedresult = pg.get_namedresult() + f = lambda q: q.getresult() + pg.set_namedresult(f) + r = pg.get_namedresult() + pg.set_namedresult(namedresult) + self.assertIs(r, f) + r = pg.get_namedresult() + self.assertIs(r, namedresult) class TestModuleConstants(unittest.TestCase): From 5ed2df22335c5d095c8ec292b126fad91fbb262b Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 27 Nov 2015 16:03:01 +0000 Subject: [PATCH 026/144] Make tests compatible with Python 2.5 --- module/tests/test_classic.py | 2 +- module/tests/test_classic_connection.py | 3 ++- module/tests/test_classic_functions.py | 18 ++++++++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/module/tests/test_classic.py b/module/tests/test_classic.py index 65fb4fe4..fdfa3784 100755 --- a/module/tests/test_classic.py +++ b/module/tests/test_classic.py @@ -308,7 +308,7 @@ def test_notify(self, options=None): self.assertFalse(target.listening) finally: target.close() - if thread.is_alive(): + if thread.isAlive(): thread.join(5) def test_notify_other_options(self): diff --git a/module/tests/test_classic_connection.py b/module/tests/test_classic_connection.py index eab988b1..9b3d7e47 100755 --- a/module/tests/test_classic_connection.py +++ b/module/tests/test_classic_connection.py @@ -204,7 +204,7 @@ def sleep(): thread.start() # run the query while 1: # make sure the query is really running time.sleep(0.1) - if thread.is_alive() or time.time() - t1 > 5: + if thread.isAlive() or time.time() - t1 > 5: break r = self.connection.cancel() # cancel the running query thread.join() # wait for the thread to end @@ -1193,6 +1193,7 @@ def testSetBool(self): self.assertIsInstance(r, str) self.assertIs(r, 't') + @unittest.skipUnless(namedtuple, 'Named tuples not available') def testGetNamedresult(self): namedresult = pg.get_namedresult() # error if a parameter is passed diff --git a/module/tests/test_classic_functions.py b/module/tests/test_classic_functions.py index 6ad22958..74873adf 100755 --- a/module/tests/test_classic_functions.py +++ b/module/tests/test_classic_functions.py @@ -21,6 +21,11 @@ import pg # the module under test +try: + from collections import namedtuple +except ImportError: # Python < 2.6 + namedtuple = None + class TestAuxiliaryFunctions(unittest.TestCase): """Test the auxiliary functions external to the connection class.""" @@ -341,14 +346,23 @@ def testSetBool(self): def testGetNamedresult(self): r = pg.get_namedresult() - self.assertIs(r, pg._namedresult) + if namedtuple: + self.assertTrue(callable(r)) + self.assertIs(r, pg._namedresult) + else: + self.assertIsNone(r) def testSetNamedresult(self): namedresult = pg.get_namedresult() + self.assertRaises(TypeError, pg.set_namedresult) + self.assertRaises(TypeError, pg.set_namedresult, None) f = lambda q: q.getresult() pg.set_namedresult(f) r = pg.get_namedresult() - pg.set_namedresult(namedresult) + if namedtuple or namedresult is not None: + pg.set_namedresult(namedresult) + else: + namedresult = f self.assertIs(r, f) r = pg.get_namedresult() self.assertIs(r, namedresult) From f92d3cbcb0597c2f4bbf7340e426464ae5837b65 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 27 Nov 2015 16:45:53 +0000 Subject: [PATCH 027/144] Use unittest2 in test_classic Also, depending on how the tests are started, relative imports may fail, so use absolute imports if that happens. --- module/tests/test_classic.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/module/tests/test_classic.py b/module/tests/test_classic.py index fdfa3784..56d9cf1b 100755 --- a/module/tests/test_classic.py +++ b/module/tests/test_classic.py @@ -2,11 +2,16 @@ from __future__ import with_statement +try: + import unittest2 as unittest # for Python < 2.7 +except ImportError: + import unittest + import sys from functools import partial from time import sleep from threading import Thread -import unittest + from pg import * # We need a database to test against. If LOCAL_PyGreSQL.py exists we will @@ -345,22 +350,24 @@ def test_notify_timeout(self): if __name__ == '__main__': - suite = unittest.TestSuite() - - if len(sys.argv) > 1: test_list = sys.argv[1:] - else: test_list = unittest.getTestCaseNames(UtilityTest, 'test_') - if len(sys.argv) == 2 and sys.argv[1] == '-l': print '\n'.join(unittest.getTestCaseNames(UtilityTest, 'test_')) - sys.exit(1) + sys.exit(0) + + test_list = [name for name in sys.argv[1:] if not name.startswith('-')] + if not test_list: + test_list = unittest.getTestCaseNames(UtilityTest, 'test_') + suite = unittest.TestSuite() for test_name in test_list: try: suite.addTest(UtilityTest(test_name)) - except: + except Exception: print "\n ERROR: %s.\n" % sys.exc_value sys.exit(1) - rc = unittest.TextTestRunner(verbosity=1).run(suite) - sys.exit(len(rc.errors+rc.failures) != 0) - + verbosity = '-v' in sys.argv[1:] and 2 or 1 + failfast = '-l' in sys.argv[1:] + runner = unittest.TextTestRunner(verbosity=verbosity, failfast=failfast) + rc = runner.run(suite) + sys.exit(1 if rc.errors or rc.failures else 0) From 40c61ae098b4d43ca9f2e311642ce771bebcd94b Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 27 Nov 2015 17:48:13 +0000 Subject: [PATCH 028/144] Make tests compatible with Python 2.4 We want to be able to test the 4.x branch with older Python versions so that we can specify eligible minimum requirements. --- module/tests/dbapi20.py | 6 +++++- module/tests/test_classic.py | 26 ++++++++++++++++--------- module/tests/test_classic_connection.py | 7 +++---- module/tests/test_classic_dbwrapper.py | 12 +++++++++--- module/tests/test_classic_largeobj.py | 3 +-- module/tests/test_dbapi20.py | 24 ++++++++++++++++++----- 6 files changed, 54 insertions(+), 24 deletions(-) diff --git a/module/tests/dbapi20.py b/module/tests/dbapi20.py index f7247807..69f41ce8 100644 --- a/module/tests/dbapi20.py +++ b/module/tests/dbapi20.py @@ -15,7 +15,11 @@ __version__ = '$Revision: 1.5 $'[11:-2] __author__ = 'Stuart Bishop ' -import unittest +try: + import unittest2 as unittest # for Python < 2.7 +except ImportError: + import unittest + import time # $Log: not supported by cvs2svn $ diff --git a/module/tests/test_classic.py b/module/tests/test_classic.py index 56d9cf1b..013d8b35 100755 --- a/module/tests/test_classic.py +++ b/module/tests/test_classic.py @@ -1,19 +1,19 @@ #! /usr/bin/python -from __future__ import with_statement - try: import unittest2 as unittest # for Python < 2.7 except ImportError: import unittest import sys -from functools import partial from time import sleep from threading import Thread from pg import * +# check whether the "with" statement is supported +no_with = sys.version_info[:2] < (2, 5) + # We need a database to test against. If LOCAL_PyGreSQL.py exists we will # get our information from that. Otherwise we use the defaults. dbname = 'unittest' @@ -121,10 +121,13 @@ def test_insert(self): db.insert('_test_schema', _test=1235) self.assertEqual(d['dvar'], 999) + @unittest.skipIf(no_with, 'context managers not supported') def test_context_manager(self): db = opendb() t = '_test_schema' d = dict(_test=1235) + # wrap "with" statements to avoid SyntaxError in Python < 2.5 + exec """from __future__ import with_statement\nif True: with db: db.insert(t, d) d['_test'] += 1 @@ -140,7 +143,7 @@ def test_context_manager(self): d['_test'] += 1 db.insert(t, d) d['_test'] += 1 - db.insert(t, d) + db.insert(t, d)\n""" self.assertTrue(db.get(t, 1235)) self.assertTrue(db.get(t, 1236)) self.assertRaises(DatabaseError, db.get, t, 1237) @@ -247,8 +250,11 @@ def test_notify(self, options=None): two_payloads = options.get('two_payloads') db = opendb() # Get function under test, can be standalone or DB method. - fut = db.notification_handler if run_as_method else partial( - NotificationHandler, db) + if run_as_method: + fut = db.notification_handler + else: + # functools.partial is not available in Python < 2.5 + fut = lambda *args: NotificationHandler(db, *args) arg_dict = dict(event=None, called=False) self.notify_timeout = False # Listen for 'event_1'. @@ -331,8 +337,10 @@ def test_notify_timeout(self): for run_as_method in False, True: db = opendb() # Get function under test, can be standalone or DB method. - fut = db.notification_handler if run_as_method else partial( - NotificationHandler, db) + if run_as_method: + fut = db.notification_handler + else: + fut = lambda *args: NotificationHandler(db, *args) arg_dict = dict(event=None, called=False) self.notify_timeout = False # Listen for 'event_1' with timeout of 10ms. @@ -370,4 +378,4 @@ def test_notify_timeout(self): failfast = '-l' in sys.argv[1:] runner = unittest.TextTestRunner(verbosity=verbosity, failfast=failfast) rc = runner.run(suite) - sys.exit(1 if rc.errors or rc.failures else 0) + sys.exit((rc.errors or rc.failures) and 1 or 0) diff --git a/module/tests/test_classic_connection.py b/module/tests/test_classic_connection.py index 9b3d7e47..63c4d966 100755 --- a/module/tests/test_classic_connection.py +++ b/module/tests/test_classic_connection.py @@ -175,7 +175,7 @@ def testMethodReset(self): query = self.connection.query # check that client encoding gets reset encoding = query('show client_encoding').getresult()[0][0].upper() - changed_encoding = 'LATIN1' if encoding == 'UTF8' else 'UTF8' + changed_encoding = encoding == 'UTF8' and 'LATIN1' or 'UTF8' self.assertNotEqual(encoding, changed_encoding) self.connection.query("set client_encoding=%s" % changed_encoding) new_encoding = query('show client_encoding').getresult()[0][0].upper() @@ -491,8 +491,7 @@ def testPrint(self): print r except Exception: pass - finally: - sys.stdout = stdout + sys.stdout = stdout f.seek(0) r = f.read() f.close() @@ -525,7 +524,7 @@ def testQueryWithBoolParams(self, use_bool=None): use_bool_default = pg.get_bool() pg.set_bool(use_bool) try: - v_false, v_true = (False, True) if use_bool else 'ft' + v_false, v_true = use_bool and (False, True) or 'ft' r_false, r_true = [(v_false,)], [(v_true,)] self.assertEqual(query("select false").getresult(), r_false) self.assertEqual(query("select true").getresult(), r_true) diff --git a/module/tests/test_classic_dbwrapper.py b/module/tests/test_classic_dbwrapper.py index d655b387..29fa2a07 100755 --- a/module/tests/test_classic_dbwrapper.py +++ b/module/tests/test_classic_dbwrapper.py @@ -11,17 +11,20 @@ """ -from __future__ import with_statement - try: import unittest2 as unittest # for Python < 2.7 except ImportError: import unittest +import sys + import pg # the module under test from decimal import Decimal +# check whether the "with" statement is supported +no_with = sys.version_info[:2] < (2, 5) + # We need a database to test against. If LOCAL_PyGreSQL.py exists we will # get our information from that. Otherwise we use the defaults. # The current user must have create schema privilege on the database. @@ -961,10 +964,13 @@ def testTransaction(self): self.assertEqual(r, [1, 2, 5, 7, 9]) query("drop table test_table") + @unittest.skipIf(no_with, 'context managers not supported') def testContextManager(self): query = self.db.query query("drop table if exists test_table") query("create table test_table (n integer check(n>0))") + # wrap "with" statements to avoid SyntaxError in Python < 2.5 + exec """from __future__ import with_statement\nif True: with self.db: query("insert into test_table values (1)") query("insert into test_table values (2)") @@ -984,7 +990,7 @@ def testContextManager(self): except pg.ProgrammingError, error: self.assertTrue('check' in str(error)) with self.db: - query("insert into test_table values (7)") + query("insert into test_table values (7)")\n""" r = [r[0] for r in query( "select * from test_table order by 1").getresult()] self.assertEqual(r, [1, 2, 5, 7]) diff --git a/module/tests/test_classic_largeobj.py b/module/tests/test_classic_largeobj.py index 60bb7396..ba0f4ce7 100755 --- a/module/tests/test_classic_largeobj.py +++ b/module/tests/test_classic_largeobj.py @@ -398,8 +398,7 @@ def testPrint(self): print self.obj except Exception: pass - finally: - sys.stdout = stdout + sys.stdout = stdout f.seek(0) r = f.read() f.close() diff --git a/module/tests/test_dbapi20.py b/module/tests/test_dbapi20.py index 8c29ca63..5ac3f944 100755 --- a/module/tests/test_dbapi20.py +++ b/module/tests/test_dbapi20.py @@ -1,12 +1,20 @@ #! /usr/bin/python # $Id$ -from __future__ import with_statement +try: + import unittest2 as unittest # for Python < 2.7 +except ImportError: + import unittest + +import sys -import unittest -import dbapi20 import pgdb +import dbapi20 + +# check whether the "with" statement is supported +no_with = sys.version_info[:2] < (2, 5) + # We need a database to test against. # If LOCAL_PyGreSQL.py exists we will get our information from that. # Otherwise we use the defaults. @@ -217,9 +225,12 @@ def test_connection_errors(self): self.assertEqual(con.DataError, pgdb.DataError) self.assertEqual(con.NotSupportedError, pgdb.NotSupportedError) + @unittest.skipIf(no_with, 'context managers not supported') def test_connection_as_contextmanager(self): table = self.table_prefix + 'booze' con = self._connect() + # wrap "with" statements to avoid SyntaxError in Python < 2.5 + exec """from __future__ import with_statement\nif True: try: cur = con.cursor() cur.execute("create table %s (n smallint check(n!=4))" % table) @@ -248,7 +259,7 @@ def test_connection_as_contextmanager(self): rows = cur.fetchall() rows = [row[0] for row in rows] finally: - con.close() + con.close()\n""" self.assertEqual(rows, [1, 2, 5, 6, 9]) def test_cursor_connection(self): @@ -257,10 +268,13 @@ def test_cursor_connection(self): self.assertEqual(cur.connection, con) cur.close() + @unittest.skipIf(no_with, 'context managers not supported') def test_cursor_as_contextmanager(self): con = self._connect() + # wrap "with" statements to avoid SyntaxError in Python < 2.5 + exec """from __future__ import with_statement\nif True: with con.cursor() as cur: - self.assertEqual(cur.connection, con) + self.assertEqual(cur.connection, con)\n""" def test_pgdb_type(self): self.assertEqual(pgdb.STRING, pgdb.STRING) From a63e04fee36ca6e4cd558b2a3e521ea2ad91e4c2 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 27 Nov 2015 18:29:58 +0000 Subject: [PATCH 029/144] Forgot to rename one .rst file --- docs/{pgdb.rst => pgdb.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/{pgdb.rst => pgdb.txt} (100%) diff --git a/docs/pgdb.rst b/docs/pgdb.txt similarity index 100% rename from docs/pgdb.rst rename to docs/pgdb.txt From 582d03b73b0396d102728a29cfecccaa8b477347 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 27 Nov 2015 22:30:33 +0000 Subject: [PATCH 030/144] Fix issue with some cleanup code in the C module --- module/pgmodule.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/module/pgmodule.c b/module/pgmodule.c index b8d9ccf0..ddf24d9c 100644 --- a/module/pgmodule.c +++ b/module/pgmodule.c @@ -2611,9 +2611,7 @@ pg_query(pgobject *self, PyObject *args) *s = PyUnicode_AsEncodedString(obj, enc, "strict"); if (*s == NULL) { - free(lparms); free(parms); free(str); - PyErr_SetString(PyExc_UnicodeError, "query parameter" - " could not be decoded (bad client encoding)"); + free(lparms); free(parms); while (i--) { if (*--s) @@ -2621,6 +2619,9 @@ pg_query(pgobject *self, PyObject *args) Py_DECREF(*s); } } + free(str); + PyErr_SetString(PyExc_UnicodeError, "query parameter" + " could not be decoded (bad client encoding)"); return NULL; } *p = PyString_AsString(*s); @@ -2631,9 +2632,7 @@ pg_query(pgobject *self, PyObject *args) *s = PyObject_Str(obj); if (*s == NULL) { - free(lparms); free(parms); free(str); - PyErr_SetString(PyExc_TypeError, - "query parameter has no string representation"); + free(lparms); free(parms); while (i--) { if (*--s) @@ -2641,6 +2640,9 @@ pg_query(pgobject *self, PyObject *args) Py_DECREF(*s); } } + free(str); + PyErr_SetString(PyExc_TypeError, + "query parameter has no string representation"); return NULL; } *p = PyString_AsString(*s); From 53680f8d94d44bcb5bf5f409de30a961e3fcb900 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 27 Nov 2015 22:40:27 +0000 Subject: [PATCH 031/144] Specify supported Python versions for the 4.x branch Tests for Python 2.4, 2.5, 2.6., 2.7 are running now, so we can specify these versions as supported in this branch. The trunk will only support Python >= 2.6. The test are not compatible with Python 2.3, but the basic functionality still works in Python 2.3, so old code to support Python < 2.4 has been left in place. A config file for testing with tox has been added. Unfortunately, tox supports only Python 2.6 and 2.7. So Python 2.4 and 2.5 need to be tested manually. --- docs/announce.txt | 7 ++++--- docs/changelog.txt | 4 +++- docs/install.txt | 4 ++-- module/pg.py | 4 ++-- module/pgdb.py | 6 +++--- module/setup.py | 11 ++++++++--- module/tests/test_classic_functions.py | 6 +++++- module/tox.ini | 12 ++++++++++++ 8 files changed, 39 insertions(+), 15 deletions(-) create mode 100644 module/tox.ini diff --git a/docs/announce.txt b/docs/announce.txt index af4e1d8f..9ee08f75 100644 --- a/docs/announce.txt +++ b/docs/announce.txt @@ -22,10 +22,11 @@ for general information. This version has been built and unit tested on: - NetBSD - FreeBSD - - openSUSE 12.2 + - openSUSE + - Ubuntu - Windows 7 with both MinGW and Visual Studio - - PostgreSQL 8.4, 9.0 and 9.2 32 and 64bit - - Python 2.5, 2.6 and 2.7 32 and 64bit + - PostgreSQL 8.4 and 9.3 64bit + - Python 2.4, 2.5, 2.6 and 2.7 32 and 64bit | D'Arcy J.M. Cain | darcy@PyGreSQL.org diff --git a/docs/changelog.txt b/docs/changelog.txt index 4799fae6..34039e2a 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -4,11 +4,13 @@ ChangeLog Version 4.2 ----------- - Set a better default for the user option "escaping-funcs". -- Greatly improve unit testing. - Force build to compile with no errors. - Fix decimal point handling. - Add option to return boolean values as bool objects. +- Add option to return money values as string. - Fix notification handler (Thanks Patrick TJ McPhee). +- Fix a small issue with large objects. +- Greatly improve unit testing, tests run with Python 2.4 to 2.7 again. Version 4.1.1 (2013-01-08) -------------------------- diff --git a/docs/install.txt b/docs/install.txt index cc7fb0fd..8b7448df 100644 --- a/docs/install.txt +++ b/docs/install.txt @@ -11,8 +11,8 @@ are on Windows, make sure that the directory with libpq.dll is in your ``PATH`` environment variable. The current version of PyGreSQL has been tested with Python 2.7 and -PostGreSQL 9.2. Older version should work as well, but you will need -at least Python 2.5 and PostgreSQL 8.3. +PostGreSQL 9.3. Older version should work as well, but you will need +at least Python 2.4 and PostgreSQL 8.3. PyGreSQL will be installed as three modules, a dynamic module called _pg.pyd, and two pure Python wrapper modules called pg.py and pgdb.py. diff --git a/module/pg.py b/module/pg.py index 325a0d84..f07b4797 100644 --- a/module/pg.py +++ b/module/pg.py @@ -34,12 +34,12 @@ import warnings try: frozenset -except NameError: # Python < 2.4 +except NameError: # Python < 2.4, unsupported from sets import ImmutableSet as frozenset try: from decimal import Decimal set_decimal(Decimal) -except ImportError: # Python < 2.4 +except ImportError: # Python < 2.4, unsupported Decimal = float try: from collections import namedtuple diff --git a/module/pgdb.py b/module/pgdb.py index 65fec3ae..46b9e1c0 100644 --- a/module/pgdb.py +++ b/module/pgdb.py @@ -66,14 +66,14 @@ from _pg import * try: frozenset -except NameError: # Python < 2.4 +except NameError: # Python < 2.4, unsupported from sets import ImmutableSet as frozenset from datetime import date, time, datetime, timedelta from time import localtime -try: # use Decimal if available +try: from decimal import Decimal set_decimal(Decimal) -except ImportError: # otherwise (Python < 2.4) +except ImportError: # Python < 2.4, unsupported Decimal = float # use float instead of Decimal try: from math import isnan, isinf diff --git a/module/setup.py b/module/setup.py index 3c34a3fa..34a27c77 100755 --- a/module/setup.py +++ b/module/setup.py @@ -21,7 +21,7 @@ * PostgreSQL pg_config tool (usually included in the devel package) (the Windows installer has it as part of the database server feature) -The supported versions are Python 2.5-2.7 and PostgreSQL 8.3-9.4. +The supported versions are Python 2.4-2.7 and PostgreSQL 8.3-9.4. Use as follows: python setup.py build # to build the module @@ -40,7 +40,7 @@ import sys -if not (2, 3) <= sys.version_info[:2] < (3, 0): +if not (2, 4) <= sys.version_info[:2] <= (2, 7): raise Exception("Sorry, PyGreSQL %s" " does not support this Python version" % version) @@ -185,7 +185,12 @@ def finalize_options(self): "License :: OSI Approved :: Python Software Foundation License", "Operating System :: OS Independent", "Programming Language :: C", - "Programming Language :: Python", + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.4', + 'Programming Language :: Python :: 2.5', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', "Programming Language :: SQL", "Topic :: Database", "Topic :: Database :: Front-Ends", diff --git a/module/tests/test_classic_functions.py b/module/tests/test_classic_functions.py index 74873adf..978d15d2 100755 --- a/module/tests/test_classic_functions.py +++ b/module/tests/test_classic_functions.py @@ -21,6 +21,10 @@ import pg # the module under test +try: + from decimal import Decimal +except ImportError: # Python < 2.4, unsupported + Decimal = None try: from collections import namedtuple except ImportError: # Python < 2.6 @@ -312,7 +316,7 @@ def testSetDecimalPoint(self): def testGetDecimal(self): r = pg.get_decimal() - self.assertIs(r, pg.Decimal) + self.assertIs(r, Decimal) def testSetDecimal(self): decimal_class = pg.Decimal diff --git a/module/tox.ini b/module/tox.ini new file mode 100644 index 00000000..d3857307 --- /dev/null +++ b/module/tox.ini @@ -0,0 +1,12 @@ +# config file for tox 2.0 +# unfortunately py24,py25 are not supported by tox any more + +[tox] +envlist = py26,py27 + +[testenv] +deps = + py26: unittest2 +commands = + py26: unit2 discover [] + py27: python -m unittest discover [] From 54c9186195b16c2085a2096955148c6442920822 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 28 Nov 2015 12:32:31 +0000 Subject: [PATCH 032/144] Fix garbage collection issues This patch fixes memory management problems, particularly on Windows. The query() method and the handling of object references inside the method has been greatly simplified and corrected. Note that we don't calculate and pass the paramLengths any more since this is only needed when also passing info about binary data in paramFormats. We also use PyMem_Malloc() instead of malloc() to allocate memory to make sure the memory is allocated in the Python heap. --- module/pgmodule.c | 171 ++++++++++++++++++++++------------------------ 1 file changed, 81 insertions(+), 90 deletions(-) diff --git a/module/pgmodule.c b/module/pgmodule.c index ddf24d9c..05b5ffb2 100644 --- a/module/pgmodule.c +++ b/module/pgmodule.c @@ -354,7 +354,7 @@ get_type_array(PGresult *result, int nfields) int *typ; int j; - if (!(typ = malloc(sizeof(int) * nfields))) + if (!(typ = PyMem_Malloc(sizeof(int) * nfields))) { PyErr_SetString(PyExc_MemoryError, "memory error in getresult()."); return NULL; @@ -414,8 +414,8 @@ format_result(const PGresult *res) if (n > 0) { - char * const aligns = (char *) malloc(n * sizeof(char)); - int * const sizes = (int *) malloc(n * sizeof(int)); + char * const aligns = (char *) PyMem_Malloc(n * sizeof(char)); + int * const sizes = (int *) PyMem_Malloc(n * sizeof(int)); if (aligns && sizes) { @@ -484,7 +484,7 @@ format_result(const PGresult *res) /* plus size of footer */ size += 40; /* is the buffer size that needs to be allocated */ - buffer = (char *) malloc(size); + buffer = (char *) PyMem_Malloc(size); if (buffer) { char *p = buffer; @@ -541,13 +541,13 @@ format_result(const PGresult *res) *p++ = '\n'; } /* free memory */ - free(aligns); - free(sizes); + PyMem_Free(aligns); + PyMem_Free(sizes); /* create the footer */ sprintf(p, "(%d row%s)", m, m == 1 ? "" : "s"); /* return the result */ result = PyString_FromString(buffer); - free(buffer); + PyMem_Free(buffer); return result; } else @@ -557,10 +557,8 @@ format_result(const PGresult *res) return NULL; } } else { - if (aligns) - free(aligns); - if (sizes) - free(aligns); + PyMem_Free(aligns); + PyMem_Free(sizes); PyErr_SetString(PyExc_MemoryError, "Not enough memory for formatting the query result."); return NULL; @@ -2246,7 +2244,7 @@ pgquery_getresult(pgqueryobject *self, PyObject *args) } exit: - free(typ); + PyMem_Free(typ); /* returns list */ return reslist; @@ -2388,7 +2386,7 @@ pgquery_dictresult(pgqueryobject *self, PyObject *args) } exit: - free(typ); + PyMem_Free(typ); /* returns list */ return reslist; @@ -2529,7 +2527,7 @@ static PyObject * pg_query(pgobject *self, PyObject *args) { char *query; - PyObject *oargs = NULL; + PyObject *param_obj = NULL; PGresult *result; pgqueryobject *npgobj; int status, @@ -2542,128 +2540,120 @@ pg_query(pgobject *self, PyObject *args) } /* get query args */ - if (!PyArg_ParseTuple(args, "s|O", &query, &oargs)) + if (!PyArg_ParseTuple(args, "s|O", &query, ¶m_obj)) { PyErr_SetString(PyExc_TypeError, "query(sql, [args]), with sql (string)."); return NULL; } - /* If oargs is passed, ensure it's a non-empty tuple. We want to treat + /* If param_obj is passed, ensure it's a non-empty tuple. We want to treat * an empty tuple the same as no argument since we'll get that when the * caller passes no arguments to db.query(), and historic behaviour was * to call PQexec() in that case, which can execute multiple commands. */ - if (oargs) + if (param_obj) { - if (!PyTuple_Check(oargs) && !PyList_Check(oargs)) - { - PyErr_SetString(PyExc_TypeError, "query parameters must be a tuple or list."); + param_obj = PySequence_Fast(param_obj, + "query parameters must be a sequence."); + if (!param_obj) return NULL; - } + nparms = (int)PySequence_Fast_GET_SIZE(param_obj); - nparms = (int)PySequence_Size(oargs); + /* if there's a single argument and it's a list or tuple, it + * contains the positional arguments. */ + if (nparms == 1) + { + PyObject *first_obj = PySequence_Fast_GET_ITEM(param_obj, 0); + if (PyList_Check(first_obj) || PyTuple_Check(first_obj)) + { + Py_DECREF(param_obj); + param_obj = PySequence_Fast(first_obj, NULL); + nparms = (int)PySequence_Fast_GET_SIZE(param_obj); + } + } } /* gets result */ if (nparms) { /* prepare arguments */ - PyObject **str, **s, *obj = PySequence_GetItem(oargs, 0); + PyObject **str, **s; char **parms, **p, *enc=NULL; - int *lparms, *l; register int i; - /* if there's a single argument and it's a list or tuple, it - * contains the positional aguments. */ - if (nparms == 1 && (PyList_Check(obj) || PyTuple_Check(obj))) - { - oargs = obj; - nparms = (int)PySequence_Size(oargs); + str = (PyObject **)PyMem_Malloc(nparms * sizeof(*str)); + parms = (char **)PyMem_Malloc(nparms * sizeof(*parms)); + if (!str || !parms) { + PyMem_Free(parms); + PyMem_Free(str); + Py_XDECREF(param_obj); + PyErr_SetString(PyExc_MemoryError, "memory error in query()."); + return NULL; } - str = (PyObject **)malloc(nparms * sizeof(*str)); - parms = (char **)malloc(nparms * sizeof(*parms)); - lparms = (int *)malloc(nparms * sizeof(*lparms)); /* convert optional args to a list of strings -- this allows * the caller to pass whatever they like, and prevents us * from having to map types to OIDs */ - for (i = 0, s=str, p=parms, l=lparms; i < nparms; i++, s++, p++, l++) + for (i = 0, s=str, p=parms; i < nparms; i++, p++) { - obj = PySequence_GetItem(oargs, i); + PyObject *obj = PySequence_Fast_GET_ITEM(param_obj, i); if (obj == Py_None) { - *s = NULL; *p = NULL; - *l = 0; } else if (PyUnicode_Check(obj)) { + PyObject *str_obj; if (!enc) enc = (char *)pg_encoding_to_char( PQclientEncoding(self->cnx)); if (!strcmp(enc, "UTF8")) - *s = PyUnicode_AsUTF8String(obj); + str_obj = PyUnicode_AsUTF8String(obj); else if (!strcmp(enc, "LATIN1")) - *s = PyUnicode_AsLatin1String(obj); + str_obj = PyUnicode_AsLatin1String(obj); else if (!strcmp(enc, "SQL_ASCII")) - *s = PyUnicode_AsASCIIString(obj); + str_obj = PyUnicode_AsASCIIString(obj); else - *s = PyUnicode_AsEncodedString(obj, enc, "strict"); - if (*s == NULL) + str_obj = PyUnicode_AsEncodedString(obj, enc, "strict"); + if (!str_obj) { - free(lparms); free(parms); - while (i--) - { - if (*--s) - { - Py_DECREF(*s); - } - } - free(str); + PyMem_Free(parms); + while (s != str) { s--; Py_DECREF(*s); } + PyMem_Free(str); + Py_XDECREF(param_obj); PyErr_SetString(PyExc_UnicodeError, "query parameter" " could not be decoded (bad client encoding)"); return NULL; } - *p = PyString_AsString(*s); - *l = (int)PyString_Size(*s); + *s++ = str_obj; + *p = PyString_AsString(str_obj); } else { - *s = PyObject_Str(obj); - if (*s == NULL) + PyObject *str_obj = PyObject_Str(obj); + if (!str_obj) { - free(lparms); free(parms); - while (i--) - { - if (*--s) - { - Py_DECREF(*s); - } - } - free(str); + PyMem_Free(parms); + while (s != str) { s--; Py_DECREF(*s); } + PyMem_Free(str); + Py_XDECREF(param_obj); PyErr_SetString(PyExc_TypeError, "query parameter has no string representation"); return NULL; } - *p = PyString_AsString(*s); - *l = (int)PyString_Size(*s); + *s++ = str_obj; + *p = PyString_AsString(str_obj); } } Py_BEGIN_ALLOW_THREADS result = PQexecParams(self->cnx, query, nparms, - NULL, (const char * const *)parms, lparms, NULL, 0); + NULL, (const char * const *)parms, NULL, NULL, 0); Py_END_ALLOW_THREADS - free(lparms); free(parms); - for (i = 0, s=str; i < nparms; i++, s++) - { - if (*s) - { - Py_DECREF(*s); - } - } - free(str); + PyMem_Free(parms); + while (s != str) { s--; Py_DECREF(*s); } + PyMem_Free(str); } else { @@ -2672,6 +2662,9 @@ pg_query(pgobject *self, PyObject *args) Py_END_ALLOW_THREADS } + /* we don't need the params any more */ + Py_XDECREF(param_obj); + /* checks result validity */ if (!result) { @@ -2912,7 +2905,7 @@ pg_inserttable(pgobject *self, PyObject *args) } /* allocate buffer */ - if (!(buffer = malloc(MAX_BUFFER_SIZE))) + if (!(buffer = PyMem_Malloc(MAX_BUFFER_SIZE))) { PyErr_SetString(PyExc_MemoryError, "can't allocate insert buffer."); @@ -2928,7 +2921,7 @@ pg_inserttable(pgobject *self, PyObject *args) if (!result) { - free(buffer); + PyMem_Free(buffer); PyErr_SetString(PyExc_ValueError, PQerrorMessage(self->cnx)); return NULL; } @@ -2961,7 +2954,7 @@ pg_inserttable(pgobject *self, PyObject *args) { if (j != n) { - free(buffer); + PyMem_Free(buffer); PyErr_SetString(PyExc_TypeError, "arrays contained in second arg must have same size."); return NULL; @@ -3037,7 +3030,7 @@ pg_inserttable(pgobject *self, PyObject *args) if (bufsiz <= 0) { - free(buffer); + PyMem_Free(buffer); PyErr_SetString(PyExc_MemoryError, "insert buffer overflow."); return NULL; @@ -3052,7 +3045,7 @@ pg_inserttable(pgobject *self, PyObject *args) { PyErr_SetString(PyExc_IOError, PQerrorMessage(self->cnx)); PQendcopy(self->cnx); - free(buffer); + PyMem_Free(buffer); return NULL; } } @@ -3062,18 +3055,18 @@ pg_inserttable(pgobject *self, PyObject *args) { PyErr_SetString(PyExc_IOError, PQerrorMessage(self->cnx)); PQendcopy(self->cnx); - free(buffer); + PyMem_Free(buffer); return NULL; } if (PQendcopy(self->cnx)) { PyErr_SetString(PyExc_IOError, PQerrorMessage(self->cnx)); - free(buffer); + PyMem_Free(buffer); return NULL; } - free(buffer); + PyMem_Free(buffer); /* no error : returns nothing */ Py_INCREF(Py_None); @@ -3207,12 +3200,11 @@ pg_escape_string(pgobject *self, PyObject *args) to_length = from_length; from_length = (from_length - 1)/2; } - to = (char *)malloc(to_length); + to = (char *)PyMem_Malloc(to_length); to_length = (int)PQescapeStringConn(self->cnx, to, from, (size_t)from_length, NULL); ret = Py_BuildValue("s#", to, to_length); - if (to) - free(to); + PyMem_Free(to); if (!ret) /* pass on exception */ return NULL; return ret; @@ -3679,11 +3671,10 @@ escape_string(PyObject *self, PyObject *args) to_length = from_length; from_length = (from_length - 1)/2; } - to = (char *)malloc(to_length); + to = (char *)PyMem_Malloc(to_length); to_length = (int)PQescapeString(to, from, (size_t)from_length); ret = Py_BuildValue("s#", to, to_length); - if (to) - free(to); + PyMem_Free(to); if (!ret) /* pass on exception */ return NULL; return ret; From b9cf6c55f107371dda59a60851f26c5c74e138e7 Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Sun, 6 Dec 2015 13:59:14 +0000 Subject: [PATCH 033/144] Update copyright. --- docs/conf.py | 2 +- docs/copyright.txt | 2 +- docs/introduction.txt | 2 +- docs/readme.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index aa204757..9f46bce3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,7 +44,7 @@ # General information about the project. project = u'PyGreSQL' -copyright = u'2012, The PyGreSQL team' +copyright = u'2015, The PyGreSQL team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/copyright.txt b/docs/copyright.txt index eff0f5a9..4feb0192 100644 --- a/docs/copyright.txt +++ b/docs/copyright.txt @@ -10,7 +10,7 @@ Copyright (c) 1995, Pascal Andre Further modifications copyright (c) 1997-2008 by D'Arcy J.M. Cain (darcy@PyGreSQL.org) -Further modifications copyright (c) 2009-2012 by the PyGreSQL team. +Further modifications copyright (c) 2009-2015 by the PyGreSQL team. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement diff --git a/docs/introduction.txt b/docs/introduction.txt index 5477f15f..37087df4 100644 --- a/docs/introduction.txt +++ b/docs/introduction.txt @@ -8,7 +8,7 @@ PostgreSQL features from a Python script. | This software is copyright © 1995, Pascal Andre. | Further modifications are copyright © 1997-2008 by D'Arcy J.M. Cain. -| Further modifications are copyright © 2009-2012 by the PyGreSQL team +| Further modifications are copyright © 2009-2015 by the PyGreSQL team **PostgreSQL** is a highly scalable, SQL compliant, open source object-relational database management system. With more than 15 years diff --git a/docs/readme.txt b/docs/readme.txt index 2c3387d7..964e0b0c 100644 --- a/docs/readme.txt +++ b/docs/readme.txt @@ -25,7 +25,7 @@ Copyright (c) 1995, Pascal Andre Further modifications copyright (c) 1997-2008 by D'Arcy J.M. Cain (darcy@PyGreSQL.org) -Further modifications copyright (c) 2009-2012 by the PyGreSQL team. +Further modifications copyright (c) 2009-2015 by the PyGreSQL team. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement From 3e91d8a7660df25a9d7b2bdd8f3fbabd460682d8 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 10 Dec 2015 12:40:11 +0000 Subject: [PATCH 034/144] Fix nasty typo in update() method This bug prevented the method from using the returning clause. --- module/pg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/pg.py b/module/pg.py index f07b4797..e4b2a9c9 100644 --- a/module/pg.py +++ b/module/pg.py @@ -871,7 +871,7 @@ def update(self, cl, d=None, **kw): return d values = ', '.join(values) selectable = self.has_table_privilege(qcl) - if selectable and self.server_version >= 880200: + if selectable and self.server_version >= 80200: ret = ' RETURNING %s*' % ('oid' in attnames and 'oid, ' or '') else: ret = '' From 0593e774279498a84f6ed1c5e5ba5a38608369c8 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 23 Dec 2015 14:55:54 +0000 Subject: [PATCH 035/144] Make database port configurable in test_dbapi20 --- module/tests/test_dbapi20.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/module/tests/test_dbapi20.py b/module/tests/test_dbapi20.py index 5ac3f944..d90c35e9 100755 --- a/module/tests/test_dbapi20.py +++ b/module/tests/test_dbapi20.py @@ -31,7 +31,8 @@ class test_PyGreSQL(dbapi20.DatabaseAPI20Test): driver = pgdb connect_args = () - connect_kw_args = {'dsn': dbhost + ':' + dbname} + connect_kw_args = {'database': dbname, + 'host': '%s:%d' % (dbhost or '', dbport or -1)} lower_func = 'lower' # For stored procedure test From a4bca8ec1b96da3ab958e689006a40807abe2c2a Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Wed, 23 Dec 2015 15:14:10 +0000 Subject: [PATCH 036/144] When creating a missing database use the same parameters as the test run. --- module/tests/test_dbapi20.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/tests/test_dbapi20.py b/module/tests/test_dbapi20.py index d90c35e9..ff564096 100755 --- a/module/tests/test_dbapi20.py +++ b/module/tests/test_dbapi20.py @@ -44,7 +44,7 @@ def setUp(self): con.close() except pgdb.Error: import pg - pg.DB().query('create database ' + dbname) + pg.DB('postgres',dbhost,dbport).query('create database ' + dbname) def tearDown(self): dbapi20.DatabaseAPI20Test.tearDown(self) From fc9f01aef2c799fe3c009bb8a632892aef1cacbb Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 23 Dec 2015 15:53:02 +0000 Subject: [PATCH 037/144] More sophisticated creation of missing database --- module/tests/test_dbapi20.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/module/tests/test_dbapi20.py b/module/tests/test_dbapi20.py index ff564096..2646f0ad 100755 --- a/module/tests/test_dbapi20.py +++ b/module/tests/test_dbapi20.py @@ -42,9 +42,15 @@ def setUp(self): try: con = self._connect() con.close() - except pgdb.Error: + except pgdb.Error: # try to create a missing database import pg - pg.DB('postgres',dbhost,dbport).query('create database ' + dbname) + try: # first try to log in as superuser + db = pg.DB('postgres', dbhost or None, dbport or -1, + user='postgres') + except Exception: # then try to log in as current user + db = pg.DB('postgres', dbhost or None, dbport or -1) + db.query('create database ' + dbname) + def tearDown(self): dbapi20.DatabaseAPI20Test.tearDown(self) From 51fff98c8544748effd5af507ccc400b5dc73cc7 Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Wed, 23 Dec 2015 15:54:45 +0000 Subject: [PATCH 038/144] Ignore local definitions file. From b3f057801b3e49042a69ac675e727dcb46545f29 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Tue, 29 Dec 2015 20:02:11 +0000 Subject: [PATCH 039/144] Update Sphinx conf and make files Update the conf and make files for creating the documentation so that newer Sphinx features are supported. --- docs/Makefile | 45 +++++++++++++++++++++++-- docs/conf.py | 92 ++++++++++++++++++++++++++++++++++++--------------- docs/make.bat | 73 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 30 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 3768edd4..0a1113c9 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -2,11 +2,16 @@ # # You can set these variables from the command line. -SPHINXOPTS = -aE +SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter @@ -14,7 +19,7 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext help: @echo "Please use \`make ' where is one of" @@ -25,21 +30,26 @@ help: @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" clean: - -rm -rf $(BUILDDIR)/* + rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @@ -81,6 +91,14 @@ qthelp: @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyGreSQL.qhc" +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @@ -108,6 +126,12 @@ latexpdf: $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @@ -151,3 +175,18 @@ doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/conf.py b/docs/conf.py index 9f46bce3..0f72534e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- # -# PyGreSQL documentation build configuration file, created by -# sphinx-quickstart on Thu Nov 1 07:47:06 2012. +# PyGreSQL documentation build configuration file. # -# This file is execfile()d with the current directory set to its containing dir. +# This file is execfile()d with the current directory set to its +# containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. @@ -11,7 +11,9 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import sys +import os +import shlex # import Cloud import cloud_sptheme as csp @@ -21,20 +23,22 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) -# -- General configuration ----------------------------------------------------- +# -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] -# The suffix of source filenames. -source_suffix = '.rst' +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.txt' # The encoding of source files. #source_encoding = 'utf-8-sig' @@ -44,20 +48,23 @@ # General information about the project. project = u'PyGreSQL' -copyright = u'2015, The PyGreSQL team' +author = u'The PyGreSQL Team' +copyright = u'2015, ' + author # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '4.2' +version = u'4.2' # The full version, including alpha/beta/rc tags. -release = '4.2' +release = u'4.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: @@ -69,7 +76,7 @@ # directories to ignore when looking for source files. exclude_patterns = ['_build'] -# The reST default role (used for this markup: `text`) to use for all documents. +# The reST default role (used for this markup: `text`) for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. @@ -89,8 +96,14 @@ # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False -# -- Options for HTML output --------------------------------------------------- +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. @@ -99,7 +112,7 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -html_theme_options = { "defaultcollapsed": True, } +html_theme_options = {'defaultcollapsed': True} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = [csp.get_theme_dir()] @@ -125,6 +138,11 @@ # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' @@ -166,11 +184,25 @@ # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + # Output file base name for HTML help builder. htmlhelp_basename = 'PyGreSQLdoc' -# -- Options for LaTeX output -------------------------------------------------- +# -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). @@ -181,13 +213,17 @@ # Additional stuff for the LaTeX preamble. #'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'PyGreSQL.tex', u'PyGreSQL Documentation', - u'The PyGreSQL team', 'manual'), + (master_doc, 'PyGreSQL.tex', u'PyGreSQL Documentation', + author, 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -211,28 +247,27 @@ #latex_domain_indices = True -# -- Options for manual page output -------------------------------------------- +# -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'pygresql', u'PyGreSQL Documentation', - [u'The PyGreSQL team'], 1) + (master_doc, 'pygresql', u'PyGreSQL Documentation', [author], 1) ] # If true, show URL addresses after external links. #man_show_urls = False -# -- Options for Texinfo output ------------------------------------------------ +# -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'PyGreSQL', u'PyGreSQL Documentation', - u'The PyGreSQL team', 'PyGreSQL', 'One line description of project.', - 'Miscellaneous'), + (master_doc, 'PyGreSQL', u'PyGreSQL Documentation', + author, 'PyGreSQL', 'One line description of project.', + 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. @@ -243,3 +278,6 @@ # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/docs/make.bat b/docs/make.bat index d6fa8189..b8571b60 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -33,8 +33,11 @@ if "%1" == "help" ( echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled goto end ) @@ -44,6 +47,31 @@ if "%1" == "clean" ( goto end ) + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 1>NUL 2>NUL +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 @@ -129,6 +157,26 @@ if "%1" == "latex" ( goto end ) +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 @@ -187,4 +235,29 @@ results in %BUILDDIR%/doctest/output.txt. goto end ) +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + :end From 46310a0fad5349bda4a0675033e28c73bf43abd1 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 30 Dec 2015 14:33:59 +0000 Subject: [PATCH 040/144] Use generic file name for source code repository docs --- docs/index.rst | 2 +- docs/source.txt | 7 +++++++ docs/svn.txt | 6 ------ 3 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 docs/source.txt delete mode 100644 docs/svn.txt diff --git a/docs/index.rst b/docs/index.rst index a29bb76f..4c7e027e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -18,7 +18,7 @@ Contents: interface classic db_api - svn + source mailinglist future examples diff --git a/docs/source.txt b/docs/source.txt new file mode 100644 index 00000000..622c8710 --- /dev/null +++ b/docs/source.txt @@ -0,0 +1,7 @@ +Access to the source repository +=============================== + +The SVN repository can be checked out from +:file:`svn://svn.pygresql.org/pygresql`. +It is also available through the +`online SVN repository `_. diff --git a/docs/svn.txt b/docs/svn.txt deleted file mode 100644 index 99dca0a7..00000000 --- a/docs/svn.txt +++ /dev/null @@ -1,6 +0,0 @@ -SVN Access -========== - -The SVN repository can be checked out from svn://svn.PyGreSQL.org/pygresql. -It is also available through the -`online SVN repository `_. From 9ee007a23f7ee9afb9657874ed54d1e5d853c251 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 30 Dec 2015 15:11:51 +0000 Subject: [PATCH 041/144] Convert pg module documentation to Sphinx The documentation was already written in reST format, but did not use Sphinx specific features for documenting the various Python objects. --- docs/classic.txt | 1714 ------------------------------------- docs/db_api.txt | 21 - docs/index.rst | 4 +- docs/pg.txt | 2085 +++++++++++++++++++++++----------------------- docs/pgdb.txt | 38 +- 5 files changed, 1048 insertions(+), 2814 deletions(-) delete mode 100644 docs/classic.txt delete mode 100644 docs/db_api.txt diff --git a/docs/classic.txt b/docs/classic.txt deleted file mode 100644 index bd1f6855..00000000 --- a/docs/classic.txt +++ /dev/null @@ -1,1714 +0,0 @@ -The classic PyGreSQL interface -============================== - -Introduction ------------- - -The `pg` module handles three types of objects, - -- the `pgobject`, which handles the connection - and all the requests to the database, -- the `pglarge` object, which handles - all the accesses to PostgreSQL large objects, -- the `pgqueryobject` that handles query results - -and it provides a convenient wrapper class `DB` for the `pgobject`. - -If you want to see a simple example of the use of some of these functions, -see the examples page. - - -functions & constants ------------------------------- -The `pg` module defines a few functions that allow to connect -to a database and to define "default variables" that override -the environment variables used by PostgreSQL. - -These "default variables" were designed to allow you to handle general -connection parameters without heavy code in your programs. You can prompt the -user for a value, put it in the default variable, and forget it, without -having to modify your environment. The support for default variables can be -disabled by setting the -DNO_DEF_VAR option in the Python setup file. Methods -relative to this are specified by the tag [DV]. - -All variables are set to `None` at module initialization, specifying that -standard environment variables should be used. - -connect -------- -Opens a pg connection - -Syntax:: - - connect([dbname], [host], [port], [opt], [tty], [user], [passwd]) - -Parameters: - :dbname: name of connected database (string/None) - :host: name of the server host (string/None) - :port: port used by the database server (integer/-1) - :opt: connection options (string/None) - :tty: debug terminal (string/None) - :user: PostgreSQL user (string/None) - :passwd: password for user (string/None) - -Return type: - :pgobject: If successful, the `pgobject` handling the connection - -Exceptions raised: - :TypeError: bad argument type, or too many arguments - :SyntaxError: duplicate argument definition - :pg.InternalError: some error occurred during pg connection definition - - (plus all exceptions relative to object allocation) - -Description: - This function opens a connection to a specified database on a given - PostgreSQL server. You can use keywords here, as described in the - Python tutorial. The names of the keywords are the name of the - parameters given in the syntax line. For a precise description - of the parameters, please refer to the PostgreSQL user manual. - -Examples:: - - import pg - - con1 = pg.connect('testdb', 'myhost', 5432, None, None, 'bob', None) - con2 = pg.connect(dbname='testdb', host='localhost', user='bob') - -get_defhost/set_defhost ------------------------ -default server host [DV] - -Syntax:: - - get_defhost() - -Parameters: - None - -Return type: - :string, None: default host specification - -Exceptions raised: - :TypeError: too many arguments - -Description: - This method returns the current default host specification, - or `None` if the environment variables should be used. - Environment variables won't be looked up. - -Syntax:: - - set_defhost(host) - -Parameters: - :host: new default host (string/None) - -Return type: - :string, None: previous default host specification - -Exceptions raised: - :TypeError: bad argument type, or too many arguments - -Description: - This methods sets the default host value for new connections. - If `None` is supplied as parameter, environment variables will - be used in future connections. It returns the previous setting - for default host. - -get_defport/set_defport ------------------------ -default server port [DV] - -Syntax:: - - get_defport() - -Parameters: - None - -Return type: - :integer, None: default port specification - -Exceptions raised: - :TypeError: too many arguments - -Description: - This method returns the current default port specification, - or `None` if the environment variables should be used. - Environment variables won't be looked up. - -Syntax:: - - set_defport(port) - -Parameters: - :port: new default port (integer/-1) - -Return type: - :integer, None: previous default port specification - -Description: - This methods sets the default port value for new connections. If -1 is - supplied as parameter, environment variables will be used in future - connections. It returns the previous setting for default port. - -get_defopt/set_defopt ---------------------- -default connection options [DV] - -Syntax:: - - get_defopt() - -Parameters: - None - -Return type: - :string, None: default options specification - -Exceptions raised: - :TypeError: too many arguments - -Description: - This method returns the current default connection options specification, - or `None` if the environment variables should be used. Environment variables - won't be looked up. - -Syntax:: - - set_defopt(options) - -Parameters: - :options: new default connection options (string/None) - -Return type: - :string, None: previous default options specification - -Exceptions raised: - :TypeError: bad argument type, or too many arguments - -Description: - This methods sets the default connection options value for new connections. - If `None` is supplied as parameter, environment variables will be used in - future connections. It returns the previous setting for default options. - -get_deftty/set_deftty ---------------------- -default debug tty [DV] - -Syntax:: - - get_deftty() - -Parameters: - None - -Return type: - :string, None: default debug terminal specification - -Exceptions raised: - :TypeError: too many arguments - -Description: - This method returns the current default debug terminal specification, or - `None` if the environment variables should be used. Environment variables - won't be looked up. - -Syntax:: - - set_deftty(terminal) - -Parameters: - :terminal: new default debug terminal (string/None) - -Return type: - :string, None: previous default debug terminal specification - -Exceptions raised: - :TypeError: bad argument type, or too many arguments - -Description: - This methods sets the default debug terminal value for new connections. If - `None` is supplied as parameter, environment variables will be used in future - connections. It returns the previous setting for default terminal. - -get_defbase/set_defbase ------------------------ -default database name [DV] - -Syntax:: - - get_defbase() - -Parameters: - None - -Return type: - :string, None: default database name specification - -Exceptions raised: - :TypeError: too many arguments - -Description: - This method returns the current default database name specification, or - `None` if the environment variables should be used. Environment variables - won't be looked up. - -Syntax:: - - set_defbase(base) - -Parameters: - :base: new default base name (string/None) - -Return type: - :string, None: previous default database name specification - -Exceptions raised: - :TypeError: bad argument type, or too many arguments - -Description: - This method sets the default database name value for new connections. If - `None` is supplied as parameter, environment variables will be used in - future connections. It returns the previous setting for default host. - -escape_string -------------- -escape a string for use within SQL - -Syntax:: - - escape_string(string) - -Parameters: - :string: the string that is to be escaped - -Return type: - :str: the escaped string - -Exceptions raised: - :TypeError: bad argument type, or too many arguments - -Description: - This function escapes a string for use within an SQL command. - This is useful when inserting data values as literal constants - in SQL commands. Certain characters (such as quotes and backslashes) - must be escaped to prevent them from being interpreted specially - by the SQL parser. `escape_string` performs this operation. - Note that there is also a `pgobject` method with the same name - which takes connection properties into account. - -.. caution:: It is especially important to do proper escaping when - handling strings that were received from an untrustworthy source. - Otherwise there is a security risk: you are vulnerable to "SQL injection" - attacks wherein unwanted SQL commands are fed to your database. - -Example:: - - name = raw_input("Name? ") - phone = con.query("select phone from employees" - " where name='%s'" % escape_string(name)).getresult() - -escape_bytea ------------- -escape binary data for use within SQL as type `bytea` - -Syntax:: - - escape_bytea(datastring) - -Parameters: - :datastring: string containing the binary data that is to be escaped - -Return type: - :str: the escaped string - -Exceptions raised: - :TypeError: bad argument type, or too many arguments - -Description: - Escapes binary data for use within an SQL command with the type `bytea`. - As with `escape_string`, this is only used when inserting data directly - into an SQL command string. - Note that there is also a `pgobject` method with the same name - which takes connection properties into account. - -Example:: - - picture = file('garfield.gif', 'rb').read() - con.query("update pictures set img='%s' where name='Garfield'" - % escape_bytea(picture)) - -unescape_bytea --------------- -unescape `bytea` data that has been retrieved as text - -Syntax:: - - unescape_bytea(string) - -Parameters: - :datastring: the `bytea` data string that has been retrieved as text - -Return type: - :str: string containing the binary data - -Exceptions raised: - :TypeError: bad argument type, or too many arguments - -Description: - Converts an escaped string representation of binary data into binary - data - the reverse of `escape_bytea`. This is needed when retrieving - `bytea` data with the `getresult()` or `dictresult()` method. - -Example:: - - picture = unescape_bytea(con.query( - "select img from pictures where name='Garfield'").getresult[0][0]) - file('garfield.gif', 'wb').write(picture) - -get_decimal ------------ -get the decimal type to be used for numeric values - -Syntax:: - - get_decimal() - -Parameters: - None - -Return type: - :cls: the Python class used for PostgreSQL numeric values - -Description: - This function returns the Python class that is used by PyGreSQL to hold - PostgreSQL numeric values. The default class is decimal.Decimal if - available, otherwise the float type is used. - -set_decimal ------------ -set a decimal type to be used for numeric values - -Syntax:: - - set_decimal(cls) - -Parameters: - :cls: the Python class to be used for PostgreSQL numeric values - -Description: - This function can be used to specify the Python class that shall be - used by PyGreSQL to hold PostgreSQL numeric values. The default class - is decimal.Decimal if available, otherwise the float type is used. - -get_decimal_point ------------------ -get the decimal mark used for monetary values - -Syntax:: - - get_decimal_point() - -Parameters: - None - -Return type: - :str: string with one character representing the decimal mark - -Description: - This function returns the decimal mark used by PyGreSQL to interpret - PostgreSQL monetary values when converting them to decimal numbers. - The default setting is '.' as a decimal point. This setting is not - adapted automatically to the locale used by PostGreSQL, but you can - use `set_decimal()` to set a different decimal mark manually. A return - value of `None` means monetary values are not interpreted as decimal - numbers, but returned as strings including the formatting and currency. - -set_decimal_point ------------------ -specify which decimal mark is used for interpreting monetary values - -Syntax:: - - set_decimal_point(str) - -Parameters: - :str: string with one character representing the decimal mark - -Description: - This function can be used to specify the decimal mark used by PyGreSQL - to interpret PostgreSQL monetary values. The default value is '.' as - a decimal point. This value is not adapted automatically to the locale - used by PostGreSQL, so if you are dealing with a database set to a - locale that uses a ',' instead of '.' as the decimal point, then you - need to call `set_decimal(',')` to have PyGreSQL interpret monetary - values correctly. If you don't want money values to be converted to - decimal numbers, then you can call `set_decimal(None)`, which will - cause PyGreSQL to return monetary values as strings including their - formatting and currency. - -get_bool --------- -check whether boolean values are returned as bool objects - -Syntax:: - - get_bool() - -Parameters: - None - -Return type: - :bool: whether or not bool objects will be returned - -Description: - This function checks whether PyGreSQL returns PostgreSQL boolean - values converted to Python bool objects, or as 'f' and 't' strings - which are the values used internally by PostgreSQL. By default, - conversion to bool objects is not activated, but you can enable - this with the `set_bool()` method. - -set_bool --------- -set whether boolean values are returned as bool objects - -Syntax:: - - set_bool(bool) - -Parameters: - :bool: whether or not bool objects shall be returned - -Description: - This function can be used to specify whether PyGreSQL shall return - PostgreSQL boolean values converted to Python bool objects, or as - 'f' and 't' strings which are the values used internally by PostgreSQL. - By default, conversion to bool objects is not activated, but you can - enable this by calling `set_bool(True)`. - -get_namedresult ---------------- -set a function that will convert to named tuples - -Syntax:: - - set_namedresult() - -Parameters: - None - -Description: - This function returns the function used by PyGreSQL to construct the - result of the `query.namedresult()` method. - -set_namedresult ---------------- -set a function that will convert to named tuples - -Syntax:: - - set_namedresult(func) - -Parameters: - :func: the function to be used to convert results to named tuples - -Description: - You can use this if you want to create different kinds of named tuples - returned by the `query.namedresult()` method. - - -Module constants ----------------- -Some constants are defined in the module dictionary. -They are intended to be used as parameters for methods calls. -You should refer to the libpq description in the PostgreSQL user manual -for more information about them. These constants are: - -:version, __version__: constants that give the current version. -:INV_READ, INV_WRITE: large objects access modes, - used by `(pgobject.)locreate` and `(pglarge.)open` -:SEEK_SET, SEEK_CUR, SEEK_END: positional flags, - used by `(pglarge.)seek` - - -pgobject --------- -Connection object - -This object handles a connection to a PostgreSQL database. It embeds and -hides all the parameters that define this connection, thus just leaving really -significant parameters in function calls. - -.. caution:: Some methods give direct access to the connection socket. - *Do not use them unless you really know what you are doing.* - If you prefer disabling them, - set the -DNO_DIRECT option in the Python setup file. - - **These methods are specified by the tag [DA].** - -.. note:: Some other methods give access to large objects - (refer to PostgreSQL user manual for more information about these). - If you want to forbid access to these from the module, - set the -DNO_LARGE option in the Python setup file. - - **These methods are specified by the tag [LO].** - -query ------ -executes a SQL command string - -Syntax:: - - query(command, [args]) - -Parameters: - :command: SQL command (string) - :args: optional positional arguments - -Return type: - :pgqueryobject, None: result values - -Exceptions raised: - :TypeError: bad argument type, or too many arguments - :TypeError: invalid connection - :ValueError: empty SQL query or lost connection - :pg.ProgrammingError: error in query - :pg.InternalError: error during query processing - -Description: - This method simply sends a SQL query to the database. If the query is an - insert statement that inserted exactly one row into a table that has OIDs, the - return value is the OID of the newly inserted row. If the query is an update - or delete statement, or an insert statement that did not insert exactly one - row in a table with OIDs, then the numer of rows affected is returned as a - string. If it is a statement that returns rows as a result (usually a select - statement, but maybe also an "insert/update ... returning" statement), this - method returns a `pgqueryobject` that can be accessed via the `getresult()`, - `dictresult()` or `namedresult()` methods or simply printed. Otherwise, it - returns `None`. - - The query may optionally contain positional parameters of the form `$1`, - `$2`, etc instead of literal data, and the values supplied as a tuple. - The values are substituted by the database in such a way that they don't - need to be escaped, making this an effective way to pass arbitrary or - unknown data without worrying about SQL injection or syntax errors. - - When the database could not process the query, a `pg.ProgrammingError` or - a `pg.InternalError` is raised. You can check the "SQLSTATE" code of this - error by reading its `sqlstate` attribute. - -Example:: - - name = raw_input("Name? ") - phone = con.query("select phone from employees" - " where name=$1", (name, )).getresult() - -reset ------ -resets the connection - -Syntax:: - - reset() - -Parameters: - None - -Return type: - None - -Exceptions raised: - :TypeError: too many (any) arguments - :TypeError: invalid connection - -Description: - This method resets the current database connection. - -cancel ------- -abandon processing of current SQL command - -Syntax:: - - cancel() - -Parameters: - None - -Return type: - None - -Exceptions raised: - :TypeError: too many (any) arguments - :TypeError: invalid connection - -Description: - This method requests that the server abandon processing - of the current SQL command. - -close ------ -close the database connection - -Syntax:: - - close() - -Parameters: - None - -Return type: - None - -Exceptions raised: - :TypeError: too many (any) arguments - -Description: - This method closes the database connection. The connection will - be closed in any case when the connection is deleted but this - allows you to explicitly close it. It is mainly here to allow - the DB-SIG API wrapper to implement a close function. - -fileno ------- -returns the socket used to connect to the database - -Syntax:: - - fileno() - -Parameters: - None - -Exceptions raised: - :TypeError: too many (any) arguments - :TypeError: invalid connection - -Description: - This method returns the underlying socket id used to connect - to the database. This is useful for use in select calls, etc. - -getnotify ---------- -gets the last notify from the server - -Syntax:: - - getnotify() - -Parameters: - None - -Return type: - :tuple, None: last notify from server - -Exceptions raised: - :TypeError: too many parameters - :TypeError: invalid connection - -Description: - This method tries to get a notify from the server (from the SQL statement - NOTIFY). If the server returns no notify, the methods returns None. - Otherwise, it returns a tuple (triplet) `(relname, pid, extra)`, where - `relname` is the name of the notify, `pid` is the process id of the - connection that triggered the notify, and `extra` is a payload string - that has been sent with the notification. Remember to do a listen query - first, otherwise getnotify() will always return `None`. - -inserttable ------------ -insert a list into a table - -Syntax:: - - inserttable(table, values) - -Parameters: - :table: the table name (string) - :values: list of rows values (list) - -Return type: - None - -Exceptions raised: - :TypeError: invalid connection, bad argument type, or too many arguments - :MemoryError: insert buffer could not be allocated - :ValueError: unsupported values - -Description: - This method allow to *quickly* insert large blocks of data in a table: - It inserts the whole values list into the given table. Internally, it - uses the COPY command of the PostgreSQL database. The list is a list - of tuples/lists that define the values for each inserted row. The rows - values may contain string, integer, long or double (real) values. - -.. caution:: *Be very careful*: - 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:: - - putline(line) - -Parameters: - :line: line to be written (string) - -Return type: - None - -Exceptions raised: - :TypeError: invalid connection, bad parameter type, or too many parameters - -Description: - This method allows to directly write a string to the server socket. - -getline -------- -gets a line from server socket [DA] - -Syntax:: - - getline() - -Parameters: - None - -Return type: - :string: the line read - -Exceptions raised: - :TypeError: invalid connection - :TypeError: too many parameters - :MemoryError: buffer overflow - -Description: - This method allows to directly read a string from the server socket. - -endcopy -------- -synchronizes client and server [DA] - -Syntax:: - - endcopy() - -Parameters: - None - -Return type: - None - -Exceptions raised: - :TypeError: invalid connection - :TypeError: too many parameters - -Description: - The use of direct access methods may desynchonize client and server. - This method ensure that client and server will be synchronized. - -locreate --------- -create a large object in the database [LO] - -Syntax:: - - locreate(mode) - -Parameters: - :mode: large object create mode - -Return type: - :pglarge: object handling the PostGreSQL large object - -Exceptions raised: - - :TypeError: invalid connection, bad parameter type, or too many parameters - :pg.OperationalError: creation error - -Description: - This method creates a large object in the database. The mode can be defined - by OR-ing the constants defined in the pg module (INV_READ, INV_WRITE and - INV_ARCHIVE). Please refer to PostgreSQL user manual for a description of - the mode values. - -getlo ------ -build a large object from given oid [LO] - -Syntax:: - - getlo(oid) - -Parameters: - :oid: OID of the existing large object (integer) - -Return type: - :pglarge: object handling the PostGreSQL large object - -Exceptions raised: - :TypeError: invalid connection, bad parameter type, or too many parameters - :ValueError: bad OID value (0 is invalid_oid) - -Description: - This method allows to reuse a formerly created large object through the - `pglarge` interface, providing the user have its OID. - -loimport --------- -import a file to a large object [LO] - -Syntax:: - - loimport(name) - -Parameters: - :name: the name of the file to be imported (string) - -Return type: - :pglarge: object handling the PostGreSQL large object - -Exceptions raised: - :TypeError: invalid connection, bad argument type, or too many arguments - :pg.OperationalError: error during file import - -Description: - This methods allows to create large objects in a very simple way. You just - give the name of a file containing the data to be used. - -Object attributes ------------------ -Every `pgobject` defines a set of read-only attributes that describe the -connection and its status. These attributes are: - - :host: the host name of the server (string) - :port: the port of the server (integer) - :db: the selected database (string) - :options: the connection options (string) - :tty: the connection debug terminal (string) - :user: user name on the database system (string) - :protocol_version: the frontend/backend protocol being used (integer) - :server_version: the backend version (integer, e.g. 80305 for 8.3.5) - :status: the status of the connection (integer: 1 - OK, 0 - bad) - :error: the last warning/error message from the server (string) - -DB --- -the DB wrapper class - -The `pgobject` methods are wrapped in the class `DB`. -The preferred way to use this module is as follows:: - - import pg - - db = pg.DB(...) # see below - - for r in db.query( # just for example - """SELECT foo,bar - FROM foo_bar_table - WHERE foo !~ bar""" - ).dictresult(): - - print '%(foo)s %(bar)s' % r - -This class can be subclassed as in this example:: - - import pg - - class DB_ride(pg.DB): - """This class encapsulates the database functions and the specific - methods for the ride database.""" - - def __init__(self): - """Opens a database connection to the rides database""" - - pg.DB.__init__(self, dbname = 'ride') - self.query("""SET DATESTYLE TO 'ISO'""") - - [Add or override methods here] - -The following describes the methods and variables of this class. - -Initialization --------------- -The DB class is initialized with the same arguments as the connect -function described in section 2. It also initializes a few -internal variables. The statement `db = DB()` will open the -local database with the name of the user just like connect() does. - -You can also initialize the DB class with an existing `_pg` or `pgdb` -connection. Pass this connection as a single unnamed parameter, or as a -single parameter named `db`. This allows you to use all of the methods -of the DB class with a DB-API 2 compliant connection. Note that the -`close()` and `reopen()` methods are inoperative in this case. - - - -pkey ----- -return the primary key of a table - -Syntax:: - - pkey(table) - -Parameters: - :table: name of table - -Return type: - :string: Name of the field which is the primary key of the table - -Description: - This method returns the primary key of a table. For composite primary - keys, the return value will be a frozenset. Note that this raises an - exception if the table does not have a primary key. - -get_databases -------------- -get list of databases in the system - -Syntax:: - - get_databases() - -Parameters: - None - -Return type: - :list: all databases in the system - -Description: - Although you can do this with a simple select, it is added here for - convenience. - -get_relations -------------- -get list of relations in connected database - -Syntax:: - - get_relations(kinds) - -Parameters: - :kinds: a string or sequence of type letters - -Description: - The type letters are `r` = ordinary table, `i` = index, `S` = sequence, - `v` = view, `c` = composite type, `s` = special, `t` = TOAST table. - If `kinds` is None or an empty string, all relations are returned (this is - also the default). Although you can do this with a simple select, it is - added here for convenience. - -get_tables ----------- -get list of tables in connected database - -Syntax:: - - get_tables() - -Parameters: - None - -Returns: - :list: all tables in connected database - -Description: - Although you can do this with a simple select, it is added here for - convenience. - -get_attnames ------------- -get the attribute names of a table - -Syntax:: - - get_attnames(table) - -Parameters: - :table: name of table - -Returns: - :dictionary: The keys are the attribute names, - the values are the type names of the attributes. - -Description: - Given the name of a table, digs out the set of attribute names. - -has_table_privilege -------------------- -check whether current user has specified table privilege - -Syntax:: - - has_table_privilege(table, privilege) - -Parameters: - :table: name of table - :privilege: privilege to be checked - default is 'select' - -Description: - Returns True if the current user has the specified privilege for the table. - -get ---- -get a row from a database table or view - -Syntax:: - - get(table, arg, [keyname]) - -Parameters: - :table: name of table or view - :arg: either a dictionary or the value to be looked up - :keyname: name of field to use as key (optional) - -Return type: - :dictionary: The keys are the attribute names, - the values are the row values. - -Description: - This method is the basic mechanism to get a single row. It assumes - that the key specifies a unique row. If `keyname` is not specified - then the primary key for the table is used. If `arg` is a dictionary - then the value for the key is taken from it and it is modified to - include the new values, replacing existing values where necessary. - For a composite key, `keyname` can also be a sequence of key names. - The OID is also put into the dictionary if the table has one, but in - order to allow the caller to work with multiple tables, it is munged - as `oid(schema.table)`. - -insert ------- -insert a row into a database table - -Syntax:: - - insert(table, [d,] [key = val, ...]) - -Parameters: - :table: name of table - :d: optional dictionary of values - -Return type: - :dictionary: The dictionary of values inserted - -Description: - This method inserts a row into a table. If the optional dictionary is - not supplied then the required values must be included as keyword/value - pairs. If a dictionary is supplied then any keywords provided will be - added to or replace the entry in the dictionary. - - The dictionary is then, if possible, reloaded with the values actually - inserted in order to pick up values modified by rules, triggers, etc. - - Note: The method currently doesn't support insert into views - although PostgreSQL does. - -update ------- -update a row in a database table - -Syntax:: - - update(table, [d,] [key = val, ...]) - -Parameters: - :table: name of table - :d: optional dictionary of values - -Return type: - :dictionary: the new row - -Description: - Similar to insert but updates an existing row. The update is based on the - OID value as munged by get or passed as keyword, or on the primary key of - the table. The dictionary is modified, if possible, to reflect any changes - caused by the update due to triggers, rules, default values, etc. - - Like insert, the dictionary is optional and updates will be performed - on the fields in the keywords. There must be an OID or primary key - either in the dictionary where the OID must be munged, or in the keywords - where it can be simply the string "oid". - -query ------ -executes a SQL command string - -Syntax:: - - query(command, [arg1, [arg2, ...]]) - -Parameters: - :command: SQL command (string) - :arg*: optional positional arguments - -Return type: - :pgqueryobject, None: result values - -Exceptions raised: - :TypeError: bad argument type, or too many arguments - :TypeError: invalid connection - :ValueError: empty SQL query or lost connection - :pg.ProgrammingError: error in query - :pg.InternalError: error during query processing - -Description: - Similar to the pgobject function with the same name, except that positional - arguments can be passed either as a single list or tuple, or as individual - positional arguments - -Example:: - - name = raw_input("Name? ") - phone = raw_input("Phone? " - rows = db.query("update employees set phone=$2" - " where name=$1", (name, phone)).getresult()[0][0] - # or - rows = db.query("update employees set phone=$2" - " where name=$1", name, phone).getresult()[0][0] - -clear ------ -clears row values in memory - -Syntax:: - - clear(table, [a]) - -Parameters: - :table: name of table - :a: optional dictionary of values - -Return type: - :dictionary: an empty row - -Description: - This method clears all the attributes to values determined by the types. - Numeric types are set to 0, Booleans are set to 'f', dates are set - to 'now()' and everything else is set to the empty string. - If the array argument is present, it is used as the array and any entries - matching attribute names are cleared with everything else left unchanged. - - If the dictionary is not supplied a new one is created. - -delete ------- -delete a row from a database table - -Syntax:: - - delete(table, [d,] [key = val, ...]) - -Parameters: - :table: name of table - :d: optional dictionary of values - -Returns: - None - -Description: - This method deletes the row from a table. It deletes based on the OID value - as munged by get or passed as keyword, or on the primary key of the table. - The return value is the number of deleted rows (i.e. 0 if the row did not - exist and 1 if the row was deleted). - -escape_string -------------- -escape a string for use within SQL - -Syntax:: - - escape_string(string) - -Parameters: - :string: the string that is to be escaped - -Return type: - :str: the escaped string - -Description: - Similar to the module function with the same name, but the - behavior of this method is adjusted depending on the connection properties - (such as character encoding). - -escape_bytea ------------- -escape binary data for use within SQL as type `bytea` - -Syntax:: - - escape_bytea(datastring) - -Parameters: - :datastring: string containing the binary data that is to be escaped - -Return type: - :str: the escaped string - -Description: - Similar to the module function with the same name, but the - behavior of this method is adjusted depending on the connection properties - (in particular, whether standard-conforming strings are enabled). - -unescape_bytea --------------- -unescape `bytea` data that has been retrieved as text - -Syntax:: - - unescape_bytea(string) - -Parameters: - :datastring: the `bytea` data string that has been retrieved as text - -Return type: - :str: string containing the binary data - -Description: - See the module function with the same name. - - -pgqueryobject methods ---------------------- - -getresult ---------- -get query values as list of tuples - -Syntax:: - - getresult() - -Parameters: - None - -Return type: - :list: result values as a list of tuples - -Exceptions raised: - :TypeError: too many (any) parameters - :MemoryError: internal memory error - -Description: - This method returns the list of the values returned by the query. - More information about this result may be accessed using listfields(), - fieldname() and fieldnum() methods. - -dictresult ----------- -get query values as list of dictionaries - -Syntax:: - - dictresult() - -Parameters: - None - -Return type: - :list: result values as a list of dictionaries - -Exceptions raised: - :TypeError: too many (any) parameters - :MemoryError: internal memory error - -Description: - This method returns the list of the values returned by the query - with each tuple returned as a dictionary with the field names - used as the dictionary index. - -namedresult ------------ -get query values as list of named tuples - -Syntax:: - - namedresult() - -Parameters: - None - -Return type: - :list: result values as a list of named tuples - -Exceptions raised: - :TypeError: too many (any) parameters - :TypeError: named tuples not supported - :MemoryError: internal memory error - -Description: - This method returns the list of the values returned by the query - with each row returned as a named tuple with proper field names. - -listfields ----------- -lists fields names of previous query result - -Syntax:: - - listfields() - -Parameters: - None - -Return type: - :list: field names - -Exceptions raised: - :TypeError: too many parameters - -Description: - This method returns the list of names of the fields defined for the - query result. The fields are in the same order as the result values. - -fieldname/fieldnum ------------------- -field name/number conversion - -Syntax:: - - fieldname(i) - -Parameters: - :i: field number (integer) - -Return type: - :string: field name - -Exceptions raised: - :TypeError: invalid connection, bad parameter type, or too many parameters - :ValueError: invalid field number - -Description: - This method allows to find a field name from its rank number. It can be - useful for displaying a result. The fields are in the same order as the - result values. - -Syntax:: - - fieldnum(name) - -Parameters: - :name: field name (string) - -Return type: - :integer: field number - -Exceptions raised: - :TypeError: invalid connection, bad parameter type, or too many parameters - :ValueError: unknown field name - -Description: - This method returns a field number from its name. It can be used to - build a function that converts result list strings to their correct - type, using a hardcoded table definition. The number returned is the - field rank in the result values list. - -ntuples -------- -return number of tuples in query object - -Syntax:: - - ntuples() - -Parameters: - None - -Return type: - :integer: number of tuples in `pgqueryobject` - -Exceptions raised: - :TypeError: Too many arguments. - -Description: - This method returns the number of tuples found in a query. - - -pglarge -------- -Large objects - -This object handles all the request concerning a PostgreSQL large object. It -embeds and hides all the "recurrent" variables (object OID and connection), -exactly in the same way `pgobjects` do, thus only keeping significant -parameters in function calls. It keeps a reference to the `pgobject` used for -its creation, sending requests though with its parameters. Any modification but -dereferencing the `pgobject` will thus affect the `pglarge` object. -Dereferencing the initial `pgobject` is not a problem since Python won't -deallocate it before the `pglarge` object dereference it. -All functions return a generic error message on call error, whatever the -exact error was. The `error` attribute of the object allows to get the exact -error message. - -See also the PostgreSQL programmer's guide for more information about the -large object interface. - -open ----- -opens a large object - -Syntax:: - - open(mode) - -Parameters: - :mode: open mode definition (integer) - -Return type: - None - -Exceptions raised: - :TypeError: invalid connection, bad parameter type, or too many parameters - :IOError: already opened object, or open error - -Description: - This method opens a large object for reading/writing, in the same way than - the Unix open() function. The mode value can be obtained by OR-ing the - constants defined in the pgmodule (INV_READ, INV_WRITE). - -close ------ -closes a large object - -Syntax:: - - close() - -Parameters: - None - -Return type: - None - -Exceptions raised: - :TypeError: invalid connection - :TypeError: too many parameters - :IOError: object is not opened, or close error - -Description: - This method closes a previously opened large object, in the same way than - the Unix close() function. - -read/write/tell/seek/unlink ---------------------------- -file like large object handling - -Syntax:: - - read(size) - -Parameters: - :size: maximal size of the buffer to be read - -Return type: - :sized string: the read buffer - -Exceptions raised: - :TypeError: invalid connection, invalid object, - bad parameter type, or too many parameters - :ValueError: if `size` is negative - :IOError: object is not opened, or read error - -Description: - This function allows to read data from a large object, starting at current - position. - -Syntax:: - - write(string) - -Parameters: - (sized) string - buffer to be written - -Return type: - None - -Exceptions raised: - :TypeError: invalid connection, bad parameter type, or too many parameters - :IOError: object is not opened, or write error - -Description: - This function allows to write data to a large object, starting at current - position. - -Syntax:: - - seek(offset, whence) - -Parameters: - :offset: position offset - :whence: positional parameter - -Return type: - :integer: new position in object - -Exceptions raised: - :TypeError: invalid connection or invalid object, - bad parameter type, or too many parameters - :IOError: object is not opened, or seek error - -Description: - This method allows to move the position cursor in the large object. The - valid values for the whence parameter are defined as constants in the - `pg` module (`SEEK_SET`, `SEEK_CUR`, `SEEK_END`). - -Syntax:: - - tell() - -Parameters: - None - -Return type: - :integer: current position in large object - -Exceptions raised: - :TypeError: invalid connection or invalid object - :TypeError: too many parameters - :IOError: object is not opened, or seek error - -Description: - This method allows to get the current position in the large object. - -Syntax:: - - unlink() - -Parameter: - None - -Return type: - None - -Exceptions raised: - :TypeError: invalid connection or invalid object - :TypeError: too many parameters - :IOError: object is not closed, or unlink error - -Description: - This methods unlinks (deletes) the PostgreSQL large object. - -size ----- -gives the large object size - -Syntax:: - - size() - -Parameters: - None - -Return type: - :integer: the large object size - -Exceptions raised: - :TypeError: invalid connection or invalid object - :TypeError: too many parameters - :IOError: object is not opened, or seek/tell error - -Description: - This (composite) method allows to get the size of a large object. It was - implemented because this function is very useful for a web interfaced - database. Currently, the large object needs to be opened first. - -export ------- -saves a large object to a file - -Syntax:: - - export(name) - -Parameters: - :name: file to be created - -Return type: - None - -Exceptions raised: - :TypeError: invalid connection or invalid object, - bad parameter type, or too many parameters - :IOError: object is not closed, or export error - -Description: - This methods allows to dump the content of a large object in a very simple - way. The exported file is created on the host of the program, not the - server host. - -Object attributes ------------------ -`pglarge` objects define a read-only set of attributes that allow to get -some information about it. These attributes are: - - :oid: the OID associated with the object - :pgcnx: the `pgobject` associated with the object - :error: the last warning/error message of the connection - -.. caution:: *Be careful*: - In multithreaded environments, `error` may be modified by another thread - using the same pgobject. Remember these object are shared, not duplicated. - You should provide some locking to be able if you want to check this. - The `oid` attribute is very interesting because it allow you reuse the OID - later, creating the `pglarge` object with a `pgobject` getlo() method call. diff --git a/docs/db_api.txt b/docs/db_api.txt deleted file mode 100644 index 8ea115d5..00000000 --- a/docs/db_api.txt +++ /dev/null @@ -1,21 +0,0 @@ -The DB-API compliant interface (pgdb module) -============================================ - -`DB-API 2.0 `_ -(Python Database API Specification v2.0) -is a specification for connecting to databases (not only PostGreSQL) -from Python that has been developed by the Python DB-SIG in 1999. - -The following documentation covers only the newer `pgdb` API. - -The authoritative programming information for the DB-API is availabe at - http://www.python.org/dev/peps/pep-0249/ - -A tutorial-like introduction to the DB-API can be found at - http://www2.linuxjournal.com/lj-issues/issue49/2605.html - - -The pgdb module ---------------- -.. note:: This section of the documentation still needs to be written. - diff --git a/docs/index.rst b/docs/index.rst index 4c7e027e..d94f1044 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,8 +16,8 @@ Contents: changelog install interface - classic - db_api + pg + pgdb source mailinglist future diff --git a/docs/pg.txt b/docs/pg.txt index fa130ca0..d4820857 100644 --- a/docs/pg.txt +++ b/docs/pg.txt @@ -1,47 +1,39 @@ -================================ -PyGreSQL Programming Information -================================ +-------------------------------------------- +:mod:`pg` --- The Classic PyGreSQL Interface +-------------------------------------------- ------------------------------------------- -The classic PyGreSQL interface (pg module) ------------------------------------------- - -.. meta:: - :description: The classic PyGreSQL interface (pg module) - :keywords: PyGreSQL, pg, PostGreSQL, Python +.. module:: pg -.. sectnum:: .. contents:: Contents Introduction ============ -You may either choose to use the -`"classic" PyGreSQL interface `_ -provided by the `pg` module or else the -`DB-API 2.0 compliant interface `_ -provided by the `pgdb` module. +You may either choose to use the "classic" PyGreSQL interface +provided by the :mod:`pg` module or else the +DB-API 2.0 compliant interface provided by the :mod:`pgdb` module. -The following documentation covers only the older `pg` API. +The following part of the documentation covers only the older :mod:`pg` API. -The `pg` module handles three types of objects, +The :mod:`pg` module handles three types of objects, -- the `pgobject`, which handles the connection +- the :class:`pgobject`, which handles the connection and all the requests to the database, -- the `pglarge` object, which handles +- the :class:`pglarge` object, which handles all the accesses to PostgreSQL large objects, -- the `pgqueryobject` that handles query results +- the :class:`pgqueryobject` that handles query results -and it provides a convenient wrapper class `DB` for the `pgobject`. +and it provides a convenient wrapper class :class:`DB` +for the :class:`pgobject`. If you want to see a simple example of the use of some of these functions, -see http://ontario.bikerides.ca where you can find a link at the bottom to the -actual Python code for the page. +see the :doc:`examples` page. Module functions and constants ============================== -The `pg` module defines a few functions that allow to connect + +The :mod:`pg` module defines a few functions that allow to connect to a database and to define "default variables" that override the environment variables used by PostgreSQL. @@ -49,353 +41,442 @@ These "default variables" were designed to allow you to handle general connection parameters without heavy code in your programs. You can prompt the user for a value, put it in the default variable, and forget it, without having to modify your environment. The support for default variables can be -disabled by setting the -DNO_DEF_VAR option in the Python setup file. Methods -relative to this are specified by the tag [DV]. +disabled by setting the ``-DNO_DEF_VAR`` option in the Python setup file. +Methods relative to this are specified by the tag [DV]. -All variables are set to `None` at module initialization, specifying that +All variables are set to ``None`` at module initialization, specifying that standard environment variables should be used. -connect - opens a pg connection -------------------------------- -Syntax:: +connect -- Open a PostgreSQL connection +--------------------------------------- - connect([dbname], [host], [port], [opt], [tty], [user], [passwd]) +.. function:: connect([dbname], [host], [port], [opt], [tty], [user], [passwd]) + + Open a :mod:`pg` connection + + :param dbname: name of connected database (*None* = :data:`defbase`) + :type str: str or None + :param host: name of the server host (*None* = :data:`defhost`) + :type host: str or None + :param port: port used by the database server (-1 = :data:`defport`) + :type port: int + :param opt: connection options (*None* = :data:`defopt`) + :type opt: str or None + :param tty: debug terminal (*None* = :data:`deftty`) + :type tty: str or None + :param user: PostgreSQL user (*None* = :data:`defuser`) + :type user: str or None + :param passwd: password for user (*None* = :data:`defpasswd`) + :type passwd: str or None + :returns: If successful, the :class:`pgobject` handling the connection + :rtype: :class:`pgobject` + :raises TypeError: bad argument type, or too many arguments + :raises SyntaxError: duplicate argument definition + :raises pg.InternalError: some error occurred during pg connection definition + :raises Exception: (all exceptions relative to object allocation) + +This function opens a connection to a specified database on a given +PostgreSQL server. You can use keywords here, as described in the +Python tutorial. The names of the keywords are the name of the +parameters given in the syntax line. For a precise description +of the parameters, please refer to the PostgreSQL user manual. -Parameters: - :dbname: name of connected database (string/None) - :host: name of the server host (string/None) - :port: port used by the database server (integer/-1) - :opt: connection options (string/None) - :tty: debug terminal (string/None) - :user: PostgreSQL user (string/None) - :passwd: password for user (string/None) +Example:: -Return type: - :pgobject: If successful, the `pgobject` handling the connection + import pg -Exceptions raised: - :TypeError: bad argument type, or too many arguments - :SyntaxError: duplicate argument definition - :pg.InternalError: some error occurred during pg connection definition + con1 = pg.connect('testdb', 'myhost', 5432, None, None, 'bob', None) + con2 = pg.connect(dbname='testdb', host='localhost', user='bob') - (plus all exceptions relative to object allocation) +get/set_defhost -- default server host [DV] +------------------------------------------- -Description: - This function opens a connection to a specified database on a given - PostgreSQL server. You can use keywords here, as described in the - Python tutorial. The names of the keywords are the name of the - parameters given in the syntax line. For a precise description - of the parameters, please refer to the PostgreSQL user manual. +.. function:: get_defhost(host) -Examples:: + Get the default host - import pg + :returns: the current default host specification + :rtype: str or None + :raises TypeError: too many arguments - con1 = pg.connect('testdb', 'myhost', 5432, None, None, 'bob', None) - con2 = pg.connect(dbname='testdb', host='localhost', user='bob') +This method returns the current default host specification, +or ``None`` if the environment variables should be used. +Environment variables won't be looked up. -get_defhost, set_defhost - default server host [DV] ---------------------------------------------------- -Syntax:: +.. function:: set_defhost(host) - get_defhost() + Set the default host -Parameters: - None + :param host: the new default host specification + :type host: str or None + :returns: the previous default host specification + :rtype: str or None + :raises TypeError: bad argument type, or too many arguments -Return type: - :string, None: default host specification +This methods sets the default host value for new connections. +If ``None`` is supplied as parameter, environment variables will +be used in future connections. It returns the previous setting +for default host. -Exceptions raised: - :TypeError: too many arguments +get/set_defport -- default server port [DV] +------------------------------------------- -Description: - This method returns the current default host specification, - or `None` if the environment variables should be used. - Environment variables won't be looked up. +.. function:: get_defport() -Syntax:: + Get the default port - set_defhost(host) + :returns: the current default port specification + :rtype: int + :raises TypeError: too many arguments -Parameters: - :host: new default host (string/None) +This method returns the current default port specification, +or ``None`` if the environment variables should be used. +Environment variables won't be looked up. -Return type: - :string, None: previous default host specification +.. function:: set_defport(port) -Exceptions raised: - :TypeError: bad argument type, or too many arguments + Set the default port -Description: - This methods sets the default host value for new connections. - If `None` is supplied as parameter, environment variables will - be used in future connections. It returns the previous setting - for default host. + :param port: the new default port + :type port: int + :returns: previous default port specification + :rtype: int or None -get_defport, set_defport - default server port [DV] ---------------------------------------------------- -Syntax:: +This methods sets the default port value for new connections. If -1 is +supplied as parameter, environment variables will be used in future +connections. It returns the previous setting for default port. - get_defport() +get/set_defopt -- default connection options [DV] +-------------------------------------------------- -Parameters: - None +.. function:: get_defopt() -Return type: - :integer, None: default port specification + Get the default connection options -Exceptions raised: - :TypeError: too many arguments + :returns: the current default options specification + :rtype: str or None + :raises TypeError: too many arguments -Description: - This method returns the current default port specification, - or `None` if the environment variables should be used. - Environment variables won't be looked up. +This method returns the current default connection options specification, +or ``None`` if the environment variables should be used. Environment variables +won't be looked up. -Syntax:: +.. function:: set_defopt(options) - set_defport(port) + Set the default connection options -Parameters: - :port: new default port (integer/-1) + :param options: the new default connection options + :type options: str or None + :returns: previous default options specification + :rtype: str or None + :raises TypeError: bad argument type, or too many arguments -Return type: - :integer, None: previous default port specification +This methods sets the default connection options value for new connections. +If ``None`` is supplied as parameter, environment variables will be used in +future connections. It returns the previous setting for default options. -Description: - This methods sets the default port value for new connections. If -1 is - supplied as parameter, environment variables will be used in future - connections. It returns the previous setting for default port. +get/set_deftty -- default debug tty [DV] +---------------------------------------- -get_defopt, set_defopt - default connection options [DV] --------------------------------------------------------- -Syntax:: +.. function:: get_deftty() - get_defopt() + Get the default debug terminal -Parameters: - None + :returns: the current default debug terminal specification + :rtype: str or None + :raises TypeError: too many arguments -Return type: - :string, None: default options specification +This method returns the current default debug terminal specification, or +``None`` if the environment variables should be used. Environment variables +won't be looked up. -Exceptions raised: - :TypeError: too many arguments +.. function:: set_deftty(terminal) -Description: - This method returns the current default connection options specification, - or `None` if the environment variables should be used. Environment variables - won't be looked up. + Set the default debug terminal -Syntax:: + :param terminal: the new default debug terminal + :type terminal: str or None + :returns: the previous default debug terminal specification + :rtype: str or None + :raises TypeError: bad argument type, or too many arguments - set_defopt(options) +This methods sets the default debug terminal value for new connections. +If ``None`` is supplied as parameter, environment variables will be used +in future connections. It returns the previous setting for default terminal. -Parameters: - :options: new default connection options (string/None) +get/set_defbase -- default database name [DV] +--------------------------------------------- -Return type: - :string, None: previous default options specification +.. function:: get_defbase() -Exceptions raised: - :TypeError: bad argument type, or too many arguments + Get the default database name -Description: - This methods sets the default connection options value for new connections. - If `None` is supplied as parameter, environment variables will be used in - future connections. It returns the previous setting for default options. + :returns: the current default database name specification + :rtype: str or None + :raises TypeError: too many arguments -get_deftty, set_deftty - default debug tty [DV] ------------------------------------------------ -Syntax:: +This method returns the current default database name specification, or +``None`` if the environment variables should be used. Environment variables +won't be looked up. - get_deftty() +.. function:: set_defbase(base) -Parameters: - None + Set the default database name -Return type: - :string, None: default debug terminal specification + :param base: the new default base name + :type base: str or None + :returns: the previous default database name specification + :rtype: str or None + :raises TypeError: bad argument type, or too many arguments -Exceptions raised: - :TypeError: too many arguments +This method sets the default database name value for new connections. If +``None`` is supplied as parameter, environment variables will be used in +future connections. It returns the previous setting for default host. -Description: - This method returns the current default debug terminal specification, or - `None` if the environment variables should be used. Environment variables - won't be looked up. - -Syntax:: +get/set_defuser -- default database user [DV] +--------------------------------------------- - set_deftty(terminal) +.. function:: get_defuser() -Parameters: - :terminal: new default debug terminal (string/None) + Get the default database user -Return type: - :string, None: previous default debug terminal specification + :returns: the current default database user specification + :rtype: str or None + :raises TypeError: too many arguments -Exceptions raised: - :TypeError: bad argument type, or too many arguments +This method returns the current default database user specification, or +``None`` if the environment variables should be used. Environment variables +won't be looked up. -Description: - This methods sets the default debug terminal value for new connections. If - `None` is supplied as parameter, environment variables will be used in future - connections. It returns the previous setting for default terminal. +.. function:: set_defuser(user) -get_defbase, set_defbase - default database name [DV] ------------------------------------------------------ -Syntax:: + Set the default database user - get_defbase() + :param user: the new default database user + :type base: str or None + :returns: the previous default database user specification + :rtype: str or None + :raises TypeError: bad argument type, or too many arguments -Parameters: - None +This method sets the default database user name for new connections. If +``None`` is supplied as parameter, environment variables will be used in +future connections. It returns the previous setting for default host. -Return type: - :string, None: default database name specification +get/set_defpasswd -- default database password [DV] +--------------------------------------------------- -Exceptions raised: - :TypeError: too many arguments +.. function:: get_defpasswd() -Description: - This method returns the current default database name specification, or - `None` if the environment variables should be used. Environment variables - won't be looked up. + Get the default database password -Syntax:: + :returns: the current default database password specification + :rtype: str or None + :raises TypeError: too many arguments - set_defbase(base) +This method returns the current default database password specification, or +``None`` if the environment variables should be used. Environment variables +won't be looked up. -Parameters: - :base: new default base name (string/None) +.. function:: set_defpasswd(passwd) -Return type: - :string, None: previous default database name specification + Set the default database password -Exceptions raised: - :TypeError: bad argument type, or too many arguments + :param passwd: the new default database password + :type base: str or None + :returns: the previous default database password specification + :rtype: str or None + :raises TypeError: bad argument type, or too many arguments -Description: - This method sets the default database name value for new connections. If - `None` is supplied as parameter, environment variables will be used in - future connections. It returns the previous setting for default host. +This method sets the default database password for new connections. If +``None`` is supplied as parameter, environment variables will be used in +future connections. It returns the previous setting for default host. -escape_string - escape a string for use within SQL --------------------------------------------------- -Syntax:: +escape_string -- escape a string for use within SQL +--------------------------------------------------- - escape_string(string) +.. function:: escape_string(string) -Parameters: - :string: the string that is to be escaped + Escape a string for use within SQL -Return type: - :str: the escaped string + :param str string: the string that is to be escaped + :returns: the escaped string + :rtype: str + :raises TypeError: bad argument type, or too many arguments -Exceptions raised: - :TypeError: bad argument type, or too many arguments +This function escapes a string for use within an SQL command. +This is useful when inserting data values as literal constants +in SQL commands. Certain characters (such as quotes and backslashes) +must be escaped to prevent them from being interpreted specially +by the SQL parser. :func:`escape_string` performs this operation. +Note that there is also a :class:`pgobject` method with the same name +which takes connection properties into account. -Description: - This function escapes a string for use within an SQL command. - This is useful when inserting data values as literal constants - in SQL commands. Certain characters (such as quotes and backslashes) - must be escaped to prevent them from being interpreted specially - by the SQL parser. `escape_string` performs this operation. - Note that there is also a `pgobject` method with the same name - which takes connection properties into account. +.. note:: -.. caution:: It is especially important to do proper escaping when - handling strings that were received from an untrustworthy source. - Otherwise there is a security risk: you are vulnerable to "SQL injection" - attacks wherein unwanted SQL commands are fed to your database. + It is especially important to do proper escaping when + handling strings that were received from an untrustworthy source. + Otherwise there is a security risk: you are vulnerable to "SQL injection" + attacks wherein unwanted SQL commands are fed to your database. Example:: - name = raw_input("Name? ") - phone = con.query("select phone from employees" - " where name='%s'" % escape_string(name)).getresult() + name = raw_input("Name? ") + phone = con.query("select phone from employees where name='%s'" + % escape_string(name)).getresult() + +escape_bytea -- escape binary data for use within SQL +----------------------------------------------------- + +.. function:: escape_bytea(datastring) + + escape binary data for use within SQL as type ``bytea`` + + :param str datastring: string containing the binary data that is to be escaped + :returns: the escaped string + :rtype: str + :raises TypeError: bad argument type, or too many arguments + +Escapes binary data for use within an SQL command with the type ``bytea``. +As with :func:`escape_string`, this is only used when inserting data directly +into an SQL command string. +Note that there is also a :class:`pgobject` method with the same name +which takes connection properties into account. + +Example:: -escape_bytea - escape binary data for use within SQL as type `bytea` --------------------------------------------------------------------- -Syntax:: + picture = open('garfield.gif', 'rb').read() + con.query("update pictures set img='%s' where name='Garfield'" + % escape_bytea(picture)) - escape_bytea(datastring) +unescape_bytea -- unescape data that has been retrieved as text +--------------------------------------------------------------- -Parameters: - :datastring: string containing the binary data that is to be escaped +.. function:: unescape_bytea(string) -Return type: - :str: the escaped string + Unescape ``bytea`` data that has been retrieved as text -Exceptions raised: - :TypeError: bad argument type, or too many arguments + :param str datastring: the ``bytea`` data string that has been retrieved as text + :returns: byte string containing the binary data + :rtype: str + :raises TypeError: bad argument type, or too many arguments -Description: - Escapes binary data for use within an SQL command with the type `bytea`. - As with `escape_string`, this is only used when inserting data directly - into an SQL command string. - Note that there is also a `pgobject` method with the same name - which takes connection properties into account. +Converts an escaped string representation of binary data into binary +data -- the reverse of :func:`escape_bytea`. This is needed when retrieving +``bytea`` data with one of the :meth:`pgqueryobject.getresult`, +:meth:`pgqueryobject.dictresult` or :meth:`pgqueryobject.namedresult` methods. Example:: - picture = file('garfield.gif', 'rb').read() - con.query("update pictures set img='%s' where name='Garfield'" - % escape_bytea(picture)) + picture = unescape_bytea(con.query( + "select img from pictures where name='Garfield'").getresult[0][0]) + open('garfield.gif', 'wb').write(picture) -unescape_bytea -- unescape `bytea` data that has been retrieved as text ------------------------------------------------------------------------ -Syntax:: +get/set_decimal -- decimal type to be used for numeric values +------------------------------------------------------------- - unescape_bytea(string) +.. function:: get_decimal() -Parameters: - :datastring: the `bytea` data string that has been retrieved as text + Get the decimal type to be used for numeric values -Return type: - :str: string containing the binary data + :returns: the Python class used for PostgreSQL numeric values + :rtype: class -Exceptions raised: - :TypeError: bad argument type, or too many arguments +This function returns the Python class that is used by PyGreSQL to hold +PostgreSQL numeric values. The default class is :class:`decimal.Decimal` +if available, otherwise the :class:`float` type is used. -Description: - Converts an escaped string representation of binary data into binary - data - the reverse of `escape_bytea`. This is needed when retrieving - `bytea` data with the `getresult()` or `dictresult()` method. +.. function:: set_decimal(cls) -Example:: + Set a decimal type to be used for numeric values - picture = unescape_bytea(con.query( - "select img from pictures where name='Garfield'").getresult[0][0]) - file('garfield.gif', 'wb').write(picture) + :param class cls: the Python class to be used for PostgreSQL numeric values -set_decimal -- set a decimal type to be used for numeric values ---------------------------------------------------------------- -Syntax:: +This function can be used to specify the Python class that shall +be used by PyGreSQL to hold PostgreSQL numeric values. +The default class is :class:`decimal.Decimal` if available, +otherwise the :class:`float` type is used. + +get/set_decimal_point -- decimal mark used for monetary values +-------------------------------------------------------------- + +.. function:: get_decimal_point() + + Get the decimal mark used for monetary values + + :returns: string with one character representing the decimal mark + :rtype: str + +This function returns the decimal mark used by PyGreSQL to interpret +PostgreSQL monetary values when converting them to decimal numbers. +The default setting is ``'.'`` as a decimal point. This setting is not +adapted automatically to the locale used by PostGreSQL, but you can +use ``set_decimal()`` to set a different decimal mark manually. A return +value of ``None`` means monetary values are not interpreted as decimal +numbers, but returned as strings including the formatting and currency. - set_decimal(cls) +.. function:: set_decimal_point(string) -Parameters: - :cls: the Python class to be used for PostgreSQL numeric values + Specify which decimal mark is used for interpreting monetary values -Description: - This function can be used to specify the Python class that shall be - used by PyGreSQL to hold PostgreSQL numeric values. The default class - is decimal.Decimal if available, otherwise the float type is used. + :param str string: string with one character representing the decimal mark -set_namedresult -- set a function that will convert to named tuples +This function can be used to specify the decimal mark used by PyGreSQL +to interpret PostgreSQL monetary values. The default value is '.' as +a decimal point. This value is not adapted automatically to the locale +used by PostGreSQL, so if you are dealing with a database set to a +locale that uses a ``','`` instead of ``'.'`` as the decimal point, +then you need to call ``set_decimal(',')`` to have PyGreSQL interpret +monetary values correctly. If you don't want money values to be converted +to decimal numbers, then you can call ``set_decimal(None)``, which will +cause PyGreSQL to return monetary values as strings including their +formatting and currency. + +get/set_bool -- whether boolean values are returned as bool objects ------------------------------------------------------------------- -Syntax:: - set_namedresult(func) +.. function:: get_bool() + + Check whether boolean values are returned as bool objects + + :returns: whether or not bool objects will be returned + :rtype: bool + +This function checks whether PyGreSQL returns PostgreSQL boolean +values converted to Python bool objects, or as ``'f'`` and ``'t'`` +strings which are the values used internally by PostgreSQL. By default, +conversion to bool objects is not activated, but you can enable +this with the ``set_bool()`` method. + +.. function:: set_bool(on) + + Set whether boolean values are returned as bool objects + + :param on: whether or not bool objects shall be returned + +This function can be used to specify whether PyGreSQL shall return +PostgreSQL boolean values converted to Python bool objects, or as +``'f'`` and ``'t'`` strings which are the values used internally by PostgreSQL. +By default, conversion to bool objects is not activated, but you can +enable this by calling ``set_bool(True)``. + +get/set_namedresult -- conversion to named tuples +------------------------------------------------- -Parameters: - :func: the function to be used to convert results to named tuples +.. function:: get_namedresult() -Description: - You can use this if you want to create different kinds of named tuples. + Get the function that converts to named tuples + +This function returns the function used by PyGreSQL to construct the +result of the :meth:`pgqueryobject.namedresult` method. + +.. function:: set_namedresult(func) + + Set a function that will convert to named tuples + + :param func: the function to be used to convert results to named tuples + +You can use this if you want to create different kinds of named tuples +returned by the :meth:`pgqueryobject.namedresult` method. Module constants @@ -405,430 +486,418 @@ They are intended to be used as parameters for methods calls. You should refer to the libpq description in the PostgreSQL user manual for more information about them. These constants are: -:version, __version__: constants that give the current version. -:INV_READ, INV_WRITE: large objects access modes, - used by `(pgobject.)locreate` and `(pglarge.)open` -:SEEK_SET, SEEK_CUR, SEEK_END: positional flags, - used by `(pglarge.)seek` +.. data:: version, __version__ + + constants that give the current version + +.. data:: INV_READ, INV_WRITE + + large objects access modes, + used by :meth:`pgobject.locreate` and :meth:`pglarge.open` + +.. data:: SEEK_SET, SEEK_CUR, SEEK_END: + positional flags, used by :meth:`pglarge.seek` + + +pgobject -- The connection object +================================= + +.. class:: pgobject -Connection objects: pgobject -============================ This object handles a connection to a PostgreSQL database. It embeds and hides all the parameters that define this connection, thus just leaving really significant parameters in function calls. -.. caution:: Some methods give direct access to the connection socket. - *Do not use them unless you really know what you are doing.* - If you prefer disabling them, - set the -DNO_DIRECT option in the Python setup file. +.. note:: - **These methods are specified by the tag [DA].** + Some methods give direct access to the connection socket. + *Do not use them unless you really know what you are doing.* + If you prefer disabling them, + set the ``-DNO_DIRECT`` option in the Python setup file. + These methods are specified by the tag [DA]. -.. note:: Some other methods give access to large objects - (refer to PostgreSQL user manual for more information about these). - If you want to forbid access to these from the module, - set the -DNO_LARGE option in the Python setup file. +.. note:: - **These methods are specified by the tag [LO].** + Some other methods give access to large objects + (refer to PostgreSQL user manual for more information about these). + If you want to forbid access to these from the module, + set the ``-DNO_LARGE`` option in the Python setup file. + These methods are specified by the tag [LO]. -query - executes a SQL command string +query -- execute a SQL command string ------------------------------------- -Syntax:: - - query(command, [args]) - -Parameters: - :command: SQL command (string) - :args: optional positional arguments - -Return type: - :pgqueryobject, None: result values - -Exceptions raised: - :TypeError: bad argument type, or too many arguments - :TypeError: invalid connection - :ValueError: empty SQL query or lost connection - :pg.ProgrammingError: error in query - :pg.InternalError: error during query processing - -Description: - This method simply sends a SQL query to the database. If the query is an - insert statement that inserted exactly one row into a table that has OIDs, the - return value is the OID of the newly inserted row. If the query is an update - or delete statement, or an insert statement that did not insert exactly one - row in a table with OIDs, then the number of rows affected is returned as a - string. If it is a statement that returns rows as a result (usually a select - statement, but maybe also an "insert/update ... returning" statement), this - method returns a `pgqueryobject` that can be accessed via the `getresult()`, - `dictresult()` or `namedresult()` methods or simply printed. Otherwise, it - returns `None`. - - The query may optionally contain positional parameters of the form `$1`, - `$2`, etc instead of literal data, and the values supplied as a tuple. - The values are substituted by the database in such a way that they don't - need to be escaped, making this an effective way to pass arbitrary or - unknown data without worrying about SQL injection or syntax errors. - - When the database could not process the query, a `pg.ProgrammingError` or - a `pg.InternalError` is raised. You can check the "SQLSTATE" code of this - error by reading its `sqlstate` attribute. + +.. method:: pgobject.query(command, [args]) + + Execute a SQL command string + + :param str command: SQL command + :param args: optional positional arguments + :returns: result values + :rtype: :class:`pgqueryobject`, None + :raises TypeError: bad argument type, or too many arguments + :raises TypeError: invalid connection + :raises ValueError: empty SQL query or lost connection + :raises pg.ProgrammingError: error in query + :raises pg.InternalError: error during query processing + +This method simply sends a SQL query to the database. If the query is an +insert statement that inserted exactly one row into a table that has OIDs, the +return value is the OID of the newly inserted row. If the query is an update +or delete statement, or an insert statement that did not insert exactly one +row in a table with OIDs, then the number of rows affected is returned as a +string. If it is a statement that returns rows as a result (usually a select +statement, but maybe also an ``"insert/update ... returning"`` statement), +this method returns a :class:`pgqueryobject` that can be accessed via the +:meth:`pgqueryobject.getresult`, :meth:`pgqueryobject.dictresult` or +:meth:`pgqueryobject.namedresult` methods or simply printed. +Otherwise, it returns ``None``. + +The query may optionally contain positional parameters of the form ``$1``, +``$2``, etc instead of literal data, and the values supplied as a tuple. +The values are substituted by the database in such a way that they don't +need to be escaped, making this an effective way to pass arbitrary or +unknown data without worrying about SQL injection or syntax errors. + +When the database could not process the query, a :exc:`pg.ProgrammingError` or +a :exc:`pg.InternalError` is raised. You can check the ``SQLSTATE`` code of +this error by reading its :attr:`sqlstate` attribute. Example:: - name = raw_input("Name? ") - phone = con.query("select phone from employees" - " where name=$1", (name, )).getresult() + name = raw_input("Name? ") + phone = con.query("select phone from employees where name=$1", + (name,)).getresult() -reset - resets the connection +reset -- reset the connection ----------------------------- -Syntax:: - reset() +.. method:: pgobject.reset() -Parameters: - None + Reset the :mod:`pg` connection -Return type: - None + :rtype: None + :raises TypeError: too many (any) arguments + :raises TypeError: invalid connection -Exceptions raised: - :TypeError: too many (any) arguments - :TypeError: invalid connection +This method resets the current database connection. -Description: - This method resets the current database connection. +cancel -- abandon processing of current SQL command +--------------------------------------------------- -cancel - abandon processing of current SQL command --------------------------------------------------- -Syntax:: +.. method:: pgobject.cancel() - cancel() + :rtype: None + :raises TypeError: too many (any) arguments + :raises TypeError: invalid connection -Parameters: - None +This method requests that the server abandon processing +of the current SQL command. -Return type: - None +close -- close the database connection +-------------------------------------- -Exceptions raised: - :TypeError: too many (any) arguments - :TypeError: invalid connection +.. method:: pgobject.close() -Description: - This method requests that the server abandon processing - of the current SQL command. + Close the :mod:`pg` connection -close - close the database connection -------------------------------------- -Syntax:: + :rtype: None + :raises TypeError: too many (any) arguments - close() +This method closes the database connection. The connection will +be closed in any case when the connection is deleted but this +allows you to explicitly close it. It is mainly here to allow +the DB-SIG API wrapper to implement a close function. -Parameters: - None +fileno -- returns the socket used to connect to the database +------------------------------------------------------------ -Return type: - None +.. method:: pgobject.fileno() -Exceptions raised: - :TypeError: too many (any) arguments + Return the socket used to connect to the database -Description: - This method closes the database connection. The connection will - be closed in any case when the connection is deleted but this - allows you to explicitly close it. It is mainly here to allow - the DB-SIG API wrapper to implement a close function. + :returns: the socket id of the database connection + :rtype: int + :raises TypeError: too many (any) arguments + :raises TypeError: invalid connection -fileno - returns the socket used to connect to the database ------------------------------------------------------------ -Syntax:: +This method returns the underlying socket id used to connect +to the database. This is useful for use in select calls, etc. - fileno() +getnotify -- get the last notify from the server +------------------------------------------------ -Parameters: - None +.. method:: pgobject.getnotify() -Exceptions raised: - :TypeError: too many (any) arguments - :TypeError: invalid connection + Get the last notify from the server -Description: - This method returns the underlying socket id used to connect - to the database. This is useful for use in select calls, etc. + :returns: last notify from server + :rtype: tuple, None + :raises TypeError: too many parameters + :raises TypeError: invalid connection -getnotify - gets the last notify from the server ------------------------------------------------- -Syntax:: +This method tries to get a notify from the server (from the SQL statement +NOTIFY). If the server returns no notify, the methods returns None. +Otherwise, it returns a tuple (triplet) *(relname, pid, extra)*, where +*relname* is the name of the notify, *pid* is the process id of the +connection that triggered the notify, and *extra* is a payload string +that has been sent with the notification. Remember to do a listen query +first, otherwise :meth:`pgobject.getnotify` will always return ``None``. - getnotify() +inserttable -- insert a list into a table +----------------------------------------- -Parameters: - None +.. method:: pgobject.inserttable(table, values) -Return type: - :tuple, None: last notify from server + Insert a Python list into a database table -Exceptions raised: - :TypeError: too many parameters - :TypeError: invalid connection + :param str table: the table name + :param list values: list of rows values + :rtype: None + :raises TypeError: invalid connection, bad argument type, or too many arguments + :raises MemoryError: insert buffer could not be allocated + :raises ValueError: unsupported values -Description: - This methods try to get a notify from the server (from the SQL statement - NOTIFY). If the server returns no notify, the methods returns None. - Otherwise, it returns a tuple (triplet) `(relname, pid, extra)`, where - `relname` is the name of the notify, `pid` is the process id of the - connection that triggered the notify, and `extra` is a payload string - that has been sent with the notification. Remember to do a listen query - first, otherwise getnotify() will always return `None`. +This method allows to *quickly* insert large blocks of data in a table: +It inserts the whole values list into the given table. Internally, it +uses the COPY command of the PostgreSQL database. The list is a list +of tuples/lists that define the values for each inserted row. The rows +values may contain string, integer, long or double (real) values. -inserttable - insert a list into a table ----------------------------------------- -Syntax:: +.. note:: - inserttable(table, values) + **Be very careful**: + This method doesn't type check the fields according to the table definition; + it just look whether or not it knows how to handle such types. -Parameters: - :table: the table name (string) - :values: list of rows values (list) +get/set_notice_receiver -- custom notice receiver +------------------------------------------------- -Return type: - None +.. method:: pgobject.get_notice_receiver() -Exceptions raised: - :TypeError: invalid connection, bad argument type, or too many arguments - :MemoryError: insert buffer could not be allocated - :ValueError: unsupported values + Get the current notice receiver -Description: - This method allow to *quickly* insert large blocks of data in a table: - It inserts the whole values list into the given table. Internally, it - uses the COPY command of the PostgreSQL database. The list is a list - of tuples/lists that define the values for each inserted row. The rows - values may contain string, integer, long or double (real) values. + :returns: the current notice receiver callable + :rtype: callable, None + :raises TypeError: too many (any) arguments -.. caution:: *Be very careful*: - 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. +This method gets the custom notice receiver callback function that has +been set with :meth:`pgobject.set_notice_receiver`, or ``None`` if no +custom notice receiver has ever been set on the connection. -set_notice_receiver - set a custom notice receiver --------------------------------------------------- -Syntax:: +.. method:: pgobject.set_notice_receiver(proc) - set_notice_receiver(proc) + Set a custom notice receiver -Parameters: - :proc: the custom notice receiver callback function + :param proc: the custom notice receiver callback function + :rtype: None + :raises TypeError: the specified notice receiver is not callable -Return type: - None +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 :class:`pgnotice` object, which provides the following read-only +attributes: -Exceptions raised: - :TypeError: the specified notice receiver is not callable + .. attribute:: pgnotice.pgcnx -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: + the connection - :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 + .. attribute:: pgnotice.message -get_notice_receiver - get the current notice receiver ------------------------------------------------------ -Syntax:: + the full message with a trailing newline - get_notice_receiver() + .. attribute:: pgnotice.severity -Parameters: - None + the level of the message, e.g. 'NOTICE' or 'WARNING' -Return type: - :callable, None: the current notice receiver callable + .. attribute:: pgnotice.primary -Exceptions raised: - :TypeError: too many (any) arguments + the primary human-readable error message -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. + .. attribute:: pgnotice.detail -putline - writes a line to the server socket [DA] -------------------------------------------------- -Syntax:: + an optional secondary error message + + .. attribute:: pgnotice.hint - putline(line) + an optional suggestion what to do about the problem -Parameters: - :line: line to be written (string) +putline -- write a line to the server socket [DA] +------------------------------------------------- -Return type: - None +.. method:: pgobject.putline(line) -Exceptions raised: - :TypeError: invalid connection, bad parameter type, or too many parameters + Write a line to the server socket -Description: - This method allows to directly write a string to the server socket. + :param str line: line to be written + :rtype: None + :raises TypeError: invalid connection, bad parameter type, or too many parameters -getline - gets a line from server socket [DA] ---------------------------------------------- -Syntax:: +This method allows to directly write a string to the server socket. - getline() +getline -- get a line from server socket [DA] +--------------------------------------------- -Parameters: - None +.. method:: pgobject.getline() -Return type: - :string: the line read + Get a line from server socket -Exceptions raised: - :TypeError: invalid connection - :TypeError: too many parameters - :MemoryError: buffer overflow + :returns: the line read + :rtype: str + :raises TypeError: invalid connection + :raises TypeError: too many parameters + :raises MemoryError: buffer overflow -Description: - This method allows to directly read a string from the server socket. +This method allows to directly read a string from the server socket. -endcopy - synchronizes client and server [DA] +endcopy -- synchronize client and server [DA] --------------------------------------------- -Syntax:: - endcopy() +.. method:: pgobject.endcopy() -Parameters: - None + Synchronize client and server -Return type: - None + :rtype: None + :raises TypeError: invalid connection + :raises TypeError: too many parameters -Exceptions raised: - :TypeError: invalid connection - :TypeError: too many parameters +The use of direct access methods may desynchronize client and server. +This method ensure that client and server will be synchronized. -Description: - The use of direct access methods may desynchonize client and server. - This method ensure that client and server will be synchronized. +locreate -- create a large object in the database [LO] +------------------------------------------------------ -locreate - create a large object in the database [LO] ------------------------------------------------------ -Syntax:: +.. method:: pgobject.locreate(mode) + + Create a large object in the database + + :param int mode: large object create mode + :returns: object handling the PostGreSQL large object + :rtype: :class:`pglarge` + :raises TypeError: invalid connection, bad parameter type, or too many parameters + :raises pg.OperationalError: creation error - locreate(mode) +This method creates a large object in the database. The mode can be defined +by OR-ing the constants defined in the :mod:`pg` module (:const:`INV_READ`, +:const:`INV_WRITE` and :const:`INV_ARCHIVE`). Please refer to PostgreSQL +user manual for a description of the mode values. -Parameters: - :mode: large object create mode +getlo -- build a large object from given oid [LO] +------------------------------------------------- -Return type: - :pglarge: object handling the PostGreSQL large object +.. method:: pgobject.getlo(oid) -Exceptions raised: + Create a large object in the database - :TypeError: invalid connection, bad parameter type, or too many parameters - :pg.OperationalError: creation error + :param int oid: OID of the existing large object + :returns: object handling the PostGreSQL large object + :rtype: :class:`pglarge` + :raises TypeError: invalid connection, bad parameter type, or too many parameters + :raises ValueError: bad OID value (0 is invalid_oid) -Description: - This method creates a large object in the database. The mode can be defined - by OR-ing the constants defined in the pg module (INV_READ, INV_WRITE and - INV_ARCHIVE). Please refer to PostgreSQL user manual for a description of - the mode values. +This method allows to reuse a formerly created large object through the +:class:`pglarge` interface, providing the user have its OID. -getlo - build a large object from given oid [LO] +loimport -- import a file to a large object [LO] ------------------------------------------------ -Syntax:: - getlo(oid) +.. method:: pgobject.loimport(name) -Parameters: - :oid: OID of the existing large object (integer) + Import a file to a large object -Return type: - :pglarge: object handling the PostGreSQL large object + :param str name: the name of the file to be imported + :returns: object handling the PostGreSQL large object + :rtype: :class:`pglarge` + :raises TypeError: invalid connection, bad argument type, or too many arguments + :raises pg.OperationalError: error during file import -Exceptions raised: - :TypeError: invalid connection, bad parameter type, or too many parameters - :ValueError: bad OID value (0 is invalid_oid) +This methods allows to create large objects in a very simple way. You just +give the name of a file containing the data to be used. -Description: - This method allows to reuse a formerly created large object through the - `pglarge` interface, providing the user have its OID. +Object attributes +----------------- +Every :class:`pgobject` defines a set of read-only attributes that describe +the connection and its status. These attributes are: -loimport - import a file to a large object [LO] ------------------------------------------------ -Syntax:: +.. attribute:: pgobject.host - loimport(name) + the host name of the server (str) -Parameters: - :name: the name of the file to be imported (string) +.. attribute:: pgobject.port -Return type: - :pglarge: object handling the PostGreSQL large object + the port of the server (int) -Exceptions raised: - :TypeError: invalid connection, bad argument type, or too many arguments - :pg.OperationalError: error during file import +.. attribute:: pgobject.db -Description: - This methods allows to create large objects in a very simple way. You just - give the name of a file containing the data to be use. + the selected database (str) -Object attributes ------------------ -Every `pgobject` defines a set of read-only attributes that describe the -connection and its status. These attributes are: +.. attribute:: pgobject.options + + the connection options (str) + +.. attribute:: pgobject.tty + + the connection debug terminal (str) + +.. attribute:: pgobject.user + + user name on the database system (str) + +.. attribute:: pgobject.protocol_version + + the frontend/backend protocol being used (int) + +.. attribute:: pgobject.server_version - :host: the host name of the server (string) - :port: the port of the server (integer) - :db: the selected database (string) - :options: the connection options (string) - :tty: the connection debug terminal (string) - :user: user name on the database system (string) - :protocol_version: the frontend/backend protocol being used (integer) - :server_version: the backend version (integer, e.g. 80305 for 8.3.5) - :status: the status of the connection (integer: 1 - OK, 0 - bad) - :error: the last warning/error message from the server (string) + the backend version (int, e.g. 80305 for 8.3.5) + +.. attribute:: pgobject.status + + the status of the connection (int: 1 = OK, 0 = bad) + +.. attribute:: pgobject.error + + the last warning/error message from the server (str) The DB wrapper class ==================== -The `pgobject` methods are wrapped in the class `DB`. + +.. class:: DB + +The :class:`pgobject` methods are wrapped in the class :class:`DB`. The preferred way to use this module is as follows:: - import pg + import pg - db = pg.DB(...) # see below + db = pg.DB(...) # see below - for r in db.query( # just for example - """SELECT foo,bar + for r in db.query( # just for example + """SELECT foo,bar FROM foo_bar_table WHERE foo !~ bar""" - ).dictresult(): + ).dictresult(): - print '%(foo)s %(bar)s' % r + print '%(foo)s %(bar)s' % r This class can be subclassed as in this example:: - import pg + import pg - class DB_ride(pg.DB): - """This class encapsulates the database functions and the specific - methods for the ride database.""" + class DB_ride(pg.DB): + """Ride database wrapper - def __init__(self): - """Opens a database connection to the rides database""" + This class encapsulates the database functions and the specific + methods for the ride database.""" - pg.DB.__init__(self, dbname = 'ride') - self.query("""SET DATESTYLE TO 'ISO'""") + def __init__(self): + """Open a database connection to the rides database""" + pg.DB.__init__(self, dbname='ride') + self.query("SET DATESTYLE TO 'ISO'") [Add or override methods here] @@ -836,672 +905,578 @@ The following describes the methods and variables of this class. Initialization -------------- -The DB class is initialized with the same arguments as the connect -function described in section 2. It also initializes a few -internal variables. The statement `db = DB()` will open the -local database with the name of the user just like connect() does. +The :class:`DB` class is initialized with the same arguments as the +:func:`connect` function described above. It also initializes a few +internal variables. The statement ``db = DB()`` will open the local +database with the name of the user just like ``connect()`` does. -You can also initialize the DB class with an existing `_pg` or `pgdb` +You can also initialize the DB class with an existing :mod:`pg` or :mod:`pgdb` connection. Pass this connection as a single unnamed parameter, or as a -single parameter named `db`. This allows you to use all of the methods +single parameter named ``db``. This allows you to use all of the methods of the DB class with a DB-API 2 compliant connection. Note that the -`close()` and `reopen()` methods are inoperative in this case. +:meth:`pgobject.close` and :meth:`pgobject.reopen` methods are inoperative +in this case. +pkey -- return the primary key of a table +----------------------------------------- +.. method:: DB.pkey(table) -pkey - return the primary key of a table ----------------------------------------- -Syntax:: + Return the primary key of a table - pkey(table) + :param str table: name of table + :returns: Name of the field which is the primary key of the table + :rtype: str -Parameters: - :table: name of table +This method returns the primary key of a table. For composite primary +keys, the return value will be a frozenset. Note that this raises an +exception if the table does not have a primary key. -Return type: - :string: Name of the field which is the primary key of the table +get_databases -- get list of databases in the system +---------------------------------------------------- -Description: - This method returns the primary key of a table. For composite primary - keys, the return value will be a frozenset. Note that this raises an - exception if the table does not have a primary key. +.. method:: DB.get_databases() -get_databases - get list of databases in the system ---------------------------------------------------- -Syntax:: + Get the list of databases in the system - get_databases() + :returns: all databases in the system + :rtype: list -Parameters: - None +Although you can do this with a simple select, it is added here for +convenience. -Return type: - :list: all databases in the system +get_relations -- get list of relations in connected database +------------------------------------------------------------ -Description: - Although you can do this with a simple select, it is added here for - convenience. +.. method:: DB.get_relations(kinds) -get_relations - get list of relations in connected database ------------------------------------------------------------ -Syntax:: + Get the list of relations in connected database - get_relations(kinds) + :param str kinds: a string or sequence of type letters + :returns: all relations of the given kinds in the database + :rtype: list -Parameters: - :kinds: a string or sequence of type letters +The type letters are ``r`` = ordinary table, ``i`` = index, ``S`` = sequence, +``v`` = view, ``c`` = composite type, ``s`` = special, ``t`` = TOAST table. +If `kinds` is None or an empty string, all relations are returned (this is +also the default). Although you can do this with a simple select, it is +added here for convenience. -Description: - The type letters are `r` = ordinary table, `i` = index, `S` = sequence, - `v` = view, `c` = composite type, `s` = special, `t` = TOAST table. - If `kinds` is None or an empty string, all relations are returned (this is - also the default). Although you can do this with a simple select, it is - added here for convenience. +get_tables -- get list of tables in connected database +------------------------------------------------------ -get_tables - get list of tables in connected database ------------------------------------------------------ -Syntax:: +.. method:: DB.get_tables() - get_tables() + Get the list of tables in connected database -Parameters: - None + :returns: all tables in connected database + :rtype: list -Returns: - :list: all tables in connected database +This is a shortcut for ``get_relations('r')`` that has been added for +convenience. -Description: - Although you can do this with a simple select, it is added here for - convenience. +get_attnames -- get the attribute names of a table +-------------------------------------------------- -get_attnames - get the attribute names of a table -------------------------------------------------- -Syntax:: +.. method:: DB.get_attnames(table) - get_attnames(table) + Get the attribute names of a table -Parameters: - :table: name of table + :param str table: name of table + :returns: A dictionary -- the keys are the attribute names, + the values are the type names of the attributes. -Returns: - :dictionary: The keys are the attribute names, - the values are the type names of the attributes. +Given the name of a table, digs out the set of attribute names. -Description: - Given the name of a table, digs out the set of attribute names. +has_table_privilege -- check whether current user has specified table privilege +------------------------------------------------------------------------------- -has_table_privilege - check whether current user has specified table privilege ------------------------------------------------------------------------------- -Syntax:: +.. method:: DB.has_table_privilege(table, privilege) - has_table_privilege(table, privilege) + Check whether current user has specified table privilege -Parameters: - :table: name of table - :privilege: privilege to be checked - default is 'select' + :param str table: the name of the table + :param str privilege: privilege to be checked -- default is 'select' + :returns: whether current user has specified table privilege + :rtype: bool -Description: - Returns True if the current user has the specified privilege for the table. +Returns True if the current user has the specified privilege for the table. -get - get a row from a database table or view ---------------------------------------------- -Syntax:: - - get(table, arg, [keyname]) - -Parameters: - :table: name of table or view - :arg: either a dictionary or the value to be looked up - :keyname: name of field to use as key (optional) - -Return type: - :dictionary: The keys are the attribute names, - the values are the row values. - -Description: - This method is the basic mechanism to get a single row. It assumes - that the key specifies a unique row. If `keyname` is not specified - then the primary key for the table is used. If `arg` is a dictionary - then the value for the key is taken from it and it is modified to - include the new values, replacing existing values where necessary. - For a composite key, `keyname` can also be a sequence of key names. - The OID is also put into the dictionary if the table has one, but in - order to allow the caller to work with multiple tables, it is munged - as `oid(schema.table)`. - -insert - insert a row into a database table -------------------------------------------- -Syntax:: +get -- get a row from a database table or view +---------------------------------------------- - insert(table, [d,] [key = val, ...]) +.. method:: DB.get(table, arg, [keyname]) -Parameters: - :table: name of table - :d: optional dictionary of values + Get a row from a database table or view -Return type: - :dictionary: The dictionary of values inserted + :param str table: name of table or view + :param arg: either a dictionary or the value to be looked up + :param str keyname: name of field to use as key (optional) + :returns: A dictionary - the keys are the attribute names, + the values are the row values. -Description: - This method inserts a row into a table. If the optional dictionary is - not supplied then the required values must be included as keyword/value - pairs. If a dictionary is supplied then any keywords provided will be - added to or replace the entry in the dictionary. +This method is the basic mechanism to get a single row. It assumes +that the key specifies a unique row. If *keyname* is not specified, +then the primary key for the table is used. If *arg* is a dictionary +then the value for the key is taken from it and it is modified to +include the new values, replacing existing values where necessary. +For a composite key, *keyname* can also be a sequence of key names. +The OID is also put into the dictionary if the table has one, but in +order to allow the caller to work with multiple tables, it is munged +as ``oid(schema.table)``. - The dictionary is then, if possible, reloaded with the values actually - inserted in order to pick up values modified by rules, triggers, etc. +insert -- insert a row into a database table +-------------------------------------------- - Note: The method currently doesn't support insert into views - although PostgreSQL does. +.. method:: DB.insert(table, [d,] [key = val, ...]) -update - update a row in a database table ------------------------------------------ -Syntax:: + Insert a row into a database table - update(table, [d,] [key = val, ...]) + :param str table: name of table + :param dict d: optional dictionary of values + :returns: the inserted values + :rtype: dict -Parameters: - :table: name of table - :d: optional dictionary of values +This method inserts a row into a table. If the optional dictionary is +not supplied then the required values must be included as keyword/value +pairs. If a dictionary is supplied then any keywords provided will be +added to or replace the entry in the dictionary. -Return type: - :dictionary: the new row +The dictionary is then, if possible, reloaded with the values actually +inserted in order to pick up values modified by rules, triggers, etc. -Description: - Similar to insert but updates an existing row. The update is based on the - OID value as munged by get or passed as keyword, or on the primary key of - the table. The dictionary is modified, if possible, to reflect any changes - caused by the update due to triggers, rules, default values, etc. +Note: The method currently doesn't support insert into views +although PostgreSQL does. - Like insert, the dictionary is optional and updates will be performed - on the fields in the keywords. There must be an OID or primary key - either in the dictionary where the OID must be munged, or in the keywords - where it can be simply the string "oid". +update -- update a row in a database table +------------------------------------------ -query - executes a SQL command string -------------------------------------- -Syntax:: +.. method:: DB.update(table, [d,] [key = val, ...]) + + Update a row in a database table + + :param str table: name of table + :param dict d: optional dictionary of values + :returns: the new row + :rtype: dict + +Similar to insert but updates an existing row. The update is based on the +OID value as munged by get or passed as keyword, or on the primary key of +the table. The dictionary is modified, if possible, to reflect any changes +caused by the update due to triggers, rules, default values, etc. - query(command, [arg1, [arg2, ...]]) +Like insert, the dictionary is optional and updates will be performed +on the fields in the keywords. There must be an OID or primary key +either in the dictionary where the OID must be munged, or in the keywords +where it can be simply the string 'oid'. -Parameters: - :command: SQL command (string) - :arg*: optional positional arguments +query -- execute a SQL command string +------------------------------------- + +.. method:: DB.query(command, [arg1, [arg2, ...]]) -Return type: - :pgqueryobject, None: result values + Execute a SQL command string -Exceptions raised: - :TypeError: bad argument type, or too many arguments - :TypeError: invalid connection - :ValueError: empty SQL query or lost connection - :pg.ProgrammingError: error in query - :pg.InternalError: error during query processing + :param str command: SQL command + :param arg*: optional positional arguments + :returns: result values + :rtype: :class:`pgqueryobject`, None + :raises TypeError: bad argument type, or too many arguments + :raises TypeError: invalid connection + :raises ValueError: empty SQL query or lost connection + :raises pg.ProgrammingError: error in query + :raises pg.InternalError: error during query processing -Description: - Similar to the pgobject function with the same name, except that positional - arguments can be passed either as a single list or tuple, or as individual - positional arguments +Similar to the :class:`pgobject` function with the same name, except that +positional arguments can be passed either as a single list or tuple, or as +individual positional arguments. Example:: - name = raw_input("Name? ") - phone = raw_input("Phone? " - rows = db.query("update employees set phone=$2" - " where name=$1", (name, phone)).getresult()[0][0] - # or - rows = db.query("update employees set phone=$2" - " where name=$1", name, phone).getresult()[0][0] + name = raw_input("Name? ") + phone = raw_input("Phone? ") + rows = db.query("update employees set phone=$2 where name=$1", + (name, phone)).getresult()[0][0] + # or + rows = db.query("update employees set phone=$2 where name=$1", + name, phone).getresult()[0][0] -clear - clears row values in memory +clear -- clear row values in memory ----------------------------------- -Syntax:: - clear(table, [a]) +.. method:: DB.clear(table, [a]) -Parameters: - :table: name of table - :a: optional dictionary of values + Clear row values in memory -Return type: - :dictionary: an empty row + :param str table: name of table + :param dict a: optional dictionary of values + :returns: an empty row + :rtype: dict -Description: - This method clears all the attributes to values determined by the types. - Numeric types are set to 0, Booleans are set to 'f', dates are set - to 'now()' and everything else is set to the empty string. - If the array argument is present, it is used as the array and any entries - matching attribute names are cleared with everything else left unchanged. +This method clears all the attributes to values determined by the types. +Numeric types are set to 0, Booleans are set to ``'f'``, dates are set +to ``'now()'`` and everything else is set to the empty string. +If the array argument is present, it is used as the array and any entries +matching attribute names are cleared with everything else left unchanged. - If the dictionary is not supplied a new one is created. +If the dictionary is not supplied a new one is created. -delete - delete a row from a database table -------------------------------------------- -Syntax:: +delete -- delete a row from a database table +-------------------------------------------- - delete(table, [d,] [key = val, ...]) +.. method:: DB.delete(table, [d,] [key = val, ...]) -Parameters: - :table: name of table - :d: optional dictionary of values + Delete a row from a database table -Returns: - None + :param str table: name of table + :param dict d: optional dictionary of values + :rtype: None -Description: - This method deletes the row from a table. It deletes based on the OID value - as munged by get or passed as keyword, or on the primary key of the table. - The return value is the number of deleted rows (i.e. 0 if the row did not - exist and 1 if the row was deleted). +This method deletes the row from a table. It deletes based on the OID value +as munged by get or passed as keyword, or on the primary key of the table. +The return value is the number of deleted rows (i.e. 0 if the row did not +exist and 1 if the row was deleted). -escape_string - escape a string for use within SQL --------------------------------------------------- -Syntax:: +escape_string -- escape a string for use within SQL +--------------------------------------------------- - escape_string(string) +.. method:: DB.escape_string(string) -Parameters: - :string: the string that is to be escaped + Escape a string for use within SQL -Return type: - :str: the escaped string + :param str string: the string that is to be escaped + :returns: the escaped string + :rtype: str -Description: - Similar to the module function with the same name, but the - behavior of this method is adjusted depending on the connection properties - (such as character encoding). +Similar to the module function with the same name, but the +behavior of this method is adjusted depending on the connection properties +(such as character encoding). -escape_bytea - escape binary data for use within SQL as type `bytea` --------------------------------------------------------------------- -Syntax:: +escape_bytea -- escape binary data for use within SQL +----------------------------------------------------- - escape_bytea(datastring) +.. method:: DB.escape_bytea(datastring) -Parameters: - :datastring: string containing the binary data that is to be escaped + Escape binary data for use within SQL as type ``bytea`` -Return type: - :str: the escaped string + :param str datastring: string containing the binary data that is to be escaped + :returns: the escaped string + :rtype: str -Description: - Similar to the module function with the same name, but the - behavior of this method is adjusted depending on the connection properties - (in particular, whether standard-conforming strings are enabled). +Similar to the module function with the same name, but the +behavior of this method is adjusted depending on the connection properties +(in particular, whether standard-conforming strings are enabled). -unescape_bytea -- unescape `bytea` data that has been retrieved as text ------------------------------------------------------------------------ -Syntax:: +unescape_bytea -- unescape data that has been retrieved as text +--------------------------------------------------------------- - unescape_bytea(string) +.. method:: DB.unescape_bytea(string) -Parameters: - :datastring: the `bytea` data string that has been retrieved as text + Unescape ``bytea`` data that has been retrieved as text -Return type: - :str: string containing the binary data + :param datastring: the ``bytea`` data string that has been retrieved as text + :returns: byte string containing the binary data + :rtype: str -Description: - See the module function with the same name. +See the module function with the same name. pgqueryobject methods ===================== -getresult - get query values as list of tuples ------------------------------------------------ -Syntax:: +.. class:: pgqueryobject - getresult() +The :class:`pgqueryobject` returned by :meth:`pgobject.query` and +:meth:`DB.query` provides the following methods for accessing +the results of the query: -Parameters: - None +getresult -- get query values as list of tuples +----------------------------------------------- -Return type: - :list: result values as a list of tuples +.. method:: pgqueryobject.getresult() -Exceptions raised: - :TypeError: too many (any) parameters - :MemoryError: internal memory error + Get query values as list of tuples -Description: - This method returns the list of the values returned by the query. - More information about this result may be accessed using listfields(), - fieldname() and fieldnum() methods. + :returns: result values as a list of tuples + :rtype: list + :raises TypeError: too many (any) parameters + :raises MemoryError: internal memory error -dictresult - get query values as list of dictionaries ------------------------------------------------------ -Syntax:: +This method returns the list of the values returned by the query. +More information about this result may be accessed using +:meth:`pgqueryobject.listfields`, :meth:`pgqueryobject.fieldname` +and :meth:`pgqueryobject.fieldnum` methods. - dictresult() - -Parameters: - None +dictresult -- get query values as list of dictionaries +------------------------------------------------------ -Return type: - :list: result values as a list of dictionaries +.. method:: pgqueryobject.dictresult() -Exceptions raised: - :TypeError: too many (any) parameters - :MemoryError: internal memory error + Get query values as list of dictionaries -Description: - This method returns the list of the values returned by the query - with each tuple returned as a dictionary with the field names - used as the dictionary index. + :returns: result values as a list of dictionaries + :rtype: list + :raises TypeError: too many (any) parameters + :raises MemoryError: internal memory error -namedresult - get query values as list of named tuples ------------------------------------------------------- -Syntax:: +This method returns the list of the values returned by the query +with each tuple returned as a dictionary with the field names +used as the dictionary index. - namedresult() +namedresult -- get query values as list of named tuples +------------------------------------------------------- -Parameters: - None +.. method:: pgqueryobject.namedresult() -Return type: - :list: result values as a list of named tuples + Get query values as list of named tuples -Exceptions raised: - :TypeError: too many (any) parameters - :TypeError: named tuples not supported - :MemoryError: internal memory error + :returns: result values as a list of named tuples + :rtype: list + :raises TypeError: too many (any) parameters + :raises TypeError: named tuples not supported + :raises MemoryError: internal memory error -Description: - This method returns the list of the values returned by the query - with each row returned as a named tuple with proper field names. +This method returns the list of the values returned by the query +with each row returned as a named tuple with proper field names. -listfields - lists fields names of previous query result +listfields -- list fields names of previous query result -------------------------------------------------------- -Syntax:: - - listfields() -Parameters: - None +.. method:: pgqueryobject.listfields() -Return type: - :list: field names + List fields names of previous query result -Exceptions raised: - :TypeError: too many parameters + :returns: field names + :rtype: list + :raises TypeError: too many parameters -Description: - This method returns the list of names of the fields defined for the - query result. The fields are in the same order as the result values. +This method returns the list of names of the fields defined for the +query result. The fields are in the same order as the result values. -fieldname, fieldnum - field name/number conversion --------------------------------------------------- -Syntax:: - - fieldname(i) +fieldname, fieldnum -- field name/number conversion +--------------------------------------------------- -Parameters: - :i: field number (integer) +.. method:: pgqueryobject.fieldname(num) -Return type: - :string: field name + Get field name from its number -Exceptions raised: - :TypeError: invalid connection, bad parameter type, or too many parameters - :ValueError: invalid field number + :param int num: field number + :returns: field name + :rtype: str + :raises TypeError: invalid connection, bad parameter type, or too many parameters + :raises ValueError: invalid field number -Description: - This method allows to find a field name from its rank number. It can be - useful for displaying a result. The fields are in the same order as the - result values. +This method allows to find a field name from its rank number. It can be +useful for displaying a result. The fields are in the same order as the +result values. -Syntax:: +.. method:: pgqueryobject.fieldnum(name) - fieldnum(name) + Get field number from its name -Parameters: - :name: field name (string) + :param str name: field name + :returns: field number + :rtype: int + :raises TypeError: invalid connection, bad parameter type, or too many parameters + :raises ValueError: unknown field name -Return type: - :integer: field number +This method returns a field number from its name. It can be used to +build a function that converts result list strings to their correct +type, using a hardcoded table definition. The number returned is the +field rank in the result values list. -Exceptions raised: - :TypeError: invalid connection, bad parameter type, or too many parameters - :ValueError: unknown field name +ntuples -- return number of tuples in query object +-------------------------------------------------- -Description: - This method returns a field number from its name. It can be used to - build a function that converts result list strings to their correct - type, using a hardcoded table definition. The number returned is the - field rank in the result values list. +.. method:: pgqueryobject.ntuples() -ntuples - return number of tuples in query object -------------------------------------------------- -Syntax:: + Return number of tuples in query object - ntuples() + :returns: number of tuples in :class:`pgqueryobject` + :rtype: int + :raises TypeError: Too many arguments. -Parameters: - None +This method returns the number of tuples found in a query. -Return type: - :integer: number of tuples in `pgqueryobject` -Exceptions raised: - :TypeError: Too many arguments. +pglarge -- Large Objects +======================== -Description: - This method returns the number of tuples found in a query. +.. class:: pglarge - -Large objects: pglarge -====================== -This object handles all the request concerning a PostgreSQL large object. It -embeds and hides all the "recurrent" variables (object OID and connection), -exactly in the same way `pgobjects` do, thus only keeping significant -parameters in function calls. It keeps a reference to the `pgobject` used for -its creation, sending requests though with its parameters. Any modification but -dereferencing the `pgobject` will thus affect the `pglarge` object. -Dereferencing the initial `pgobject` is not a problem since Python won't -deallocate it before the `pglarge` object dereference it. -All functions return a generic error message on call error, whatever the -exact error was. The `error` attribute of the object allows to get the exact -error message. +Objects that are instances of the class :class:`pglarge` are used to handle +all the requests concerning a PostgreSQL large object. These objects embed +and hide all the "recurrent" variables (object OID and connection), exactly +in the same way :class:`pgobject` instances do, thus only keeping significant +parameters in function calls. The class:`pglarge` object keeps a reference +to the :class:`pgobject` used for its creation, sending requests though with +its parameters. Any modification but dereferencing the :class:`pgobject` +will thus affect the :class:`pglarge` object. Dereferencing the initial +:class:`pgobject` is not a problem since Python won't deallocate it before +the :class:`pglarge` object dereferences it. All functions return a generic +error message on call error, whatever the exact error was. The :attr:`error` +attribute of the object allows to get the exact error message. See also the PostgreSQL programmer's guide for more information about the large object interface. -open - opens a large object +open -- open a large object --------------------------- -Syntax:: - - open(mode) -Parameters: - :mode: open mode definition (integer) +.. method:: pglarge.open(mode) -Return type: - None + Open a large object -Exceptions raised: - :TypeError: invalid connection, bad parameter type, or too many parameters - :IOError: already opened object, or open error + :param int mode: open mode definition + :rtype: None + :raises TypeError: invalid connection, bad parameter type, or too many parameters + :raises IOError: already opened object, or open error -Description: - This method opens a large object for reading/writing, in the same way than - the Unix open() function. The mode value can be obtained by OR-ing the - constants defined in the pgmodule (INV_READ, INV_WRITE). +This method opens a large object for reading/writing, in the same way than the +Unix open() function. The mode value can be obtained by OR-ing the constants +defined in the :mod:`pg` module (:const:`INV_READ`, :const:`INV_WRITE`). -close - closes a large object +close -- close a large object ----------------------------- -Syntax:: - - close() - -Parameters: - None -Return type: - None +.. method:: pglarge.close() -Exceptions raised: - :TypeError: invalid connection - :TypeError: too many parameters - :IOError: object is not opened, or close error + Close a large object -Description: - This method closes a previously opened large object, in the same way than - the Unix close() function. + :rtype: None + :raises TypeError: invalid connection + :raises TypeError: too many parameters + :raises IOError: object is not opened, or close error -read, write, tell, seek, unlink - file like large object handling ------------------------------------------------------------------ -Syntax:: +This method closes a previously opened large object, in the same way than +the Unix close() function. - read(size) +read, write, tell, seek, unlink -- file-like large object handling +------------------------------------------------------------------ -Parameters: - :size: maximal size of the buffer to be read +.. method:: pglarge.read(size) -Return type: - :sized string: the read buffer + Read data from large object -Exceptions raised: - :TypeError: invalid connection, invalid object, - bad parameter type, or too many parameters - :ValueError: if `size` is negative - :IOError: object is not opened, or read error + :param int size: maximal size of the buffer to be read + :returns: the read buffer + :rtype: str + :raises TypeError: invalid connection, invalid object, + bad parameter type, or too many parameters + :raises ValueError: if `size` is negative + :raises IOError: object is not opened, or read error -Description: - This function allows to read data from a large object, starting at current - position. +This function allows to read data from a large object, starting at current +position. -Syntax:: +.. method:: pglarge.write(string) - write(string) + Read data to large object -Parameters: - (sized) string - buffer to be written + :param str string: string buffer to be written + :rtype: None + :raises TypeError: invalid connection, bad parameter type, or too many parameters + :raises IOError: object is not opened, or write error -Return type: - None +This function allows to write data to a large object, starting at current +position. -Exceptions raised: - :TypeError: invalid connection, bad parameter type, or too many parameters - :IOError: object is not opened, or write error +.. method:: pglarge.seek(offset, whence) -Description: - This function allows to write data to a large object, starting at current - position. + Change current position in large object -Syntax:: + :param int offset: position offset + :param int whence: positional parameter + :returns: new position in object + :rtype: int + :raises TypeError: invalid connection or invalid object, + bad parameter type, or too many parameters + :raises IOError: object is not opened, or seek error - seek(offset, whence) +This method allows to move the position cursor in the large object. +The valid values for the whence parameter are defined as constants in the +:mod:`pg` module (:const:`SEEK_SET`, :const:`SEEK_CUR`, :const:`SEEK_END`). -Parameters: - :offset: position offset - :whence: positional parameter +.. method:: pglarge.tell() -Return type: - :integer: new position in object + Return current position in large object -Exceptions raised: - :TypeError: binvalid connection or invalid object, - bad parameter type, or too many parameters - :IOError: object is not opened, or seek error + :returns: current position in large object + :rtype: int + :raises TypeError: invalid connection or invalid object + :raises TypeError: too many parameters + :raises IOError: object is not opened, or seek error -Description: - This method allows to move the position cursor in the large object. The - whence parameter can be obtained by OR-ing the constants defined in the - `pg` module (`SEEK_SET`, `SEEK_CUR`, `SEEK_END`). +This method allows to get the current position in the large object. -Syntax:: +.. method:: pglarge.unlink() - tell() + Delete large object -Parameters: - None + :rtype: None + :raises TypeError: invalid connection or invalid object + :raises TypeError: too many parameters + :raises IOError: object is not closed, or unlink error -Return type: - :integer: current position in large object +This methods unlinks (deletes) the PostgreSQL large object. -Exceptions raised: - :TypeError: invalid connection or invalid object - :TypeError: too many parameters - :IOError: object is not opened, or seek error +size -- get the large object size +--------------------------------- -Description: - This method allows to get the current position in the large object. +.. method:: pglarge.size() -Syntax:: + Return the large object size - unlink() + :returns: the large object size + :rtype: int + :raises TypeError: invalid connection or invalid object + :raises TypeError: too many parameters + :raises IOError: object is not opened, or seek/tell error -Parameter: - None +This (composite) method allows to get the size of a large object. It was +implemented because this function is very useful for a web interfaced +database. Currently, the large object needs to be opened first. -Return type: - None - -Exceptions raised: - :TypeError: invalid connection or invalid object - :TypeError: too many parameters - :IOError: object is not closed, or unlink error - -Description: - This methods unlinks (deletes) the PostgreSQL large object. - -size - gives the large object size ----------------------------------- +export -- save a large object to a file +--------------------------------------- -Syntax:: +.. method:: pglarge.export(name) - size() + Export a large object to a file -Parameters: - None + :param str name: file to be created + :rtype: None + :raises TypeError: invalid connection or invalid object, + bad parameter type, or too many parameters + :raises IOError: object is not closed, or export error -Return type: - :integer: the large object size +This methods allows to dump the content of a large object in a very simple +way. The exported file is created on the host of the program, not the +server host. -Exceptions raised: - :TypeError: invalid connection or invalid object - :TypeError: too many parameters - :IOError: object is not opened, or seek/tell error +Object attributes +----------------- +:class:`pglarge` objects define a read-only set of attributes that allow +to get some information about it. These attributes are: -Description: - This (composite) method allows to get the size of a large object. It was - implemented because this function is very useful for a web interfaced - database. Currently, the large object needs to be opened first. +.. attribute:: pglarge.oid -export - saves a large object to a file ---------------------------------------- -Syntax:: + the OID associated with the object (int) - export(name) +.. attribute:: pglarge.pgcnx -Parameters: - :name: file to be created + the :class:`pgobject` associated with the object -Return type: - None +.. attribute:: pglarge.error -Exceptions raised: - :TypeError: invalid connection or invalid object, - bad parameter type, or too many parameters - :IOError: object is not closed, or export error + the last warning/error message of the connection -Description: - This methods allows to dump the content of a large object in a very simple - way. The exported file is created on the host of the program, not the - server host. +.. note:: -Object attributes ------------------ -`pglarge` objects define a read-only set of attributes that allow to get -some information about it. These attributes are: - - :oid: the OID associated with the object - :pgcnx: the `pgobject` associated with the object - :error: the last warning/error message of the connection - -.. caution:: *Be careful*: - In multithreaded environments, `error` may be modified by another thread - using the same pgobject. Remember these object are shared, not duplicated. - You should provide some locking to be able if you want to check this. - The `oid` attribute is very interesting because it allow you reuse the OID - later, creating the `pglarge` object with a `pgobject` getlo() method call. + **Be careful**: + In multithreaded environments, :attr:`pglarge.error` may be modified by + another thread using the same :class:`pgobject`. Remember these object + are shared, not duplicated. You should provide some locking to be able + if you want to check this. The :attr:`pglarge.oid` attribute is very + interesting, because it allows you to reuse the OID later, creating the + :class:`pglarge` object with a :meth:`pgobject.getlo` method call. diff --git a/docs/pgdb.txt b/docs/pgdb.txt index b333c016..ec4f307d 100644 --- a/docs/pgdb.txt +++ b/docs/pgdb.txt @@ -1,42 +1,36 @@ -================================ -PyGreSQL Programming Information -================================ +---------------------------------------------- +:mod:`pgdb` --- The DB-API Compliant Interface +---------------------------------------------- --------------------------------------------- -The DB-API compliant interface (pgdb module) --------------------------------------------- +.. module:: pgdb -.. meta:: - :description: The DB-API compliant interface (pgdb module) - :keywords: PyGreSQL, pgdb, DB-API, PostGreSQL, Python - -.. sectnum:: .. contents:: Contents Introduction ============ -You may either choose to use the -`"classic" PyGreSQL interface `_ -provided by the `pg` module or else the -`DB-API 2.0 compliant interface `_ -provided by the `pgdb` module. +You may either choose to use the "classic" PyGreSQL interface +provided by the :mod:`pg` module or else the +DB-API 2.0 compliant interface provided by the :mod:`pgdb` module. `DB-API 2.0 `_ (Python Database API Specification v2.0) is a specification for connecting to databases (not only PostGreSQL) from Python that has been developed by the Python DB-SIG in 1999. -The following documentation covers only the newer `pgdb` API. +The following documentation covers only the newer :mod:`pgdb` API. -The authoritative programming information for the DB-API is availabe at - http://www.python.org/dev/peps/pep-0249/ +The authoritative programming information for the DB-API is :pep:`0249` -A tutorial-like introduction to the DB-API can be found at - http://www2.linuxjournal.com/lj-issues/issue49/2605.html +A useful tutorial-like `introduction to the DB-API +`_ +has been written by Andrew M. Kuchling for the LINUX Journal in 1998. The pgdb module =============== -.. note:: This section of the documentation still needs to be written. + +.. note:: + + **TO DO**: This section of the documentation still needs to be written. From 8e826055a80b5949f6ae20fc5df7d4c54efac61f Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 30 Dec 2015 16:39:40 +0000 Subject: [PATCH 042/144] Use consistent indentation in docs --- docs/pg.txt | 98 ++++++++++++++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/docs/pg.txt b/docs/pg.txt index d4820857..af0f74fb 100644 --- a/docs/pg.txt +++ b/docs/pg.txt @@ -488,16 +488,16 @@ for more information about them. These constants are: .. data:: version, __version__ - constants that give the current version + constants that give the current version .. data:: INV_READ, INV_WRITE - large objects access modes, - used by :meth:`pgobject.locreate` and :meth:`pglarge.open` + large objects access modes, + used by :meth:`pgobject.locreate` and :meth:`pglarge.open` .. data:: SEEK_SET, SEEK_CUR, SEEK_END: - positional flags, used by :meth:`pglarge.seek` + positional flags, used by :meth:`pglarge.seek` pgobject -- The connection object @@ -511,19 +511,19 @@ significant parameters in function calls. .. note:: - Some methods give direct access to the connection socket. - *Do not use them unless you really know what you are doing.* - If you prefer disabling them, - set the ``-DNO_DIRECT`` option in the Python setup file. - These methods are specified by the tag [DA]. + Some methods give direct access to the connection socket. + *Do not use them unless you really know what you are doing.* + If you prefer disabling them, + set the ``-DNO_DIRECT`` option in the Python setup file. + These methods are specified by the tag [DA]. .. note:: - Some other methods give access to large objects - (refer to PostgreSQL user manual for more information about these). - If you want to forbid access to these from the module, - set the ``-DNO_LARGE`` option in the Python setup file. - These methods are specified by the tag [LO]. + Some other methods give access to large objects + (refer to PostgreSQL user manual for more information about these). + If you want to forbid access to these from the module, + set the ``-DNO_LARGE`` option in the Python setup file. + These methods are specified by the tag [LO]. query -- execute a SQL command string ------------------------------------- @@ -575,11 +575,11 @@ reset -- reset the connection .. method:: pgobject.reset() - Reset the :mod:`pg` connection + Reset the :mod:`pg` connection - :rtype: None - :raises TypeError: too many (any) arguments - :raises TypeError: invalid connection + :rtype: None + :raises TypeError: too many (any) arguments + :raises TypeError: invalid connection This method resets the current database connection. @@ -588,9 +588,9 @@ cancel -- abandon processing of current SQL command .. method:: pgobject.cancel() - :rtype: None - :raises TypeError: too many (any) arguments - :raises TypeError: invalid connection + :rtype: None + :raises TypeError: too many (any) arguments + :raises TypeError: invalid connection This method requests that the server abandon processing of the current SQL command. @@ -600,10 +600,10 @@ close -- close the database connection .. method:: pgobject.close() - Close the :mod:`pg` connection + Close the :mod:`pg` connection - :rtype: None - :raises TypeError: too many (any) arguments + :rtype: None + :raises TypeError: too many (any) arguments This method closes the database connection. The connection will be closed in any case when the connection is deleted but this @@ -615,12 +615,12 @@ fileno -- returns the socket used to connect to the database .. method:: pgobject.fileno() - Return the socket used to connect to the database + Return the socket used to connect to the database - :returns: the socket id of the database connection - :rtype: int - :raises TypeError: too many (any) arguments - :raises TypeError: invalid connection + :returns: the socket id of the database connection + :rtype: int + :raises TypeError: too many (any) arguments + :raises TypeError: invalid connection This method returns the underlying socket id used to connect to the database. This is useful for use in select calls, etc. @@ -630,12 +630,12 @@ getnotify -- get the last notify from the server .. method:: pgobject.getnotify() - Get the last notify from the server + Get the last notify from the server - :returns: last notify from server - :rtype: tuple, None - :raises TypeError: too many parameters - :raises TypeError: invalid connection + :returns: last notify from server + :rtype: tuple, None + :raises TypeError: too many parameters + :raises TypeError: invalid connection This method tries to get a notify from the server (from the SQL statement NOTIFY). If the server returns no notify, the methods returns None. @@ -650,14 +650,14 @@ inserttable -- insert a list into a table .. method:: pgobject.inserttable(table, values) - Insert a Python list into a database table + Insert a Python list into a database table - :param str table: the table name - :param list values: list of rows values - :rtype: None - :raises TypeError: invalid connection, bad argument type, or too many arguments - :raises MemoryError: insert buffer could not be allocated - :raises ValueError: unsupported values + :param str table: the table name + :param list values: list of rows values + :rtype: None + :raises TypeError: invalid connection, bad argument type, or too many arguments + :raises MemoryError: insert buffer could not be allocated + :raises ValueError: unsupported values This method allows to *quickly* insert large blocks of data in a table: It inserts the whole values list into the given table. Internally, it @@ -676,11 +676,11 @@ get/set_notice_receiver -- custom notice receiver .. method:: pgobject.get_notice_receiver() - Get the current notice receiver + Get the current notice receiver - :returns: the current notice receiver callable - :rtype: callable, None - :raises TypeError: too many (any) arguments + :returns: the current notice receiver callable + :rtype: callable, None + :raises TypeError: too many (any) arguments This method gets the custom notice receiver callback function that has been set with :meth:`pgobject.set_notice_receiver`, or ``None`` if no @@ -688,11 +688,11 @@ custom notice receiver has ever been set on the connection. .. method:: pgobject.set_notice_receiver(proc) - Set a custom notice receiver + Set a custom notice receiver - :param proc: the custom notice receiver callback function - :rtype: None - :raises TypeError: the specified notice receiver is not callable + :param proc: the custom notice receiver callback function + :rtype: None + :raises TypeError: the specified notice receiver is not callable This method allows setting a custom notice receiver callback function. When a notice or warning message is received from the server, From aab54768cc5f0da30d361037386d2c5023a48d96 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 30 Dec 2015 17:04:32 +0000 Subject: [PATCH 043/144] Convert from Docutils to Sphinx for good Make the switch to using Sphinx exclusively for generating the docs. Rename all reST files to .rst so editors like PyCharm automatically will use syntax highlighting and linting for the reST format. --- buildhtml.py | 226 ---------------- docs/{announce.txt => announce.rst} | 0 docs/{changelog.txt => changelog.rst} | 0 docs/conf.py | 2 +- docs/{copyright.txt => copyright.rst} | 0 docs/default.css | 279 -------------------- docs/docs.css | 109 -------- docs/{examples.txt => examples.rst} | 0 docs/{future.txt => future.rst} | 0 docs/{install.txt => install.rst} | 0 docs/{interface.txt => interface.rst} | 0 docs/{introduction.txt => introduction.rst} | 0 docs/{mailinglist.txt => mailinglist.rst} | 0 docs/{pg.txt => pg.rst} | 0 docs/{pgdb.txt => pgdb.rst} | 0 docs/{readme.txt => readme.rst} | 0 docs/{source.txt => source.rst} | 0 mkdocs | 11 +- 18 files changed, 10 insertions(+), 617 deletions(-) delete mode 100755 buildhtml.py rename docs/{announce.txt => announce.rst} (100%) rename docs/{changelog.txt => changelog.rst} (100%) rename docs/{copyright.txt => copyright.rst} (100%) delete mode 100644 docs/default.css delete mode 100644 docs/docs.css rename docs/{examples.txt => examples.rst} (100%) rename docs/{future.txt => future.rst} (100%) rename docs/{install.txt => install.rst} (100%) rename docs/{interface.txt => interface.rst} (100%) rename docs/{introduction.txt => introduction.rst} (100%) rename docs/{mailinglist.txt => mailinglist.rst} (100%) rename docs/{pg.txt => pg.rst} (100%) rename docs/{pgdb.txt => pgdb.rst} (100%) rename docs/{readme.txt => readme.rst} (100%) rename docs/{source.txt => source.rst} (100%) diff --git a/buildhtml.py b/buildhtml.py deleted file mode 100755 index 110a2374..00000000 --- a/buildhtml.py +++ /dev/null @@ -1,226 +0,0 @@ -#! /usr/bin/python - -# Author: David Goodger -# Contact: goodger@users.sourceforge.net -# Revision: $Revision$ -# Date: $Date$ -# Copyright: This module has been placed in the public domain. - -""" -Generates .html from all the .txt files in a directory. - -Ordinary .txt files are understood to be standalone reStructuredText. -Files named ``pep-*.txt`` are interpreted as reStructuredText PEPs. -""" -# Once PySource is here, build .html from .py as well. - -__docformat__ = 'reStructuredText' - - -try: - import locale - locale.setlocale(locale.LC_ALL, '') -except: - pass - -import sys -import os -import os.path -import copy -import docutils -from docutils import ApplicationError -from docutils import core, frontend -from docutils.parsers import rst -from docutils.readers import standalone, pep -from docutils.writers import html4css1, pep_html - - -usage = '%prog [options] [ ...]' -description = ('Generates .html from all the reStructuredText .txt files ' - '(including PEPs) in each ' - '(default is the current directory).') - - -class SettingsSpec(docutils.SettingsSpec): - - """ - Runtime settings & command-line options for the front end. - """ - - # Can't be included in OptionParser below because we don't want to - # override the base class. - settings_spec = ( - 'Build-HTML Options', - None, - (('Recursively scan subdirectories for files to process. This is ' - 'the default.', - ['--recurse'], - {'action': 'store_true', 'default': 1, - 'validator': frontend.validate_boolean}), - ('Do not scan subdirectories for files to process.', - ['--local'], {'dest': 'recurse', 'action': 'store_false'}), - ('Do not process files in . This option may be used ' - 'more than once to specify multiple directories.', - ['--prune'], - {'metavar': '', 'action': 'append', - 'validator': frontend.validate_colon_separated_string_list}), - ('Work silently (no progress messages). Independent of "--quiet".', - ['--silent'], - {'action': 'store_true', 'validator': frontend.validate_boolean}),)) - - relative_path_settings = ('prune',) - config_section = 'buildhtml application' - config_section_dependencies = ('applications',) - - -class OptionParser(frontend.OptionParser): - - """ - Command-line option processing for the ``buildhtml.py`` front end. - """ - - def check_values(self, values, args): - frontend.OptionParser.check_values(self, values, args) - values._source = None - return values - - def check_args(self, args): - source = destination = None - if args: - self.values._directories = args - else: - self.values._directories = [os.getcwd()] - return source, destination - - -class Struct: - - """Stores data attributes for dotted-attribute access.""" - - def __init__(self, **keywordargs): - self.__dict__.update(keywordargs) - - -class Builder: - - def __init__(self): - self.publishers = { - '': Struct(components=(pep.Reader, rst.Parser, pep_html.Writer, - SettingsSpec)), - '.txt': Struct(components=(rst.Parser, standalone.Reader, - html4css1.Writer, SettingsSpec), - reader_name='standalone', - writer_name='html'), - 'PEPs': Struct(components=(rst.Parser, pep.Reader, - pep_html.Writer, SettingsSpec), - reader_name='pep', - writer_name='pep_html')} - """Publisher-specific settings. Key '' is for the front-end script - itself. ``self.publishers[''].components`` must contain a superset of - all components used by individual publishers.""" - - self.setup_publishers() - - def setup_publishers(self): - """ - Manage configurations for individual publishers. - - Each publisher (combination of parser, reader, and writer) may have - its own configuration defaults, which must be kept separate from those - of the other publishers. Setting defaults are combined with the - config file settings and command-line options by - `self.get_settings()`. - """ - for name, publisher in self.publishers.items(): - option_parser = OptionParser( - components=publisher.components, read_config_files=1, - usage=usage, description=description) - publisher.option_parser = option_parser - publisher.setting_defaults = option_parser.get_default_values() - frontend.make_paths_absolute(publisher.setting_defaults.__dict__, - option_parser.relative_path_settings) - publisher.config_settings = ( - option_parser.get_standard_config_settings()) - self.settings_spec = self.publishers[''].option_parser.parse_args( - values=frontend.Values()) # no defaults; just the cmdline opts - self.initial_settings = self.get_settings('') - - def get_settings(self, publisher_name, directory=None): - """ - Return a settings object, from multiple sources. - - Copy the setting defaults, overlay the startup config file settings, - then the local config file settings, then the command-line options. - Assumes the current directory has been set. - """ - publisher = self.publishers[publisher_name] - settings = frontend.Values(publisher.setting_defaults.__dict__) - settings.update(publisher.config_settings, publisher.option_parser) - if directory: - local_config = publisher.option_parser.get_config_file_settings( - os.path.join(directory, 'docutils.conf')) - frontend.make_paths_absolute( - local_config, publisher.option_parser.relative_path_settings, - directory) - settings.update(local_config, publisher.option_parser) - settings.update(self.settings_spec.__dict__, publisher.option_parser) - return settings - - def run(self, directory=None, recurse=1): - recurse = recurse and self.initial_settings.recurse - if directory: - self.directories = [directory] - elif self.settings_spec._directories: - self.directories = self.settings_spec._directories - else: - self.directories = [os.getcwd()] - for directory in self.directories: - os.path.walk(directory, self.visit, recurse) - - def visit(self, recurse, directory, names): - settings = self.get_settings('', directory) - if settings.prune and (os.path.abspath(directory) in settings.prune): - print >>sys.stderr, '/// ...Skipping directory (pruned):', directory - sys.stderr.flush() - names[:] = [] - return - if not self.initial_settings.silent: - print >>sys.stderr, '/// Processing directory:', directory - sys.stderr.flush() - prune = 0 - for name in names: - if name.endswith('.txt'): - prune = self.process_txt(directory, name) - if prune: - break - if not recurse: - del names[:] - - def process_txt(self, directory, name): - if name.startswith('pep-'): - publisher = 'PEPs' - else: - publisher = '.txt' - settings = self.get_settings(publisher, directory) - pub_struct = self.publishers[publisher] - if settings.prune and (directory in settings.prune): - return 1 - settings._source = os.path.normpath(os.path.join(directory, name)) - settings._destination = settings._source[:-4]+'.html' - if not self.initial_settings.silent: - print >>sys.stderr, ' ::: Processing:', name - sys.stderr.flush() - try: - core.publish_file(source_path=settings._source, - destination_path=settings._destination, - reader_name=pub_struct.reader_name, - parser_name='restructuredtext', - writer_name=pub_struct.writer_name, - settings=settings) - except ApplicationError, error: - print >>sys.stderr, (' Error (%s): %s' - % (error.__class__.__name__, error)) - - -if __name__ == "__main__": - Builder().run() diff --git a/docs/announce.txt b/docs/announce.rst similarity index 100% rename from docs/announce.txt rename to docs/announce.rst diff --git a/docs/changelog.txt b/docs/changelog.rst similarity index 100% rename from docs/changelog.txt rename to docs/changelog.rst diff --git a/docs/conf.py b/docs/conf.py index 0f72534e..d31c851c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -38,7 +38,7 @@ # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] -source_suffix = '.txt' +source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' diff --git a/docs/copyright.txt b/docs/copyright.rst similarity index 100% rename from docs/copyright.txt rename to docs/copyright.rst diff --git a/docs/default.css b/docs/default.css deleted file mode 100644 index e94df154..00000000 --- a/docs/default.css +++ /dev/null @@ -1,279 +0,0 @@ -/* -:Author: David Goodger -:Contact: goodger@users.sourceforge.net -:Date: $Date$ -:Revision: $Revision$ -:Copyright: This stylesheet has been placed in the public domain. - -Default cascading style sheet for the HTML output of Docutils. - -See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to -customize this style sheet. -*/ - -/* used to remove borders from tables and images */ -.borderless, table.borderless td, table.borderless th { - border: 0 } - -table.borderless td, table.borderless th { - /* Override padding for "table.docutils td" with "! important". - The right padding separates the table cells. */ - padding: 0 0.5em 0 0 ! important } - -.first { - /* Override more specific margin styles with "! important". */ - margin-top: 0 ! important } - -.last, .with-subtitle { - margin-bottom: 0 ! important } - -.hidden { - display: none } - -a.toc-backref { - text-decoration: none ; - color: black } - -blockquote.epigraph { - margin: 2em 5em ; } - -dl.docutils dd { - margin-bottom: 0.5em } - -/* Uncomment (and remove this text!) to get bold-faced definition list terms -dl.docutils dt { - font-weight: bold } -*/ - -div.abstract { - margin: 2em 5em } - -div.abstract p.topic-title { - font-weight: bold ; - text-align: center } - -div.admonition, div.attention, div.caution, div.danger, div.error, -div.hint, div.important, div.note, div.tip, div.warning { - margin: 2em ; - border: medium outset ; - padding: 1em } - -div.admonition p.admonition-title, div.hint p.admonition-title, -div.important p.admonition-title, div.note p.admonition-title, -div.tip p.admonition-title { - font-weight: bold ; - font-family: sans-serif } - -div.attention p.admonition-title, div.caution p.admonition-title, -div.danger p.admonition-title, div.error p.admonition-title, -div.warning p.admonition-title { - color: red ; - font-weight: bold ; - font-family: sans-serif } - -/* Uncomment (and remove this text!) to get reduced vertical space in - compound paragraphs. -div.compound .compound-first, div.compound .compound-middle { - margin-bottom: 0.5em } - -div.compound .compound-last, div.compound .compound-middle { - margin-top: 0.5em } -*/ - -div.dedication { - margin: 2em 5em ; - text-align: center ; - font-style: italic } - -div.dedication p.topic-title { - font-weight: bold ; - font-style: normal } - -div.figure { - margin-left: 2em ; - margin-right: 2em } - -div.footer, div.header { - clear: both; - font-size: smaller } - -div.line-block { - display: block ; - margin-top: 1em ; - margin-bottom: 1em } - -div.line-block div.line-block { - margin-top: 0 ; - margin-bottom: 0 ; - margin-left: 1.5em } - -div.sidebar { - margin-left: 1em ; - border: medium outset ; - padding: 1em ; - background-color: #ffffee ; - width: 40% ; - float: right ; - clear: right } - -div.sidebar p.rubric { - font-family: sans-serif ; - font-size: medium } - -div.system-messages { - margin: 5em } - -div.system-messages h1 { - color: red } - -div.system-message { - border: medium outset ; - padding: 1em } - -div.system-message p.system-message-title { - color: red ; - font-weight: bold } - -div.topic { - margin: 2em } - -h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, -h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { - margin-top: 0.4em } - -h1.title { - text-align: center } - -h2.subtitle { - text-align: center } - -hr.docutils { - width: 75% } - -img.align-left { - clear: left } - -img.align-right { - clear: right } - -ol.simple, ul.simple { - margin-bottom: 1em } - -ol.arabic { - list-style: decimal } - -ol.loweralpha { - list-style: lower-alpha } - -ol.upperalpha { - list-style: upper-alpha } - -ol.lowerroman { - list-style: lower-roman } - -ol.upperroman { - list-style: upper-roman } - -p.attribution { - text-align: right ; - margin-left: 50% } - -p.caption { - font-style: italic } - -p.credits { - font-style: italic ; - font-size: smaller } - -p.label { - white-space: nowrap } - -p.rubric { - font-weight: bold ; - font-size: larger ; - color: maroon ; - text-align: center } - -p.sidebar-title { - font-family: sans-serif ; - font-weight: bold ; - font-size: larger } - -p.sidebar-subtitle { - font-family: sans-serif ; - font-weight: bold } - -p.topic-title { - font-weight: bold } - -pre.address { - margin-bottom: 0 ; - margin-top: 0 ; - font-family: serif ; - font-size: 100% } - -pre.literal-block, pre.doctest-block { - margin-left: 2em ; - margin-right: 2em ; - background-color: #eeeeee } - -span.classifier { - font-family: sans-serif ; - font-style: oblique } - -span.classifier-delimiter { - font-family: sans-serif ; - font-weight: bold } - -span.interpreted { - font-family: sans-serif } - -span.option { - white-space: nowrap } - -span.pre { - white-space: pre } - -span.problematic { - color: red } - -span.section-subtitle { - /* font-size relative to parent (h1..h6 element) */ - font-size: 80% } - -table.citation { - border-left: solid 1px gray; - margin-left: 1px } - -table.docinfo { - margin: 2em 4em } - -table.docutils { - margin-top: 0.5em ; - margin-bottom: 0.5em } - -table.footnote { - border-left: solid 1px black; - margin-left: 1px } - -table.docutils td, table.docutils th, -table.docinfo td, table.docinfo th { - padding-left: 0.5em ; - padding-right: 0.5em ; - vertical-align: top } - -table.docutils th.field-name, table.docinfo th.docinfo-name { - font-weight: bold ; - text-align: left ; - white-space: nowrap ; - padding-left: 0 } - -h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, -h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { - font-size: 100% } - -tt.docutils { - background-color: #eeeeee } - -ul.auto-toc { - list-style-type: none } diff --git a/docs/docs.css b/docs/docs.css deleted file mode 100644 index 3d99c950..00000000 --- a/docs/docs.css +++ /dev/null @@ -1,109 +0,0 @@ -/* -Stylesheet for use with Docutils. - -Customized for PyGreSQL docs. -*/ - -@import url(default.css); - -body { - margin: 8pt; - padding: 8pt; - background-color: #f8f8ff; - color: #000008; - text-align: justify; - font-family: Arial, Verdana, Helvetica, sans-serif; - font-size: 11pt; } - -a { - text-decoration: none; } - -a:hover { - text-decoration: underline; } - -.title, .subtitle { - color: #003; } - -.topic-title { - color: #006; - font-size: 14pt; } - -h1, h2, h3, h4 { - color: #006; } - -h1 { - padding-top: 20pt; - font-size: 17pt; } - -div#pygresql-changelog div.section h1 { - font-size: 12pt; -} - -h1.title { - font-size: 20pt; -} - -h2 { - font-size: 14pt; } - -h2.subtitle { - font-size: 16pt; -} - -h3 { - font-size: 13pt; } - -h4 { - font-size: 12pt; } - -a.toc-backref { - color: #006; } - -ul.simple li { - margin-top: 4pt; } - -ul.simple ul li { - margin-top: 2pt; } - -div.contents ul { - list-style-type: none; } - -div.contents ul li { - margin-top: 4pt; - font-size: 12pt; } - -div.contents ul ul li { - margin-top: 2pt; - font-size: 11pt; } - -cite { - font-style: normal; - font-family: monospace; - font-weight: bold; } - -table.field-list th.field-name { - font-style: normal; - font-family: monospace; - font-weight: bold; } - -tt.literal, pre.literal-block { - font-style: normal; - font-family: monospace; - font-weight: bold; - background-color: #fff; } - -tt.literal { - padding-left: 2pt; - padding-right: 2pt; } - -pre.literal-block { - padding: 4pt; - border: 1px dotted #ccc; } - -table.docutils { - border-spacing: 0px; - border-collapse: collapse; } - -table.docutils td { - margin: 0px; - padding: 2pt; } diff --git a/docs/examples.txt b/docs/examples.rst similarity index 100% rename from docs/examples.txt rename to docs/examples.rst diff --git a/docs/future.txt b/docs/future.rst similarity index 100% rename from docs/future.txt rename to docs/future.rst diff --git a/docs/install.txt b/docs/install.rst similarity index 100% rename from docs/install.txt rename to docs/install.rst diff --git a/docs/interface.txt b/docs/interface.rst similarity index 100% rename from docs/interface.txt rename to docs/interface.rst diff --git a/docs/introduction.txt b/docs/introduction.rst similarity index 100% rename from docs/introduction.txt rename to docs/introduction.rst diff --git a/docs/mailinglist.txt b/docs/mailinglist.rst similarity index 100% rename from docs/mailinglist.txt rename to docs/mailinglist.rst diff --git a/docs/pg.txt b/docs/pg.rst similarity index 100% rename from docs/pg.txt rename to docs/pg.rst diff --git a/docs/pgdb.txt b/docs/pgdb.rst similarity index 100% rename from docs/pgdb.txt rename to docs/pgdb.rst diff --git a/docs/readme.txt b/docs/readme.rst similarity index 100% rename from docs/readme.txt rename to docs/readme.rst diff --git a/docs/source.txt b/docs/source.rst similarity index 100% rename from docs/source.txt rename to docs/source.rst diff --git a/mkdocs b/mkdocs index 4dc59cbf..25bac2d2 100755 --- a/mkdocs +++ b/mkdocs @@ -1,6 +1,13 @@ #! /bin/sh -echo "Making HTML docs..." +# small safety test +if [ ! -f module/pgmodule.c ] +then + echo "Hmmm. Are you sure you are in the right directory?" + exit 1 +fi -python ./buildhtml.py --stylesheet docs.css --link-stylesheet --prune=docs/CVS docs +echo "Making Sphinx docs..." +cd docs +make html From 6de539da20e105318b8d5478c7d8aac7289e27b5 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 30 Dec 2015 17:16:05 +0000 Subject: [PATCH 044/144] Add a manifest template --- module/MANIFEST.in | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 module/MANIFEST.in diff --git a/module/MANIFEST.in b/module/MANIFEST.in new file mode 100644 index 00000000..a4029f24 --- /dev/null +++ b/module/MANIFEST.in @@ -0,0 +1,11 @@ +include *.c +include *.h +include *.py +include *.cfg +recursive-include tests *.py + +global-exclude *.pyc +global-exclude *.pyo +global-exclude *.o +global-exclude *.so +global-exclude _pg.* \ No newline at end of file From 4019e5ede597c96a7dec863114f0d8f0fe775764 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 30 Dec 2015 20:47:58 +0000 Subject: [PATCH 045/144] Add the missing docs for pgdb For years we only pointed to the general DB-API docs, now I have finally created the custom-fit pgdb documentation. --- docs/pg.rst | 2 +- docs/pgdb.rst | 487 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 484 insertions(+), 5 deletions(-) diff --git a/docs/pg.rst b/docs/pg.rst index af0f74fb..ad4fa81a 100644 --- a/docs/pg.rst +++ b/docs/pg.rst @@ -157,7 +157,7 @@ get/set_defopt -- default connection options [DV] :rtype: str or None :raises TypeError: too many arguments -This method returns the current default connection options specification, +This method returns the current default connection options specification, or ``None`` if the environment variables should be used. Environment variables won't be looked up. diff --git a/docs/pgdb.rst b/docs/pgdb.rst index ec4f307d..cd8d6389 100644 --- a/docs/pgdb.rst +++ b/docs/pgdb.rst @@ -27,10 +27,489 @@ A useful tutorial-like `introduction to the DB-API has been written by Andrew M. Kuchling for the LINUX Journal in 1998. -The pgdb module -=============== +Module functions and constants +============================== -.. note:: +The :mod:`pgdb` module defines a :func:`connect` function that allows to +connect to a database, some global constants describing the capabilities +of the module as well as several exception classes. - **TO DO**: This section of the documentation still needs to be written. +connect -- Open a PostgreSQL connection +--------------------------------------- +.. function:: connect([dsn], [user], [password], [host], [database]) + + Return a new connection to the database + + :param str dsn: data source name as string + :param str user: the database user name + :param str password: the database password + :param str host: the hostname of the database + :param database: the name of the database + :returns: a connection object + :rtype: :class:`pgdbCnx` + :raises pgdb.OperationalError: error connecting to the database + +This function takes parameters specifying how to connect to a PostgreSQL +database and returns a :class:`pgdbCnx` object using these parameters. +If specified, the *dsn* parameter must be a string with the format +``'host:base:user:passwd:opt:tty'``. All of the parts specified in the *dsn* +are optional. You can also specify the parameters individually using keyword +arguments, which always take precedence. The *host* can also contain a port +if specified in the format ``'host:port'``. In the *opt* part of the *dsn* +you can pass command-line options to the server, the *tty* part is used to +send server debug output. + +Example:: + + con = connect(dsn='myhost:mydb', user='guido', password='234$') + + +Module constants +---------------- + +.. data:: apilevel + + The string constant ``'2.0'``, stating that the module is DB-API 2.0 level + compliant. + +.. data:: threadsafety + + The integer constant 1, stating that the module itself is thread-safe, + but the connections are not thread-safe, and therefore must be protected + with a lock if you want to use them from different threads. + +.. data:: paramstyle + + The string constant ``pyformat``, stating that parameters should be passed + using Python extended format codes, e.g. ``" ... WHERE name=%(name)s"``. + +Errors raised by this module +---------------------------- + +The errors that can be raised by the :mod:`pgdb` module are the following: + +.. exception:: Warning + + Exception raised for important warnings like data truncations while + inserting. + +.. exception:: Error + + Exception that is the base class of all other error exceptions. You can + use this to catch all errors with one single except statement. + Warnings are not considered errors and thus do not use this class as base. + +.. exception:: InterfaceError + + Exception raised for errors that are related to the database interface + rather than the database itself. + +.. exception:: DatabaseError + + Exception raised for errors that are related to the database. + +.. exception:: DataError + + Exception raised for errors that are due to problems with the processed + data like division by zero or numeric value out of range. + +.. exception:: OperationalError + + Exception raised for errors that are related to the database's operation + and not necessarily under the control of the programmer, e.g. an unexpected + disconnect occurs, the data source name is not found, a transaction could + not be processed, or a memory allocation error occurred during processing. + +.. exception:: IntegrityError + + Exception raised when the relational integrity of the database is affected, + e.g. a foreign key check fails. + +.. exception:: ProgrammingError + + Exception raised for programming errors, e.g. table not found or already + exists, syntax error in the SQL statement or wrong number of parameters + specified. + +.. exception:: NotSupportedError + + Exception raised in case a method or database API was used which is not + supported by the database. + + +pgdbCnx -- The connection object +================================ + +.. class:: pgdbCnx + +These connection objects respond to the following methods. + +Note that ``pgdb.pgdbCnx`` objects also implement the context manager protocol, +i.e. you can use them in a ``with`` statement. + +close -- close the connection +----------------------------- + +.. method:: pgdbCnx.close() + + Close the connection now (rather than whenever it is deleted) + + :rtype: None + +The connection will be unusable from this point forward; an :exc:`Error` +(or subclass) exception will be raised if any operation is attempted with +the connection. The same applies to all cursor objects trying to use the +connection. Note that closing a connection without committing the changes +first will cause an implicit rollback to be performed. + +commit -- commit the connection +------------------------------- + +.. method:: pgdbCnx.commit() + + Commit any pending transaction to the database + + :rtype: None + +Note that connections always use a transaction, there is no auto-commit. + +rollback -- roll back the connection +------------------------------------ + +.. method:: pgdbCnx.rollback() + + Roll back any pending transaction to the database + + :rtype: None + +This method causes the database to roll back to the start of any pending +transaction. Closing a connection without committing the changes first will +cause an implicit rollback to be performed. + +cursor -- return a new cursor object +------------------------------------ + +.. method:: pgdbCnx.cusor() + + Return a new cursor object using the connection + + :returns: a connection object + :rtype: :class:`pgdbCursor` + +This method returns a new :class:`pgdbCursor` object that can be used to +operate on the database in the way described in the next section. + + +pgdbCursor -- The cursor object +=============================== + +.. class:: pgdb.Cursor + +These objects represent a database cursor, which is used to manage the context +of a fetch operation. Cursors created from the same connection are not +isolated, i.e., any changes done to the database by a cursor are immediately +visible by the other cursors. Cursors created from different connections can +or can not be isolated, depending on the level of transaction isolation. +The default PostgreSQL transaction isolation level is "read committed". + +Cursor objects respond to the following methods and attributes. + +Note that ``pgdb.Cursor`` objects also implement both the iterator and the +context manager protocol, i.e. you can iterate over them and you can use them +in a ``with`` statement. + +description -- details regarding the result columns +--------------------------------------------------- + +.. attribute:: Cursor.description + + This read-only attribute is a sequence of 7-item sequences. + + Each of these sequences contains information describing one result column: + + - *name* + - *type_code* + - *display_size* + - *internal_size* + - *precision* + - *scale* + - *null_ok* + + Note that *precision*, *scale* and *null_ok* are not implemented. + + This attribute will be ``None`` for operations that do not return rows + or if the cursor has not had an operation invoked via the + :meth:`pgdbCursor.execute` or :meth:`pgdbCursor.executemany` method yet. + +rowcount -- number of rows of the result +---------------------------------------- + +.. attribute:: Cursor.rowcount + + This read-only attribute specifies the number of rows that the last + :meth:`pgdbCursor.execute` or :meth:`pgdbCursor.executemany` call produced + (for DQL statements like SELECT) or affected (for DML statements like + UPDATE or INSERT ). The attribute is -1 in case no such method call has + been performed on the cursor or the rowcount of the last operation + cannot be determined by the interface. + +close -- close the cursor +------------------------- + +.. method:: pgdbCursor.close() + + Close the cursor now (rather than whenever it is deleted) + + :rtype: None + +The cursor will be unusable from this point forward; an :exc:`Error` +(or subclass) exception will be raised if any operation is attempted +with the cursor. + +execute -- execute a database operation +--------------------------------------- + +.. method:: pgdbCursor.execute(operation, [parameters]) + + Prepare and execute a database operation (query or command) + + :param str operation: the database operation + :param parameters: a sequence or mapping of parameters + :returns: the cursor, so you can chain commands + +Parameters may be provided as sequence or mapping and will be bound to +variables in the operation. Variables are specified using Python extended +format codes, e.g. ``" ... WHERE name=%(name)s"``. + +A reference to the operation will be retained by the cursor. If the same +operation object is passed in again, then the cursor can optimize its behavior. +This is most effective for algorithms where the same operation is used, +but different parameters are bound to it (many times). + +The parameters may also be specified as list of tuples to e.g. insert multiple +rows in a single operation, but this kind of usage is deprecated: +:meth:`pgdbCursor.executemany` should be used instead. + +executemany -- execute many similar database operations +------------------------------------------------------- + +.. method:: pgdbCursor.executemany(operation, [seq_of_parameters]) + + Prepare and execute many similar database operations (queries or commands) + + :param str operation: the database operation + :param seq_of_parameters: a sequence or mapping of parameter tuples or mappings + :returns: the cursor, so you can chain commands + +Prepare a database operation (query or command) and then execute it against +all parameter tuples or mappings found in the sequence *seq_of_parameters*. + +Parameters are bounded to the query using Python extended format codes, +e.g. ``" ... WHERE name=%(name)s"``. + +fetchone -- fetch next row of the query result +---------------------------------------------- + +.. method:: pgdbCursor.fetchone() + + Fetch the next row of a query result set + + :returns: the next row of the query result set + :rtype: tuple or None + +Fetch the next row of a query result set, returning a single tuple, +or ``None`` when no more data is available. + +An :exc:`Error` (or subclass) exception is raised if the previous call to +:meth:`pgdbCursor.execute` or :meth:`pgdbCursor.executemany` did not produce +any result set or no call was issued yet. + +fetchmany -- fetch next set of rows of the query result +------------------------------------------------------- + +.. method:: pgdbCursor.fetchmany([size=None], [keep=False]) + + Fetch the next set of rows of a query result + + :param size: the number of rows to be fetched + :type size: int or None + :param keep: if set to true, will keep the passed arraysize + :tpye keep: bool + :returns: the next set of rows of the query result + :rtype: list of tuples + +Fetch the next set of rows of a query result, returning a list of tuples. +An empty sequence is returned when no more rows are available. + +The number of rows to fetch per call is specified by the *size* parameter. +If it is not given, the cursor's :attr:`arraysize` determines the number of +rows to be fetched. If you set the *keep* parameter to True, this is kept as +new :attr:`arraysize`. + +The method tries to fetch as many rows as indicated by the *size* parameter. +If this is not possible due to the specified number of rows not being +available, fewer rows may be returned. + +An :exc:`Error` (or subclass) exception is raised if the previous call to +:meth:`pgdbCursor.execute` or :meth:`pgdbCursor.executemany` did not produce +any result set or no call was issued yet. + +Note there are performance considerations involved with the *size* parameter. +For optimal performance, it is usually best to use the :attr:`arraysize` +attribute. If the *size* parameter is used, then it is best for it to retain +the same value from one :meth:`pgdbCursor.fetchmany` call to the next. + +fetchall -- fetch all rows of the query result +---------------------------------------------- + +.. method:: pgdbCursor.fetchall() + + Fetch all (remaining) rows of a query result + + :returns: the set of all rows of the query result + :rtype: list of tuples + +Fetch all (remaining) rows of a query result, returning them as list of tuples. +Note that the cursor's :attr:`arraysize` attribute can affect the performance +of this operation. + +row_factory -- process a row of the query result +------------------------------------------------ + +.. method:: pgdbCursor.row_factory(row) + + Process rows before they are returned + + :param tuple row: the currently processed row of the result set + :returns: the transformed row that the cursor methods shall return + +Note that this method is not part of the DB-API 2 standard. + +You can overwrite this method with a custom row factory, e.g. +if you want to return rows as dicts instead of tuples:: + + class DictCursor(pgdb.pgdbCursor): + + def row_factory(self, row): + return {desc[0]:value + for desc, value in zip(self.description, row)} + + cur = DictCursor(con) + +arraysize - the number of rows to fetch at a time +------------------------------------------------- + +.. attribute:: pgdbCursor.arraysize + + The number of rows to fetch at a time + +This read/write attribute specifies the number of rows to fetch at a time with +:meth:`pgdbCursor.fetchmany`. It defaults to 1 meaning to fetch a single row +at a time. + + +pgdbType -- Type objects and constructors +========================================= + +The :attr:`pgdbCursor.description` attribute returns information about each +of the result columns of a query. The *type_code* must compare equal to one +of the :class:`pgdbType` objects defined below. Type objects can be equal to +more than one type code (e.g. :class:`DATETIME` is equal to the type codes +for date, time and timestamp columns). + +The :mod:`pgdb` module exports the following constructors and singletons: + +.. function:: Date(year, month, day) + + Construct an object holding a date value + +.. function:: Time(hour, minute=0, second=0, microsecond=0) + + Construct an object holding a time value + +.. function:: Timestamp(year, month, day, hour=0, minute=0, second=0, microsecond=0) + + Construct an object holding a time stamp value + +.. function:: DateFromTicks(ticks) + + Construct an object holding a date value from the given *ticks* value + +.. function:: TimeFromTicks(ticks) + + Construct an object holding a time value from the given *ticks* value + +.. function:: TimestampFromTicks(ticks) + + Construct an object holding a time stamp from the given *ticks* value + +.. function:: Binary(bytes) + + Construct an object capable of holding a (long) binary string value + +.. class:: STRING + + Used to describe columns that are string-based (e.g. ``char``, ``varchar``, ``text``) + +.. class:: BINARY type + + Used to describe (long) binary columns (``bytea``) + +.. class:: NUMBER + + Used to describe numeric columns (e.g. ``int``, ``float``, ``numeric``, ``money``) + +.. class:: DATETIME + + Used to describe date/time columns (e.g. ``date``, ``time``, ``timestamp``, ``interval``) + +.. class:: ROWID + + Used to describe the ``oid`` column of PostgreSQL database tables + +The following more specific types are not part of the DB-API 2 standard: + +.. class:: BOOL + + Used to describe ``boolean`` columns + +.. class:: SMALLINT + + Used to describe ``smallint`` columns + +.. class:: INTEGER + + Used to describe ``integer`` columns + +.. class:: LONG + + Used to describe ``bigint`` columns + +.. class:: FLOAT + + Used to describe ``float`` columns + +.. class:: NUMERIC + + Used to describe ``numeric`` columns + +.. class:: MONEY + + Used to describe ``money`` columns + +.. class:: DATE + + Used to describe ``date`` columns + +.. class:: TIME + + Used to describe ``time`` columns + +.. class:: TIMESTAMP + + Used to describe ``timestamp`` columns + +.. class:: INTERVAL + + Used to describe date and time ``interval`` columns From be683922df4f73ad0f00b1b15463d3e5cf4161b1 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 30 Dec 2015 20:55:07 +0000 Subject: [PATCH 046/144] Fix some class names in pgdb docs --- docs/pgdb.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/pgdb.rst b/docs/pgdb.rst index cd8d6389..18c5b599 100644 --- a/docs/pgdb.rst +++ b/docs/pgdb.rst @@ -204,7 +204,7 @@ operate on the database in the way described in the next section. pgdbCursor -- The cursor object =============================== -.. class:: pgdb.Cursor +.. class:: pgdbCursor These objects represent a database cursor, which is used to manage the context of a fetch operation. Cursors created from the same connection are not @@ -215,14 +215,14 @@ The default PostgreSQL transaction isolation level is "read committed". Cursor objects respond to the following methods and attributes. -Note that ``pgdb.Cursor`` objects also implement both the iterator and the +Note that ``pgdbCursor`` objects also implement both the iterator and the context manager protocol, i.e. you can iterate over them and you can use them in a ``with`` statement. description -- details regarding the result columns --------------------------------------------------- -.. attribute:: Cursor.description +.. attribute:: pgdbCursor.description This read-only attribute is a sequence of 7-item sequences. @@ -245,7 +245,7 @@ description -- details regarding the result columns rowcount -- number of rows of the result ---------------------------------------- -.. attribute:: Cursor.rowcount +.. attribute:: pgdbCursor.rowcount This read-only attribute specifies the number of rows that the last :meth:`pgdbCursor.execute` or :meth:`pgdbCursor.executemany` call produced @@ -412,6 +412,8 @@ at a time. pgdbType -- Type objects and constructors ========================================= +.. class:: pgdbType + The :attr:`pgdbCursor.description` attribute returns information about each of the result columns of a query. The *type_code* must compare equal to one of the :class:`pgdbType` objects defined below. Type objects can be equal to From df78b650610b9aa737221f9471feb10bae87eefd Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 30 Dec 2015 21:31:47 +0000 Subject: [PATCH 047/144] Use simpler and more standard type names in pgdb We already renamed the types provided by the classic module to be more simple and intuitive. We now do the same for the types provided by the DB-API 2 module. --- docs/changelog.rst | 2 -- docs/pg.rst | 1 + docs/pgdb.rst | 1 + module/pgdb.py | 16 +++++++++------- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 34039e2a..c4d687b8 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -57,7 +57,6 @@ Version 4.1 (2013-01-01) - New method use_regtypes() that can be called to let getattnames() return regular type names instead of the simplified classic types (#44). - Version 4.0 (2009-01-01) ------------------------ - Dropped support for Python below 2.3 and PostgreSQL below 7.4. @@ -105,7 +104,6 @@ Version 4.0 (2009-01-01) - delete() now also works based on the primary key if no oid available and returns whether the row existed or not. - Version 3.8.1 (2006-06-05) -------------------------- - Use string methods instead of deprecated string functions. diff --git a/docs/pg.rst b/docs/pg.rst index ad4fa81a..4fa6cff0 100644 --- a/docs/pg.rst +++ b/docs/pg.rst @@ -9,6 +9,7 @@ Introduction ============ + You may either choose to use the "classic" PyGreSQL interface provided by the :mod:`pg` module or else the DB-API 2.0 compliant interface provided by the :mod:`pgdb` module. diff --git a/docs/pgdb.rst b/docs/pgdb.rst index 18c5b599..4435d93d 100644 --- a/docs/pgdb.rst +++ b/docs/pgdb.rst @@ -9,6 +9,7 @@ Introduction ============ + You may either choose to use the "classic" PyGreSQL interface provided by the :mod:`pg` module or else the DB-API 2.0 compliant interface provided by the :mod:`pgdb` module. diff --git a/module/pgdb.py b/module/pgdb.py index 46b9e1c0..6f7cb5fd 100644 --- a/module/pgdb.py +++ b/module/pgdb.py @@ -101,7 +101,7 @@ # shortcut methods are not supported by default # since they have been excluded from DB API 2 -# and are not recommended by the DB SIG; +# and are not recommended by the DB SIG. shortcutmethods = 0 @@ -290,12 +290,14 @@ def row_factory(row): You can overwrite this with a custom row factory, e.g. a dict factory: - class myCursor(pgdb.pgdbCursor): - def cursor.row_factory(self, row): - d = {} - for idx, col in enumerate(self.description): - d[col[0]] = row[idx] - return d + class myCursor(pgdb.pgdbCursor): + + def row_factory(self, row): + d = {} + for idx, col in enumerate(self.description): + d[col[0]] = row[idx] + return d + cursor = myCursor(cnx) """ From 5667c6a2ef4d3328618d23ad65becf7ce1c0998b Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 30 Dec 2015 21:56:42 +0000 Subject: [PATCH 048/144] Remove the deprecated tty parameter and attribute This parameter has been ignored by PostgreSQL since version 7.4. --- docs/pg.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/pg.rst b/docs/pg.rst index 4fa6cff0..a3e77087 100644 --- a/docs/pg.rst +++ b/docs/pg.rst @@ -189,7 +189,7 @@ get/set_deftty -- default debug tty [DV] This method returns the current default debug terminal specification, or ``None`` if the environment variables should be used. Environment variables -won't be looked up. +won't be looked up. Note that this is ignored in newer PostgreSQL versions. .. function:: set_deftty(terminal) @@ -204,6 +204,7 @@ won't be looked up. This methods sets the default debug terminal value for new connections. If ``None`` is supplied as parameter, environment variables will be used in future connections. It returns the previous setting for default terminal. +Note that this is ignored in newer PostgreSQL versions. get/set_defbase -- default database name [DV] --------------------------------------------- From 609edbbd3d480649a7a1ebfd6cfe2c32fb2e62d5 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 2 Jan 2016 13:10:47 +0000 Subject: [PATCH 049/144] Some fixes in the documentation --- docs/pgdb.rst | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/docs/pgdb.rst b/docs/pgdb.rst index 4435d93d..6916ba5f 100644 --- a/docs/pgdb.rst +++ b/docs/pgdb.rst @@ -191,7 +191,7 @@ cause an implicit rollback to be performed. cursor -- return a new cursor object ------------------------------------ -.. method:: pgdbCnx.cusor() +.. method:: pgdbCnx.cursor() Return a new cursor object using the connection @@ -225,9 +225,9 @@ description -- details regarding the result columns .. attribute:: pgdbCursor.description - This read-only attribute is a sequence of 7-item sequences. + This read-only attribute is a sequence of 7-item tuples. - Each of these sequences contains information describing one result column: + Each of these tuples contains information describing one result column: - *name* - *type_code* @@ -237,7 +237,8 @@ description -- details regarding the result columns - *scale* - *null_ok* - Note that *precision*, *scale* and *null_ok* are not implemented. + Note that *display_size*, *precision*, *scale* and *null_ok* + are not implemented. This attribute will be ``None`` for operations that do not return rows or if the cursor has not had an operation invoked via the @@ -317,9 +318,9 @@ fetchone -- fetch next row of the query result Fetch the next row of a query result set :returns: the next row of the query result set - :rtype: tuple or None + :rtype: list or None -Fetch the next row of a query result set, returning a single tuple, +Fetch the next row of a query result set, returning a single list, or ``None`` when no more data is available. An :exc:`Error` (or subclass) exception is raised if the previous call to @@ -338,9 +339,9 @@ fetchmany -- fetch next set of rows of the query result :param keep: if set to true, will keep the passed arraysize :tpye keep: bool :returns: the next set of rows of the query result - :rtype: list of tuples + :rtype: list of lists -Fetch the next set of rows of a query result, returning a list of tuples. +Fetch the next set of rows of a query result, returning a list of lists. An empty sequence is returned when no more rows are available. The number of rows to fetch per call is specified by the *size* parameter. @@ -369,9 +370,9 @@ fetchall -- fetch all rows of the query result Fetch all (remaining) rows of a query result :returns: the set of all rows of the query result - :rtype: list of tuples + :rtype: list of list -Fetch all (remaining) rows of a query result, returning them as list of tuples. +Fetch all (remaining) rows of a query result, returning them as list of lists. Note that the cursor's :attr:`arraysize` attribute can affect the performance of this operation. @@ -382,19 +383,20 @@ row_factory -- process a row of the query result Process rows before they are returned - :param tuple row: the currently processed row of the result set + :param list row: the currently processed row of the result set :returns: the transformed row that the cursor methods shall return -Note that this method is not part of the DB-API 2 standard. +.. note:: + + This method is not part of the DB-API 2 standard. You can overwrite this method with a custom row factory, e.g. -if you want to return rows as dicts instead of tuples:: +if you want to return rows as dicts instead of lists:: class DictCursor(pgdb.pgdbCursor): def row_factory(self, row): - return {desc[0]:value - for desc, value in zip(self.description, row)} + return dict((d[0], v) for d, v in zip(self.description, row)) cur = DictCursor(con) @@ -471,7 +473,9 @@ The :mod:`pgdb` module exports the following constructors and singletons: Used to describe the ``oid`` column of PostgreSQL database tables -The following more specific types are not part of the DB-API 2 standard: +.. note: + + The following more specific types are not part of the DB-API 2 standard. .. class:: BOOL From 897a2a6b08f221eef0260f704d3826ecdd642c07 Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Sat, 2 Jan 2016 15:21:32 +0000 Subject: [PATCH 050/144] It was a team effort. --- docs/announce.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/announce.rst b/docs/announce.rst index 9ee08f75..7fece981 100644 --- a/docs/announce.rst +++ b/docs/announce.rst @@ -30,3 +30,4 @@ This version has been built and unit tested on: | D'Arcy J.M. Cain | darcy@PyGreSQL.org +| and the PyGreSQL team From aadade24ca9606a7f693161a32cc6f1676357ace Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 2 Jan 2016 15:57:27 +0000 Subject: [PATCH 051/144] Improve the script for creating the source distribution Made some small adaptations for Sphinx and added the test package. This should eventually be done with "setup.py sdist", but we need to move the docs directory into the module folder to make this possible. --- mktar | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/mktar b/mktar index 25211c87..76af22b4 100755 --- a/mktar +++ b/mktar @@ -19,12 +19,21 @@ else SYMLINK=PyGreSQL.tgz fi +# Package up as a source tarball in the distribution directory. +# Note that this does essentially the same as "python setup.py sdist", +# except this also makes the docs and bundles them as source and html. + DISTDIR=/u/pyg/files TD=PyGreSQL-$VERSION TF=$DISTDIR/$TD.tgz -MODFILES="module/pg.py module/pgdb.py module/pgmodule.c module/pgfs.h module/pgtypes.h module/setup.py" -DOCFILES="docs/*.txt docs/*.html docs/*.css" +MODFILES="module/pg.py module/pgdb.py module/pgmodule.c + module/pgfs.h module/pgtypes.h + module/setup.py module/setup.cfg" +DOCFILES="docs/Makefile docs/make.bat docs/*.rst + docs/_build/html/*.html docs/_build/html/*.js + docs/_build/html/_static" +TESTFILES="module/tests/*.py" TUTFILES="tutorial/*.py" echo "Making source tarball..." @@ -33,10 +42,12 @@ echo "Making source tarball..." rm -rf $TD mkdir $TD -mkdir $TD/docs +mkdir -p $TD/docs/_static +mkdir $TD/tests mkdir $TD/tutorial cp $MODFILES $TD -cp $DOCFILES $TD/docs +cp -r $DOCFILES $TD/docs +cp $TESTFILES $TD/tests cp $TUTFILES $TD/tutorial tar -cvzf $TF $TD chmod 644 $TF From 3ad9687f986a785160b2d6affc52abbd062c855a Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Sat, 2 Jan 2016 16:02:40 +0000 Subject: [PATCH 052/144] The docs Makefile now requires gmake. On Linux make is gmake but on BSD systems we need to run gmake. While not perfect, this change will check for a separate gmake executible and use it if it exists. Also, added a comment in Makefile to help users figure out why the error is happening. --- docs/Makefile | 2 +- mkdocs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 0a1113c9..ae3e5d2b 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,5 @@ # Makefile for Sphinx documentation -# +# -*- Note: requires GNU Make -*- # You can set these variables from the command line. SPHINXOPTS = diff --git a/mkdocs b/mkdocs index 25bac2d2..a113611d 100755 --- a/mkdocs +++ b/mkdocs @@ -1,5 +1,8 @@ #! /bin/sh +MAKE=make +which gmake && MAKE=gmake + # small safety test if [ ! -f module/pgmodule.c ] then @@ -10,4 +13,4 @@ fi echo "Making Sphinx docs..." cd docs -make html +${MAKE} html From 234d0e8820336f63c44e313b37138c42e26cf92b Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Sat, 2 Jan 2016 16:40:01 +0000 Subject: [PATCH 053/144] Update copyright. --- docs/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.html b/docs/index.html index 1b0724dd..a462b7cf 100644 --- a/docs/index.html +++ b/docs/index.html @@ -110,7 +110,7 @@

PyGreSQL – PostgreSQL module for Python

This software is copyright © 1995, Pascal Andre.
Further modifications are copyright © 1997-2008 by D'Arcy J.M. Cain.
- Further modifications are copyright © 2009-2012 by the PyGreSQL team

+ Further modifications are copyright © 2009-2016 by the PyGreSQL team

See the copyright notice From 6e844c0b944d46c455328cd1b1fb43bd2c9994bb Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Sat, 2 Jan 2016 16:41:04 +0000 Subject: [PATCH 054/144] Update copyright. --- docs/readme.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readme.rst b/docs/readme.rst index 964e0b0c..0b08e27b 100644 --- a/docs/readme.rst +++ b/docs/readme.rst @@ -25,7 +25,7 @@ Copyright (c) 1995, Pascal Andre Further modifications copyright (c) 1997-2008 by D'Arcy J.M. Cain (darcy@PyGreSQL.org) -Further modifications copyright (c) 2009-2015 by the PyGreSQL team. +Further modifications copyright (c) 2009-2016 by the PyGreSQL team. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement From d592841fdd32ce45fb6b89561e5a1691700ce2fd Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 2 Jan 2016 23:25:14 +0000 Subject: [PATCH 055/144] Amend tests so that they can run with PostgreSQL < 9.0 Note that we do not need to make these amendments in the trunk, because we assume PostgreSQL >= 9.0 for PyGreSQL version 5.0. --- module/pg.py | 2 ++ module/setup.py | 14 +++++----- module/tests/test_classic.py | 4 +++ module/tests/test_classic_connection.py | 35 ++++++++++++------------ module/tests/test_classic_dbwrapper.py | 36 ++++++++++++++++++++----- module/tests/test_dbapi20.py | 1 + 6 files changed, 62 insertions(+), 30 deletions(-) diff --git a/module/pg.py b/module/pg.py index e4b2a9c9..0870e817 100644 --- a/module/pg.py +++ b/module/pg.py @@ -197,6 +197,8 @@ def notify(self, db=None, stop=False, payload=None): Note: If the main loop is running in another thread, you must pass a different database connection to avoid a collision. + The payload parameter is only supported in PostgreSQL >= 9.0. + """ if not db: db = self.db diff --git a/module/setup.py b/module/setup.py index 34a27c77..6ab8f816 100755 --- a/module/setup.py +++ b/module/setup.py @@ -128,11 +128,11 @@ def finalize_options(self): define_macros.append(('LARGE_OBJECTS', None)) if self.default_vars: define_macros.append(('DEFAULT_VARS', None)) - if self.escaping_funcs: + if self.escaping_funcs and pg_version[0] >= 9: define_macros.append(('ESCAPING_FUNCS', None)) if sys.platform == 'win32': bits = platform.architecture()[0] - if bits == '64bit': # we need to find libpq64 + if bits == '64bit': # we need to find libpq64 for path in os.environ['PATH'].split(os.pathsep) + [ r'C:\Program Files\PostgreSQL\libpq64']: library_dir = os.path.join(path, 'lib') @@ -149,13 +149,13 @@ def finalize_options(self): library_dirs.insert(1, library_dir) if include_dir not in include_dirs: include_dirs.insert(1, include_dir) - libraries[0] += 'dll' # libpqdll instead of libpq + libraries[0] += 'dll' # libpqdll instead of libpq break compiler = self.get_compiler() - if compiler == 'mingw32': # MinGW - if bits == '64bit': # needs MinGW-w64 + if compiler == 'mingw32': # MinGW + if bits == '64bit': # needs MinGW-w64 define_macros.append(('MS_WIN64', None)) - elif compiler == 'msvc': # Microsoft Visual C++ + elif compiler == 'msvc': # Microsoft Visual C++ libraries[0] = 'lib' + libraries[0] @@ -163,7 +163,7 @@ def finalize_options(self): name="PyGreSQL", version=version, description="Python PostgreSQL Interfaces", - long_description=__doc__.split('\n\n', 2)[1], # first passage + long_description=__doc__.split('\n\n', 2)[1], # first passage keywords="pygresql postgresql database api dbapi", author="D'Arcy J. M. Cain", author_email="darcy@PyGreSQL.org", diff --git a/module/tests/test_classic.py b/module/tests/test_classic.py index 013d8b35..0dd27631 100755 --- a/module/tests/test_classic.py +++ b/module/tests/test_classic.py @@ -249,6 +249,8 @@ def test_notify(self, options=None): call_notify = options.get('call_notify') two_payloads = options.get('two_payloads') db = opendb() + if db.server_version < 90000: # PostgreSQL < 9.0 + self.skipTest('Notify with payload not supported') # Get function under test, can be standalone or DB method. if run_as_method: fut = db.notification_handler @@ -336,6 +338,8 @@ def test_notify_other_options(self): def test_notify_timeout(self): for run_as_method in False, True: db = opendb() + if db.server_version < 90000: # PostgreSQL < 9.0 + self.skipTest('Notify with payload not supported') # Get function under test, can be standalone or DB method. if run_as_method: fut = db.notification_handler diff --git a/module/tests/test_classic_connection.py b/module/tests/test_classic_connection.py index 63c4d966..04218ae5 100755 --- a/module/tests/test_classic_connection.py +++ b/module/tests/test_classic_connection.py @@ -86,6 +86,9 @@ def testAllConnectMethods(self): fileno get_notice_receiver getline getlo getnotify inserttable locreate loimport parameter putline query reset set_notice_receiver source transaction'''.split() + if self.connection.server_version < 90000: # PostgreSQL < 9.0 + methods.remove('escape_identifier') + methods.remove('escape_literal') connection_methods = [a for a in dir(self.connection) if callable(eval("self.connection." + a))] self.assertEqual(methods, connection_methods) @@ -558,7 +561,7 @@ def testQueryWithIntParams(self): [(Decimal('2'),)]) self.assertEqual(query("select 1, $1::integer", (2,) ).getresult(), [(1, 2)]) - self.assertEqual(query("select 1 union select $1", (2,) + self.assertEqual(query("select 1 union select $1::integer", (2,) ).getresult(), [(1,), (2,)]) self.assertEqual(query("select $1::integer+$2", (1, 2) ).getresult(), [(3,)]) @@ -816,7 +819,7 @@ def testGetline(self): v = getline() if i < n: self.assertEqual(v, '%d\t%s' % data[i]) - elif i == n: + elif i == n or self.c.server_version < 90000: self.assertEqual(v, '\\.') else: self.assertIsNone(v) @@ -842,6 +845,8 @@ def tearDown(self): self.c.close() def testGetNotify(self): + if self.c.server_version < 90000: # PostgreSQL < 9.0 + self.skipTest('Notify with payload not supported') getnotify = self.c.getnotify query = self.c.query self.assertIsNone(getnotify()) @@ -858,20 +863,16 @@ def testGetNotify(self): self.assertEqual(r[0], 'test_notify') self.assertEqual(r[2], '') self.assertIsNone(self.c.getnotify()) - try: - query("notify test_notify, 'test_payload'") - except pg.ProgrammingError: # PostgreSQL < 9.0 - pass - else: - r = getnotify() - self.assertTrue(isinstance(r, tuple)) - self.assertEqual(len(r), 3) - self.assertIsInstance(r[0], str) - self.assertIsInstance(r[1], int) - self.assertIsInstance(r[2], str) - self.assertEqual(r[0], 'test_notify') - self.assertEqual(r[2], 'test_payload') - self.assertIsNone(getnotify()) + query("notify test_notify, 'test_payload'") + r = getnotify() + self.assertTrue(isinstance(r, tuple)) + self.assertEqual(len(r), 3) + self.assertIsInstance(r[0], str) + self.assertIsInstance(r[1], int) + self.assertIsInstance(r[2], str) + self.assertEqual(r[0], 'test_notify') + self.assertEqual(r[2], 'test_payload') + self.assertIsNone(getnotify()) finally: query('unlisten test_notify') @@ -983,7 +984,7 @@ def testSetDecimalPoint(self): en_money = '$34.25', '$ 34.25', '34.25$', '34.25 $', '34.25 Dollar' de_locales = 'de', 'de_DE', 'de_DE.utf8', 'de_DE.UTF-8' de_money = ('34,25€', '34,25 €', '€34,25' '€ 34,25', - '34,25 EUR', '34,25 Euro', '34,25 DM') + 'EUR34,25', 'EUR 34,25', '34,25 EUR', '34,25 Euro', '34,25 DM') # first try with English localization (using the point) for lc in en_locales: try: diff --git a/module/tests/test_classic_dbwrapper.py b/module/tests/test_classic_dbwrapper.py index 29fa2a07..a74517a0 100755 --- a/module/tests/test_classic_dbwrapper.py +++ b/module/tests/test_classic_dbwrapper.py @@ -120,6 +120,9 @@ def testAllDBAttributes(self): 'use_regtypes', 'user', ] + if self.db.server_version < 90000: # PostgreSQL < 9.0 + attributes.remove('escape_identifier') + attributes.remove('escape_literal') db_attributes = [a for a in dir(self.db) if not a.startswith('_')] self.assertEqual(attributes, db_attributes) @@ -190,9 +193,13 @@ def testAttributeUser(self): self.assertEqual(user, self.db.db.user) def testMethodEscapeLiteral(self): + if self.db.server_version < 90000: # PostgreSQL < 9.0 + self.skipTest('Escaping functions not supported') self.assertEqual(self.db.escape_literal(''), "''") def testMethodEscapeIdentifier(self): + if self.db.server_version < 90000: # PostgreSQL < 9.0 + self.skipTest('Escaping functions not supported') self.assertEqual(self.db.escape_identifier(''), '""') def testMethodEscapeString(self): @@ -289,13 +296,19 @@ def setUp(self): query = self.db.query query('set client_encoding=utf8') query('set standard_conforming_strings=on') - query('set bytea_output=hex') query("set lc_monetary='C'") + query("set datestyle='ISO,YMD'") + try: + query('set bytea_output=hex') + except pg.ProgrammingError: # PostgreSQL < 9.0 + pass def tearDown(self): self.db.close() def testEscapeLiteral(self): + if self.db.server_version < 90000: # PostgreSQL < 9.0 + self.skipTest('Escaping functions not supported') f = self.db.escape_literal self.assertEqual(f("plain"), "'plain'") self.assertEqual(f("that's k\xe4se"), "'that''s k\xe4se'") @@ -305,6 +318,8 @@ def testEscapeLiteral(self): "'No \"quotes\" must be escaped.'") def testEscapeIdentifier(self): + if self.db.server_version < 90000: # PostgreSQL < 9.0 + self.skipTest('Escaping functions not supported') f = self.db.escape_identifier self.assertEqual(f("plain"), '"plain"') self.assertEqual(f("that's k\xe4se"), '"that\'s k\xe4se"') @@ -322,11 +337,16 @@ def testEscapeString(self): def testEscapeBytea(self): f = self.db.escape_bytea - # note that escape_byte always returns hex output since Pg 9.0, + # note that escape_byte always returns hex output since PostgreSQL 9.0, # regardless of the bytea_output setting - self.assertEqual(f("plain"), r"\x706c61696e") - self.assertEqual(f("that's k\xe4se"), r"\x746861742773206be47365") - self.assertEqual(f('O\x00ps\xff!'), r"\x4f007073ff21") + if self.db.server_version < 90000: + self.assertEqual(f("plain"), r"plain") + self.assertEqual(f("that's k\xe4se"), r"that''s k\344se") + self.assertEqual(f('O\x00ps\xff!'), r"O\000ps\377!") + else: + self.assertEqual(f("plain"), r"\x706c61696e") + self.assertEqual(f("that's k\xe4se"), r"\x746861742773206be47365") + self.assertEqual(f('O\x00ps\xff!'), r"\x4f007073ff21") def testUnescapeBytea(self): f = self.db.unescape_bytea @@ -689,6 +709,7 @@ def testGetFromView(self): def testInsert(self): insert = self.db.insert query = self.db.query + server_version = self.db.server_version for table in ('insert_test_table', 'test table for insert'): query('drop table if exists "%s"' % table) query('create table "%s" (' @@ -716,7 +737,7 @@ def testInsert(self): dict(d=Decimal('123456789.9876543212345678987654321')), dict(m=None), (dict(m=''), dict(m=None)), dict(m=Decimal('-1234.56')), - (dict(m=('-1234.56')), dict(m=Decimal('-1234.56'))), + (dict(m='-1234.56'), dict(m=Decimal('-1234.56'))), dict(m=Decimal('1234.56')), dict(m=Decimal('123456')), (dict(m='1234.56'), dict(m=Decimal('1234.56'))), (dict(m=1234.5), dict(m=Decimal('1234.5'))), @@ -751,6 +772,9 @@ def testInsert(self): data, change = test expect = data.copy() expect.update(change) + if data.get('m') and server_version < 910000: + # PostgreSQL < 9.1 cannot directly convert numbers to money + data['m'] = "'%s'::money" % data['m'] self.assertEqual(insert(table, data), data) self.assertIn(oid_table, data) oid = data[oid_table] diff --git a/module/tests/test_dbapi20.py b/module/tests/test_dbapi20.py index 2646f0ad..8c0be84a 100755 --- a/module/tests/test_dbapi20.py +++ b/module/tests/test_dbapi20.py @@ -85,6 +85,7 @@ def test_fetch_2_rows(self): con = self._connect() try: cur = con.cursor() + cur.execute("set datestyle to 'iso'") cur.execute("create table %s (" "stringtest varchar," "binarytest bytea," From 34c43e31e6fa75f74348f8f86a1f0e66c4c0ecdc Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 2 Jan 2016 23:35:15 +0000 Subject: [PATCH 056/144] Print a warning when the PostgreSQL version is too old --- module/setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/module/setup.py b/module/setup.py index 6ab8f816..04128922 100755 --- a/module/setup.py +++ b/module/setup.py @@ -47,6 +47,7 @@ import os import platform +import warnings try: from setuptools import setup except ImportError: @@ -75,7 +76,7 @@ def pg_version(): if part.isdigit(): part = int(part) parts.append(part) - return tuple(parts or [8]) + return tuple(parts or [8, 3]) pg_version = pg_version() @@ -118,6 +119,8 @@ def initialize_options(self): self.large_objects = True self.default_vars = True self.escaping_funcs = pg_version[0] >= 9 + if pg_version < (8, 3): + warnings.warn("PygreSQL does not support this PostgreSQL version.") def finalize_options(self): """Set final values for all build_pg options.""" From 6ce60e0a23a48eac89e54d5dc9688b8b61641740 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 3 Jan 2016 14:11:21 +0000 Subject: [PATCH 057/144] Implement the callproc() cursor method The implementation has been a bit simplified in that output and input/output parameters are not changed in the return value, i.e. they can only be retrieved using the fetch methods. To implement this, it would be necessary to determine (through a query to the database catalog) which parameters are output parameters and fetch the return values for these, which would be very costly. --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index c4d687b8..71d090db 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,8 @@ ChangeLog Version 4.2 ----------- - Set a better default for the user option "escaping-funcs". +- The supported Python versions are 2.4 to 2.7. +- PostgreSQL is supported in all versions from 8.3 to 9.4. - Force build to compile with no errors. - Fix decimal point handling. - Add option to return boolean values as bool objects. From 187be21dc9743aaa7e71fddefe14f1b5946c2579 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 6 Jan 2016 21:42:50 +0000 Subject: [PATCH 058/144] Reorganize and improve the Sphinx docs The Sphinx HTML docs can now also serve as home page for the project. They can be built with the mkdocs script and can then be found in the docs/_build/html directory (this needs sphinx and the cloud_sptheme). The "actual" core docs can be found in docs/contents. The layout has been modified to only show these pages with the typical documentation relbars; the other pages of the homepage use a simpler layout. The pg and pgdb documentation has been cut in chunks and can now be found in docs/contents/pg and docs/contents/pgdb. --- docs/{ => _static}/favicon.ico | Bin docs/_static/pygresql.css_t | 86 ++ docs/_templates/layout.html | 58 + docs/{introduction.rst => about.rst} | 19 +- docs/community/bugtracker.rst | 15 + docs/community/homes.rst | 11 + docs/community/index.rst | 17 + docs/community/mailinglist.rst | 11 + docs/community/source.rst | 12 + docs/community/support.rst | 18 + docs/conf.py | 22 +- docs/{ => contents}/changelog.rst | 0 docs/{ => contents}/examples.rst | 3 + docs/contents/general.rst | 35 + docs/contents/index.rst | 23 + docs/{ => contents}/install.rst | 2 +- docs/contents/pg/connection.rst | 362 +++++++ docs/contents/pg/db_wrapper.rst | 315 ++++++ docs/contents/pg/index.rst | 16 + docs/contents/pg/introduction.rst | 24 + docs/contents/pg/large_objects.rst | 180 ++++ docs/contents/pg/module.rst | 469 ++++++++ docs/contents/pg/query.rst | 116 ++ docs/contents/pgdb/connection.rst | 61 ++ docs/contents/pgdb/cursor.rst | 208 ++++ docs/contents/pgdb/index.rst | 15 + docs/contents/pgdb/introduction.rst | 19 + docs/contents/pgdb/module.rst | 109 ++ docs/contents/pgdb/types.rst | 108 ++ docs/copyright.rst | 2 - docs/download/download.rst | 20 + docs/download/files.rst | 33 + docs/download/index.rst | 24 + docs/future.rst | 48 - docs/index.rst | 31 +- docs/interface.rst | 17 - docs/mailinglist.rst | 8 - docs/pg.rst | 1484 -------------------------- docs/pgdb.rst | 522 --------- docs/readme.rst | 208 ---- docs/source.rst | 7 - mkdocs | 1 + mktar | 17 +- 43 files changed, 2417 insertions(+), 2339 deletions(-) rename docs/{ => _static}/favicon.ico (100%) create mode 100644 docs/_static/pygresql.css_t create mode 100644 docs/_templates/layout.html rename docs/{introduction.rst => about.rst} (71%) create mode 100644 docs/community/bugtracker.rst create mode 100644 docs/community/homes.rst create mode 100644 docs/community/index.rst create mode 100644 docs/community/mailinglist.rst create mode 100644 docs/community/source.rst create mode 100644 docs/community/support.rst rename docs/{ => contents}/changelog.rst (100%) rename docs/{ => contents}/examples.rst (81%) create mode 100644 docs/contents/general.rst create mode 100644 docs/contents/index.rst rename docs/{ => contents}/install.rst (98%) create mode 100644 docs/contents/pg/connection.rst create mode 100644 docs/contents/pg/db_wrapper.rst create mode 100644 docs/contents/pg/index.rst create mode 100644 docs/contents/pg/introduction.rst create mode 100644 docs/contents/pg/large_objects.rst create mode 100644 docs/contents/pg/module.rst create mode 100644 docs/contents/pg/query.rst create mode 100644 docs/contents/pgdb/connection.rst create mode 100644 docs/contents/pgdb/cursor.rst create mode 100644 docs/contents/pgdb/index.rst create mode 100644 docs/contents/pgdb/introduction.rst create mode 100644 docs/contents/pgdb/module.rst create mode 100644 docs/contents/pgdb/types.rst create mode 100644 docs/download/download.rst create mode 100644 docs/download/files.rst create mode 100644 docs/download/index.rst delete mode 100644 docs/future.rst delete mode 100644 docs/interface.rst delete mode 100644 docs/mailinglist.rst delete mode 100644 docs/pg.rst delete mode 100644 docs/pgdb.rst delete mode 100644 docs/readme.rst delete mode 100644 docs/source.rst diff --git a/docs/favicon.ico b/docs/_static/favicon.ico similarity index 100% rename from docs/favicon.ico rename to docs/_static/favicon.ico diff --git a/docs/_static/pygresql.css_t b/docs/_static/pygresql.css_t new file mode 100644 index 00000000..a3bc4de2 --- /dev/null +++ b/docs/_static/pygresql.css_t @@ -0,0 +1,86 @@ +{% macro experimental(keyword, value) %} + {% if value %} + -moz-{{keyword}}: {{value}}; + -webkit-{{keyword}}: {{value}}; + -o-{{keyword}}: {{value}}; + -ms-{{keyword}}: {{value}}; + {{keyword}}: {{value}}; + {% endif %} +{% endmacro %} + +{% macro border_radius(value) -%} + {{experimental("border-radius", value)}} +{% endmacro %} + +{% macro box_shadow(value) -%} + {{experimental("box-shadow", value)}} +{% endmacro %} + +.pageheader.related { + text-align: left; + padding: 10px 15px; + border: 1px solid #eeeeee; + margin-bottom: 10px; + {{border_radius("1em 1em 1em 1em")}} + {% if theme_borderless_decor | tobool %} + border-top: 0; + border-bottom: 0; + {% endif %} +} + +.pageheader.related .logo { + font-size: 36px; + font-style: italic; + letter-spacing: 5px; + margin-right: 2em; +} + +.pageheader.related .logo { + font-size: 36px; + font-style: italic; + letter-spacing: 5px; + margin-right: 2em; +} + +.pageheader.related .logo a, .pageheader.related .logo a:hover { + background: transparent; + color: {{ theme_relbarlinkcolor }}; + border: none; + text-decoration: none; + text-shadow: none; + {{box_shadow("none")}} +} + +.pageheader.related ul { + float: right; + margin: 2px 1em; +} + +.pageheader.related li { + float: left; + margin: 0 0 0 10px; +} + +.pageheader.related li a { + padding: 8px 12px; +} + +.norelbar .subtitle { + font-size: 14px; + line-height: 18px; + font-weight: bold; + letter-spacing: 4px; + text-align: right; + padding: 0 1em; + margin-top: -9px; +} + +.relbar-top .related.norelbar { + height: 22px; + border-bottom: 14px solid #eeeeee; +} + +.relbar-bottom .related.norelbar { + height: 22px; + border-top: 14px solid #eeeeee; +} diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html new file mode 100644 index 00000000..1cb2ddee --- /dev/null +++ b/docs/_templates/layout.html @@ -0,0 +1,58 @@ +{%- extends "cloud/layout.html" %} + +{% set css_files = css_files + ["_static/pygresql.css"] %} + +{# + This layout adds a page header above the standard layout. + It also removes the relbars from all pages that are not part + of the core documentation in the contents/ directory, + adapting the navigation bar (breadcrumb) appropriately. +#} + +{% set is_content = pagename.startswith(('contents/', 'genindex', 'modindex', 'py-', 'search')) %} +{% if is_content %} +{% set master_doc = 'contents/index' %} +{% set parents = parents[1:] %} +{% endif %} + +{% block header %} +

+ + +{% endblock %} + +{% block relbar1 -%} +{%- if is_content -%} + {{ super() }} +{% else %} +
+{%- endif -%} +{%- endblock %} + +{% block relbar2 -%} +{%- if is_content -%} + {{ super() }} +{%- else -%} +
+{%- endif -%} +{%- endblock %} + +{% block content -%} +{%- if is_content -%} +{{ super() }} +{%- else -%} +
{{ super() }}
+{%- endif -%} +{%- endblock %} diff --git a/docs/introduction.rst b/docs/about.rst similarity index 71% rename from docs/introduction.rst rename to docs/about.rst index 37087df4..6cfd091d 100644 --- a/docs/introduction.rst +++ b/docs/about.rst @@ -1,14 +1,12 @@ -Introduction -============ - **PyGreSQL** is an *open-source* `Python `_ module that interfaces to a `PostgreSQL `_ database. It embeds the PostgreSQL query library to allow easy use of the powerful PostgreSQL features from a Python script. -| This software is copyright © 1995, Pascal Andre. -| Further modifications are copyright © 1997-2008 by D'Arcy J.M. Cain. -| Further modifications are copyright © 2009-2015 by the PyGreSQL team + | This software is copyright © 1995, Pascal Andre. + | Further modifications are copyright © 1997-2008 by D'Arcy J.M. Cain. + | Further modifications are copyright © 2009-2016 by the PyGreSQL team. + | For licensing details, see the full :doc:`copyright`. **PostgreSQL** is a highly scalable, SQL compliant, open source object-relational database management system. With more than 15 years @@ -32,10 +30,11 @@ even for commercial use. It embeds the PostgreSQL query library to allow easy use of the powerful PostgreSQL features from a Python script. -PyGreSQL is developed and tested on a NetBSD system, but it should also -run on most other platforms where PostgreSQL and Python is running. It is -based on the PyGres95 code written by Pascal Andre (andre@chimay.via.ecp.fr). +PyGreSQL is developed and tested on a NetBSD system, but it also runs on +most other platforms where PostgreSQL and Python is running. It is based +on the PyGres95 code written by Pascal Andre (andre@chimay.via.ecp.fr). D'Arcy (darcy@druid.net) renamed it to PyGreSQL starting with version 2.0 and serves as the "BDFL" of PyGreSQL. -The current version PyGreSQL 4.2 needs PostgreSQL 8.3 and Python 2.5 or above. +The current version PyGreSQL 4.2 needs PostgreSQL 8.3 or newer and Python 2.5 +to 2.7. If you are using Python 3.x, you will need PyGreSQL 5.0 or newer. diff --git a/docs/community/bugtracker.rst b/docs/community/bugtracker.rst new file mode 100644 index 00000000..2708eab4 --- /dev/null +++ b/docs/community/bugtracker.rst @@ -0,0 +1,15 @@ +Bug Tracker +----------- + +We are using `Trac `_ as an issue tracker. + +Track tickets are usually entered after discussion on the mailing list, +but you may also request an account for the issue tracker and add or +process tickets if you want to get more involved into the development +of the project. You can use the following links to get an overview: + +* `PyGreSQL Issues Tracker `_ +* `Timeline with all changes `_ +* `Roadmap of the project `_ +* `Lists of active tickets `_ +* `PyGreSQL Trac browser `_ \ No newline at end of file diff --git a/docs/community/homes.rst b/docs/community/homes.rst new file mode 100644 index 00000000..abf3bae5 --- /dev/null +++ b/docs/community/homes.rst @@ -0,0 +1,11 @@ +Project home sites +------------------ + +**Python**: + http://www.python.org + +**PostgreSQL**: + http://www.postgresql.org + +**PyGreSQL**: + http://www.pygresql.org \ No newline at end of file diff --git a/docs/community/index.rst b/docs/community/index.rst new file mode 100644 index 00000000..83160268 --- /dev/null +++ b/docs/community/index.rst @@ -0,0 +1,17 @@ +PyGreSQL Development and Support +================================ + +PyGreSQL is an open-source project created by a group of volunteers. +The project and the development infrastructure are currently maintained +by D'Arcy J.M. Cain. We would be glad to welcome more contributors +so that PyGreSQL can be further developed, modernized and improved. + +.. include:: mailinglist.rst + +.. include:: source.rst + +.. include:: bugtracker.rst + +.. include:: support.rst + +.. include:: homes.rst diff --git a/docs/community/mailinglist.rst b/docs/community/mailinglist.rst new file mode 100644 index 00000000..c0269512 --- /dev/null +++ b/docs/community/mailinglist.rst @@ -0,0 +1,11 @@ +Mailing list +------------ + +You can join +`the mailing list `_ +to discuss future development of the PyGreSQL interface or if you have +questions or problems with PyGreSQL that are not covered in the +:doc:`documentation <../contents/index>`. + +This is usually a low volume list except when there are new features +being added. diff --git a/docs/community/source.rst b/docs/community/source.rst new file mode 100644 index 00000000..32447326 --- /dev/null +++ b/docs/community/source.rst @@ -0,0 +1,12 @@ +Access to the source repository +------------------------------- + +We are using a central `Subversion `_ +source code repository for PyGreSQL. + +The repository can be checked out with the command:: + + svn co svn://svn.pygresql.org/pygresql + +You can also browse through the repository using the +`PyGreSQL Trac browser `_. diff --git a/docs/community/support.rst b/docs/community/support.rst new file mode 100644 index 00000000..ac4fa6e8 --- /dev/null +++ b/docs/community/support.rst @@ -0,0 +1,18 @@ +Support +------- + +**Python**: + see http://www.python.org/community/ + +**PostgreSQL**: + see http://www.postgresql.org/support/ + +**PyGreSQL**: + Join `the PyGreSQL mailing list `_ + if you need help regarding PyGreSQL. + + Please also send context diffs there, if you would like to proposes changes. + + Please note that messages to individual developers will generally not be + answered directly. All questions, comments and code changes must be + submitted to the mailing list for peer review and archiving purposes. \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index d31c851c..bb7ed2ee 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,8 +15,9 @@ import os import shlex -# import Cloud -import cloud_sptheme as csp +# import Cloud theme +# this will also automatically add the theme directory +import cloud_sptheme # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -76,6 +77,14 @@ # directories to ignore when looking for source files. exclude_patterns = ['_build'] +# List of pages which are included in other pages and therefore should +# not appear in the toctree. +exclude_patterns += ['about.rst', + 'download/download.rst', 'download/files.rst', + 'community/mailinglist.rst', 'community/source.rst', + 'community/bugtracker.rst', 'community/support.rst', + 'community/homes.rst'] + # The reST default role (used for this markup: `text`) for all documents. #default_role = None @@ -112,10 +121,13 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -html_theme_options = {'defaultcollapsed': True} +html_theme_options = { + 'roottarget': 'contents/index', + 'defaultcollapsed': True, + 'shaded_decor': True} # Add any paths that contain custom themes here, relative to this directory. -html_theme_path = [csp.get_theme_dir()] +html_theme_path = ['_themes'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". @@ -131,7 +143,7 @@ # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +html_favicon = '_static/favicon.ico' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/docs/changelog.rst b/docs/contents/changelog.rst similarity index 100% rename from docs/changelog.rst rename to docs/contents/changelog.rst diff --git a/docs/examples.rst b/docs/contents/examples.rst similarity index 81% rename from docs/examples.rst rename to docs/contents/examples.rst index a7227578..2b279dcf 100644 --- a/docs/examples.rst +++ b/docs/contents/examples.rst @@ -5,6 +5,9 @@ I am starting to collect examples of applications that use PyGreSQL. So far I only have a few but if you have an example for me, you can either send me the files or the URL for me to point to. +The *tutorial* directory that is part of the PyGreSQL distribution +shows some examples of using PostgreSQL with PyGreSQL. + Here is a `list of motorcycle rides in Ontario `_ that uses a PostgreSQL database to store the rides. diff --git a/docs/contents/general.rst b/docs/contents/general.rst new file mode 100644 index 00000000..7ac2108e --- /dev/null +++ b/docs/contents/general.rst @@ -0,0 +1,35 @@ +General PyGreSQL programming information +---------------------------------------- + +PyGreSQL consists of two parts: the "classic" PyGreSQL interface +provided by the :mod:`pg` module and the newer +DB-API 2.0 compliant interface provided by the :mod:`pgdb` module. + +If you use only the standard features of the DB-API 2.0 interface, +it will be easier to switch from PostgreSQL to another database +for which a DB-API 2.0 compliant interface exists. + +The "classic" interface may be easier to use for beginners, and it +provides some higher-level and PostgreSQL specific convenience methods. + +.. seealso:: + + **DB-API 2.0** (Python Database API Specification v2.0) + is a specification for connecting to databases (not only PostGreSQL) + from Python that has been developed by the Python DB-SIG in 1999. + The authoritative programming information for the DB-API is :pep:`0249`. + +Both Python modules utilize the same lower level C extension module that +serves as a wrapper for the C API to PostgreSQL that is available in form +of the so-called "libpq" library. + +This means you must have the libpq library installed as a shared library +on your client computer, in a version that is supported by PyGreSQL. +Depending on the client platform, you may have to set environment variables +like `PATH` or `LD_LIBRARY_PATH` so that PyGreSQL can find the library. + +.. warning:: + + Note that PyGreSQL is not thread-safe on the connection level. Therefore + we recommend using `DBUtils `_ + for multi-threaded environments, which supports both PyGreSQL interfaces. diff --git a/docs/contents/index.rst b/docs/contents/index.rst new file mode 100644 index 00000000..392d8e11 --- /dev/null +++ b/docs/contents/index.rst @@ -0,0 +1,23 @@ + +The PyGreSQL documentation +========================== + +Contents +-------- + +.. toctree:: + :maxdepth: 1 + + Installing PyGreSQL + What's new and history of changes + General PyGreSQL programming information + The Classic PyGreSQL Interface + The DB-API Compliant Interface + Examples for using PyGreSQL + +Indices and tables +------------------ + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/install.rst b/docs/contents/install.rst similarity index 98% rename from docs/install.rst rename to docs/contents/install.rst index 8b7448df..4acfd034 100644 --- a/docs/install.rst +++ b/docs/contents/install.rst @@ -5,7 +5,7 @@ General ------- You must first have installed Python and PostgreSQL on your system. -If you want to access remote database only, you don't need to install +If you want to access remote databases only, you don't need to install the full PostgreSQL server, but only the C interface (libpq). If you are on Windows, make sure that the directory with libpq.dll is in your ``PATH`` environment variable. diff --git a/docs/contents/pg/connection.rst b/docs/contents/pg/connection.rst new file mode 100644 index 00000000..ea3ade99 --- /dev/null +++ b/docs/contents/pg/connection.rst @@ -0,0 +1,362 @@ +pgobject -- The connection object +================================= + +.. class:: pgobject + +This object handles a connection to a PostgreSQL database. It embeds and +hides all the parameters that define this connection, thus just leaving really +significant parameters in function calls. + +.. note:: + + Some methods give direct access to the connection socket. + *Do not use them unless you really know what you are doing.* + If you prefer disabling them, + set the ``-DNO_DIRECT`` option in the Python setup file. + These methods are specified by the tag [DA]. + +.. note:: + + Some other methods give access to large objects + (refer to PostgreSQL user manual for more information about these). + If you want to forbid access to these from the module, + set the ``-DNO_LARGE`` option in the Python setup file. + These methods are specified by the tag [LO]. + +query -- execute a SQL command string +------------------------------------- + +.. method:: pgobject.query(command, [args]) + + Execute a SQL command string + + :param str command: SQL command + :param args: optional positional arguments + :returns: result values + :rtype: :class:`pgqueryobject`, None + :raises TypeError: bad argument type, or too many arguments + :raises TypeError: invalid connection + :raises ValueError: empty SQL query or lost connection + :raises pg.ProgrammingError: error in query + :raises pg.InternalError: error during query processing + +This method simply sends a SQL query to the database. If the query is an +insert statement that inserted exactly one row into a table that has OIDs, the +return value is the OID of the newly inserted row. If the query is an update +or delete statement, or an insert statement that did not insert exactly one +row in a table with OIDs, then the number of rows affected is returned as a +string. If it is a statement that returns rows as a result (usually a select +statement, but maybe also an ``"insert/update ... returning"`` statement), +this method returns a :class:`pgqueryobject` that can be accessed via the +:meth:`pgqueryobject.getresult`, :meth:`pgqueryobject.dictresult` or +:meth:`pgqueryobject.namedresult` methods or simply printed. +Otherwise, it returns ``None``. + +The query may optionally contain positional parameters of the form ``$1``, +``$2``, etc instead of literal data, and the values supplied as a tuple. +The values are substituted by the database in such a way that they don't +need to be escaped, making this an effective way to pass arbitrary or +unknown data without worrying about SQL injection or syntax errors. + +When the database could not process the query, a :exc:`pg.ProgrammingError` or +a :exc:`pg.InternalError` is raised. You can check the ``SQLSTATE`` code of +this error by reading its :attr:`sqlstate` attribute. + +Example:: + + name = raw_input("Name? ") + phone = con.query("select phone from employees where name=$1", + (name,)).getresult() + +reset -- reset the connection +----------------------------- + +.. method:: pgobject.reset() + + Reset the :mod:`pg` connection + + :rtype: None + :raises TypeError: too many (any) arguments + :raises TypeError: invalid connection + +This method resets the current database connection. + +cancel -- abandon processing of current SQL command +--------------------------------------------------- + +.. method:: pgobject.cancel() + + :rtype: None + :raises TypeError: too many (any) arguments + :raises TypeError: invalid connection + +This method requests that the server abandon processing +of the current SQL command. + +close -- close the database connection +-------------------------------------- + +.. method:: pgobject.close() + + Close the :mod:`pg` connection + + :rtype: None + :raises TypeError: too many (any) arguments + +This method closes the database connection. The connection will +be closed in any case when the connection is deleted but this +allows you to explicitly close it. It is mainly here to allow +the DB-SIG API wrapper to implement a close function. + +fileno -- returns the socket used to connect to the database +------------------------------------------------------------ + +.. method:: pgobject.fileno() + + Return the socket used to connect to the database + + :returns: the socket id of the database connection + :rtype: int + :raises TypeError: too many (any) arguments + :raises TypeError: invalid connection + +This method returns the underlying socket id used to connect +to the database. This is useful for use in select calls, etc. + +getnotify -- get the last notify from the server +------------------------------------------------ + +.. method:: pgobject.getnotify() + + Get the last notify from the server + + :returns: last notify from server + :rtype: tuple, None + :raises TypeError: too many parameters + :raises TypeError: invalid connection + +This method tries to get a notify from the server (from the SQL statement +NOTIFY). If the server returns no notify, the methods returns None. +Otherwise, it returns a tuple (triplet) *(relname, pid, extra)*, where +*relname* is the name of the notify, *pid* is the process id of the +connection that triggered the notify, and *extra* is a payload string +that has been sent with the notification. Remember to do a listen query +first, otherwise :meth:`pgobject.getnotify` will always return ``None``. + +inserttable -- insert a list into a table +----------------------------------------- + +.. method:: pgobject.inserttable(table, values) + + Insert a Python list into a database table + + :param str table: the table name + :param list values: list of rows values + :rtype: None + :raises TypeError: invalid connection, bad argument type, or too many arguments + :raises MemoryError: insert buffer could not be allocated + :raises ValueError: unsupported values + +This method allows to *quickly* insert large blocks of data in a table: +It inserts the whole values list into the given table. Internally, it +uses the COPY command of the PostgreSQL database. The list is a list +of tuples/lists that define the values for each inserted row. The rows +values may contain string, integer, long or double (real) values. + +.. note:: + + **Be very careful**: + This method doesn't type check the fields according to the table definition; + it just look whether or not it knows how to handle such types. + +get/set_notice_receiver -- custom notice receiver +------------------------------------------------- + +.. method:: pgobject.get_notice_receiver() + + Get the current notice receiver + + :returns: the current notice receiver callable + :rtype: callable, None + :raises TypeError: too many (any) arguments + +This method gets the custom notice receiver callback function that has +been set with :meth:`pgobject.set_notice_receiver`, or ``None`` if no +custom notice receiver has ever been set on the connection. + +.. method:: pgobject.set_notice_receiver(proc) + + Set a custom notice receiver + + :param proc: the custom notice receiver callback function + :rtype: None + :raises TypeError: the specified notice receiver is not callable + +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 :class:`pgnotice` object, which provides the following read-only +attributes: + + .. attribute:: pgnotice.pgcnx + + the connection + + .. attribute:: pgnotice.message + + the full message with a trailing newline + + .. attribute:: pgnotice.severity + + the level of the message, e.g. 'NOTICE' or 'WARNING' + + .. attribute:: pgnotice.primary + + the primary human-readable error message + + .. attribute:: pgnotice.detail + + an optional secondary error message + + .. attribute:: pgnotice.hint + + an optional suggestion what to do about the problem + +putline -- write a line to the server socket [DA] +------------------------------------------------- + +.. method:: pgobject.putline(line) + + Write a line to the server socket + + :param str line: line to be written + :rtype: None + :raises TypeError: invalid connection, bad parameter type, or too many parameters + +This method allows to directly write a string to the server socket. + +getline -- get a line from server socket [DA] +--------------------------------------------- + +.. method:: pgobject.getline() + + Get a line from server socket + + :returns: the line read + :rtype: str + :raises TypeError: invalid connection + :raises TypeError: too many parameters + :raises MemoryError: buffer overflow + +This method allows to directly read a string from the server socket. + +endcopy -- synchronize client and server [DA] +--------------------------------------------- + +.. method:: pgobject.endcopy() + + Synchronize client and server + + :rtype: None + :raises TypeError: invalid connection + :raises TypeError: too many parameters + +The use of direct access methods may desynchronize client and server. +This method ensure that client and server will be synchronized. + +locreate -- create a large object in the database [LO] +------------------------------------------------------ + +.. method:: pgobject.locreate(mode) + + Create a large object in the database + + :param int mode: large object create mode + :returns: object handling the PostGreSQL large object + :rtype: :class:`pglarge` + :raises TypeError: invalid connection, bad parameter type, or too many parameters + :raises pg.OperationalError: creation error + +This method creates a large object in the database. The mode can be defined +by OR-ing the constants defined in the :mod:`pg` module (:const:`INV_READ`, +:const:`INV_WRITE` and :const:`INV_ARCHIVE`). Please refer to PostgreSQL +user manual for a description of the mode values. + +getlo -- build a large object from given oid [LO] +------------------------------------------------- + +.. method:: pgobject.getlo(oid) + + Create a large object in the database + + :param int oid: OID of the existing large object + :returns: object handling the PostGreSQL large object + :rtype: :class:`pglarge` + :raises TypeError: invalid connection, bad parameter type, or too many parameters + :raises ValueError: bad OID value (0 is invalid_oid) + +This method allows to reuse a formerly created large object through the +:class:`pglarge` interface, providing the user have its OID. + +loimport -- import a file to a large object [LO] +------------------------------------------------ + +.. method:: pgobject.loimport(name) + + Import a file to a large object + + :param str name: the name of the file to be imported + :returns: object handling the PostGreSQL large object + :rtype: :class:`pglarge` + :raises TypeError: invalid connection, bad argument type, or too many arguments + :raises pg.OperationalError: error during file import + +This methods allows to create large objects in a very simple way. You just +give the name of a file containing the data to be used. + +Object attributes +----------------- +Every :class:`pgobject` defines a set of read-only attributes that describe +the connection and its status. These attributes are: + +.. attribute:: pgobject.host + + the host name of the server (str) + +.. attribute:: pgobject.port + + the port of the server (int) + +.. attribute:: pgobject.db + + the selected database (str) + +.. attribute:: pgobject.options + + the connection options (str) + +.. attribute:: pgobject.tty + + the connection debug terminal (str) + +.. attribute:: pgobject.user + + user name on the database system (str) + +.. attribute:: pgobject.protocol_version + + the frontend/backend protocol being used (int) + +.. attribute:: pgobject.server_version + + the backend version (int, e.g. 80305 for 8.3.5) + +.. attribute:: pgobject.status + + the status of the connection (int: 1 = OK, 0 = bad) + +.. attribute:: pgobject.error + + the last warning/error message from the server (str) diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst new file mode 100644 index 00000000..31d6c22e --- /dev/null +++ b/docs/contents/pg/db_wrapper.rst @@ -0,0 +1,315 @@ +The DB wrapper class +==================== + +.. class:: DB + +The :class:`pgobject` methods are wrapped in the class :class:`DB`. +The preferred way to use this module is as follows:: + + import pg + + db = pg.DB(...) # see below + + for r in db.query( # just for example + """SELECT foo,bar + FROM foo_bar_table + WHERE foo !~ bar""" + ).dictresult(): + + print '%(foo)s %(bar)s' % r + +This class can be subclassed as in this example:: + + import pg + + class DB_ride(pg.DB): + """Ride database wrapper + + This class encapsulates the database functions and the specific + methods for the ride database.""" + + def __init__(self): + """Open a database connection to the rides database""" + pg.DB.__init__(self, dbname='ride') + self.query("SET DATESTYLE TO 'ISO'") + + [Add or override methods here] + +The following describes the methods and variables of this class. + +Initialization +-------------- +The :class:`DB` class is initialized with the same arguments as the +:func:`connect` function described above. It also initializes a few +internal variables. The statement ``db = DB()`` will open the local +database with the name of the user just like ``connect()`` does. + +You can also initialize the DB class with an existing :mod:`pg` or :mod:`pgdb` +connection. Pass this connection as a single unnamed parameter, or as a +single parameter named ``db``. This allows you to use all of the methods +of the DB class with a DB-API 2 compliant connection. Note that the +:meth:`pgobject.close` and :meth:`pgobject.reopen` methods are inoperative +in this case. + +pkey -- return the primary key of a table +----------------------------------------- + +.. method:: DB.pkey(table) + + Return the primary key of a table + + :param str table: name of table + :returns: Name of the field which is the primary key of the table + :rtype: str + +This method returns the primary key of a table. For composite primary +keys, the return value will be a frozenset. Note that this raises an +exception if the table does not have a primary key. + +get_databases -- get list of databases in the system +---------------------------------------------------- + +.. method:: DB.get_databases() + + Get the list of databases in the system + + :returns: all databases in the system + :rtype: list + +Although you can do this with a simple select, it is added here for +convenience. + +get_relations -- get list of relations in connected database +------------------------------------------------------------ + +.. method:: DB.get_relations(kinds) + + Get the list of relations in connected database + + :param str kinds: a string or sequence of type letters + :returns: all relations of the given kinds in the database + :rtype: list + +The type letters are ``r`` = ordinary table, ``i`` = index, ``S`` = sequence, +``v`` = view, ``c`` = composite type, ``s`` = special, ``t`` = TOAST table. +If `kinds` is None or an empty string, all relations are returned (this is +also the default). Although you can do this with a simple select, it is +added here for convenience. + +get_tables -- get list of tables in connected database +------------------------------------------------------ + +.. method:: DB.get_tables() + + Get the list of tables in connected database + + :returns: all tables in connected database + :rtype: list + +This is a shortcut for ``get_relations('r')`` that has been added for +convenience. + +get_attnames -- get the attribute names of a table +-------------------------------------------------- + +.. method:: DB.get_attnames(table) + + Get the attribute names of a table + + :param str table: name of table + :returns: A dictionary -- the keys are the attribute names, + the values are the type names of the attributes. + +Given the name of a table, digs out the set of attribute names. + +has_table_privilege -- check whether current user has specified table privilege +------------------------------------------------------------------------------- + +.. method:: DB.has_table_privilege(table, privilege) + + Check whether current user has specified table privilege + + :param str table: the name of the table + :param str privilege: privilege to be checked -- default is 'select' + :returns: whether current user has specified table privilege + :rtype: bool + +Returns True if the current user has the specified privilege for the table. + +get -- get a row from a database table or view +---------------------------------------------- + +.. method:: DB.get(table, arg, [keyname]) + + Get a row from a database table or view + + :param str table: name of table or view + :param arg: either a dictionary or the value to be looked up + :param str keyname: name of field to use as key (optional) + :returns: A dictionary - the keys are the attribute names, + the values are the row values. + +This method is the basic mechanism to get a single row. It assumes +that the key specifies a unique row. If *keyname* is not specified, +then the primary key for the table is used. If *arg* is a dictionary +then the value for the key is taken from it and it is modified to +include the new values, replacing existing values where necessary. +For a composite key, *keyname* can also be a sequence of key names. +The OID is also put into the dictionary if the table has one, but in +order to allow the caller to work with multiple tables, it is munged +as ``oid(schema.table)``. + +insert -- insert a row into a database table +-------------------------------------------- + +.. method:: DB.insert(table, [d,] [key = val, ...]) + + Insert a row into a database table + + :param str table: name of table + :param dict d: optional dictionary of values + :returns: the inserted values + :rtype: dict + +This method inserts a row into a table. If the optional dictionary is +not supplied then the required values must be included as keyword/value +pairs. If a dictionary is supplied then any keywords provided will be +added to or replace the entry in the dictionary. + +The dictionary is then, if possible, reloaded with the values actually +inserted in order to pick up values modified by rules, triggers, etc. + +Note: The method currently doesn't support insert into views +although PostgreSQL does. + +update -- update a row in a database table +------------------------------------------ + +.. method:: DB.update(table, [d,] [key = val, ...]) + + Update a row in a database table + + :param str table: name of table + :param dict d: optional dictionary of values + :returns: the new row + :rtype: dict + +Similar to insert but updates an existing row. The update is based on the +OID value as munged by get or passed as keyword, or on the primary key of +the table. The dictionary is modified, if possible, to reflect any changes +caused by the update due to triggers, rules, default values, etc. + +Like insert, the dictionary is optional and updates will be performed +on the fields in the keywords. There must be an OID or primary key +either in the dictionary where the OID must be munged, or in the keywords +where it can be simply the string 'oid'. + +query -- execute a SQL command string +------------------------------------- + +.. method:: DB.query(command, [arg1, [arg2, ...]]) + + Execute a SQL command string + + :param str command: SQL command + :param arg*: optional positional arguments + :returns: result values + :rtype: :class:`pgqueryobject`, None + :raises TypeError: bad argument type, or too many arguments + :raises TypeError: invalid connection + :raises ValueError: empty SQL query or lost connection + :raises pg.ProgrammingError: error in query + :raises pg.InternalError: error during query processing + +Similar to the :class:`pgobject` function with the same name, except that +positional arguments can be passed either as a single list or tuple, or as +individual positional arguments. + +Example:: + + name = raw_input("Name? ") + phone = raw_input("Phone? ") + rows = db.query("update employees set phone=$2 where name=$1", + (name, phone)).getresult()[0][0] + # or + rows = db.query("update employees set phone=$2 where name=$1", + name, phone).getresult()[0][0] + +clear -- clear row values in memory +----------------------------------- + +.. method:: DB.clear(table, [a]) + + Clear row values in memory + + :param str table: name of table + :param dict a: optional dictionary of values + :returns: an empty row + :rtype: dict + +This method clears all the attributes to values determined by the types. +Numeric types are set to 0, Booleans are set to ``'f'``, dates are set +to ``'now()'`` and everything else is set to the empty string. +If the array argument is present, it is used as the array and any entries +matching attribute names are cleared with everything else left unchanged. + +If the dictionary is not supplied a new one is created. + +delete -- delete a row from a database table +-------------------------------------------- + +.. method:: DB.delete(table, [d,] [key = val, ...]) + + Delete a row from a database table + + :param str table: name of table + :param dict d: optional dictionary of values + :rtype: None + +This method deletes the row from a table. It deletes based on the OID value +as munged by get or passed as keyword, or on the primary key of the table. +The return value is the number of deleted rows (i.e. 0 if the row did not +exist and 1 if the row was deleted). + +escape_string -- escape a string for use within SQL +--------------------------------------------------- + +.. method:: DB.escape_string(string) + + Escape a string for use within SQL + + :param str string: the string that is to be escaped + :returns: the escaped string + :rtype: str + +Similar to the module function with the same name, but the +behavior of this method is adjusted depending on the connection properties +(such as character encoding). + +escape_bytea -- escape binary data for use within SQL +----------------------------------------------------- + +.. method:: DB.escape_bytea(datastring) + + Escape binary data for use within SQL as type ``bytea`` + + :param str datastring: string containing the binary data that is to be escaped + :returns: the escaped string + :rtype: str + +Similar to the module function with the same name, but the +behavior of this method is adjusted depending on the connection properties +(in particular, whether standard-conforming strings are enabled). + +unescape_bytea -- unescape data that has been retrieved as text +--------------------------------------------------------------- + +.. method:: DB.unescape_bytea(string) + + Unescape ``bytea`` data that has been retrieved as text + + :param datastring: the ``bytea`` data string that has been retrieved as text + :returns: byte string containing the binary data + :rtype: str + +See the module function with the same name. diff --git a/docs/contents/pg/index.rst b/docs/contents/pg/index.rst new file mode 100644 index 00000000..be9fd32d --- /dev/null +++ b/docs/contents/pg/index.rst @@ -0,0 +1,16 @@ +-------------------------------------------- +:mod:`pg` --- The Classic PyGreSQL Interface +-------------------------------------------- + +.. module:: pg + +Contents +======== + +.. toctree:: + introduction + module + connection + db_wrapper + query + large_objects diff --git a/docs/contents/pg/introduction.rst b/docs/contents/pg/introduction.rst new file mode 100644 index 00000000..a8719faa --- /dev/null +++ b/docs/contents/pg/introduction.rst @@ -0,0 +1,24 @@ +Introduction +============ + +You may either choose to use the "classic" PyGreSQL interface provided by +the :mod:`pg` module or else the newer DB-API 2.0 compliant interface +provided by the :mod:`pgdb` module. + +The following part of the documentation covers only the older :mod:`pg` API. + +The :mod:`pg` module handles three types of objects, + +- the :class:`pgobject`, which handles the connection + and all the requests to the database, +- the :class:`pglarge` object, which handles + all the accesses to PostgreSQL large objects, +- the :class:`pgqueryobject` that handles query results + +and it provides a convenient wrapper class :class:`DB` +for the :class:`pgobject`. + +.. seealso:: + + If you want to see a simple example of the use of some of these functions, + see the :doc:`../examples` page. diff --git a/docs/contents/pg/large_objects.rst b/docs/contents/pg/large_objects.rst new file mode 100644 index 00000000..b0bd013d --- /dev/null +++ b/docs/contents/pg/large_objects.rst @@ -0,0 +1,180 @@ +pglarge -- Large Objects +======================== + +.. class:: pglarge + +Objects that are instances of the class :class:`pglarge` are used to handle +all the requests concerning a PostgreSQL large object. These objects embed +and hide all the "recurrent" variables (object OID and connection), exactly +in the same way :class:`pgobject` instances do, thus only keeping significant +parameters in function calls. The :class:`pglarge` object keeps a reference +to the :class:`pgobject` used for its creation, sending requests though with +its parameters. Any modification but dereferencing the :class:`pgobject` +will thus affect the :class:`pglarge` object. Dereferencing the initial +:class:`pgobject` is not a problem since Python won't deallocate it before +the :class:`pglarge` object dereferences it. All functions return a generic +error message on call error, whatever the exact error was. The :attr:`error` +attribute of the object allows to get the exact error message. + +See also the PostgreSQL programmer's guide for more information about the +large object interface. + +open -- open a large object +--------------------------- + +.. method:: pglarge.open(mode) + + Open a large object + + :param int mode: open mode definition + :rtype: None + :raises TypeError: invalid connection, bad parameter type, or too many parameters + :raises IOError: already opened object, or open error + +This method opens a large object for reading/writing, in the same way than the +Unix open() function. The mode value can be obtained by OR-ing the constants +defined in the :mod:`pg` module (:const:`INV_READ`, :const:`INV_WRITE`). + +close -- close a large object +----------------------------- + +.. method:: pglarge.close() + + Close a large object + + :rtype: None + :raises TypeError: invalid connection + :raises TypeError: too many parameters + :raises IOError: object is not opened, or close error + +This method closes a previously opened large object, in the same way than +the Unix close() function. + +read, write, tell, seek, unlink -- file-like large object handling +------------------------------------------------------------------ + +.. method:: pglarge.read(size) + + Read data from large object + + :param int size: maximal size of the buffer to be read + :returns: the read buffer + :rtype: str + :raises TypeError: invalid connection, invalid object, + bad parameter type, or too many parameters + :raises ValueError: if `size` is negative + :raises IOError: object is not opened, or read error + +This function allows to read data from a large object, starting at current +position. + +.. method:: pglarge.write(string) + + Read data to large object + + :param str string: string buffer to be written + :rtype: None + :raises TypeError: invalid connection, bad parameter type, or too many parameters + :raises IOError: object is not opened, or write error + +This function allows to write data to a large object, starting at current +position. + +.. method:: pglarge.seek(offset, whence) + + Change current position in large object + + :param int offset: position offset + :param int whence: positional parameter + :returns: new position in object + :rtype: int + :raises TypeError: invalid connection or invalid object, + bad parameter type, or too many parameters + :raises IOError: object is not opened, or seek error + +This method allows to move the position cursor in the large object. +The valid values for the whence parameter are defined as constants in the +:mod:`pg` module (:const:`SEEK_SET`, :const:`SEEK_CUR`, :const:`SEEK_END`). + +.. method:: pglarge.tell() + + Return current position in large object + + :returns: current position in large object + :rtype: int + :raises TypeError: invalid connection or invalid object + :raises TypeError: too many parameters + :raises IOError: object is not opened, or seek error + +This method allows to get the current position in the large object. + +.. method:: pglarge.unlink() + + Delete large object + + :rtype: None + :raises TypeError: invalid connection or invalid object + :raises TypeError: too many parameters + :raises IOError: object is not closed, or unlink error + +This methods unlinks (deletes) the PostgreSQL large object. + +size -- get the large object size +--------------------------------- + +.. method:: pglarge.size() + + Return the large object size + + :returns: the large object size + :rtype: int + :raises TypeError: invalid connection or invalid object + :raises TypeError: too many parameters + :raises IOError: object is not opened, or seek/tell error + +This (composite) method allows to get the size of a large object. It was +implemented because this function is very useful for a web interfaced +database. Currently, the large object needs to be opened first. + +export -- save a large object to a file +--------------------------------------- + +.. method:: pglarge.export(name) + + Export a large object to a file + + :param str name: file to be created + :rtype: None + :raises TypeError: invalid connection or invalid object, + bad parameter type, or too many parameters + :raises IOError: object is not closed, or export error + +This methods allows to dump the content of a large object in a very simple +way. The exported file is created on the host of the program, not the +server host. + +Object attributes +----------------- +:class:`pglarge` objects define a read-only set of attributes that allow +to get some information about it. These attributes are: + +.. attribute:: pglarge.oid + + the OID associated with the object (int) + +.. attribute:: pglarge.pgcnx + + the :class:`pgobject` associated with the object + +.. attribute:: pglarge.error + + the last warning/error message of the connection + +.. warning:: + + In multi-threaded environments, :attr:`pglarge.error` may be modified by + another thread using the same :class:`pgobject`. Remember these object + are shared, not duplicated. You should provide some locking to be able + if you want to check this. The :attr:`pglarge.oid` attribute is very + interesting, because it allows you to reuse the OID later, creating the + :class:`pglarge` object with a :meth:`pgobject.getlo` method call. diff --git a/docs/contents/pg/module.rst b/docs/contents/pg/module.rst new file mode 100644 index 00000000..2cf6d2e7 --- /dev/null +++ b/docs/contents/pg/module.rst @@ -0,0 +1,469 @@ +Module functions and constants +============================== + +The :mod:`pg` module defines a few functions that allow to connect +to a database and to define "default variables" that override +the environment variables used by PostgreSQL. + +These "default variables" were designed to allow you to handle general +connection parameters without heavy code in your programs. You can prompt the +user for a value, put it in the default variable, and forget it, without +having to modify your environment. The support for default variables can be +disabled by setting the ``-DNO_DEF_VAR`` option in the Python setup file. +Methods relative to this are specified by the tag [DV]. + +All variables are set to ``None`` at module initialization, specifying that +standard environment variables should be used. + +connect -- Open a PostgreSQL connection +--------------------------------------- + +.. function:: pg.connect([dbname], [host], [port], [opt], [tty], [user], [passwd]) + + Open a :mod:`pg` connection + + :param dbname: name of connected database (*None* = :data:`defbase`) + :type str: str or None + :param host: name of the server host (*None* = :data:`defhost`) + :type host: str or None + :param port: port used by the database server (-1 = :data:`defport`) + :type port: int + :param opt: connection options (*None* = :data:`defopt`) + :type opt: str or None + :param tty: debug terminal (*None* = :data:`deftty`) + :type tty: str or None + :param user: PostgreSQL user (*None* = :data:`defuser`) + :type user: str or None + :param passwd: password for user (*None* = :data:`defpasswd`) + :type passwd: str or None + :returns: If successful, the :class:`pgobject` handling the connection + :rtype: :class:`pgobject` + :raises TypeError: bad argument type, or too many arguments + :raises SyntaxError: duplicate argument definition + :raises pg.InternalError: some error occurred during pg connection definition + :raises Exception: (all exceptions relative to object allocation) + +This function opens a connection to a specified database on a given +PostgreSQL server. You can use keywords here, as described in the +Python tutorial. The names of the keywords are the name of the +parameters given in the syntax line. For a precise description +of the parameters, please refer to the PostgreSQL user manual. + +Example:: + + import pg + + con1 = pg.connect('testdb', 'myhost', 5432, None, None, 'bob', None) + con2 = pg.connect(dbname='testdb', host='localhost', user='bob') + +get/set_defhost -- default server host [DV] +------------------------------------------- + +.. function:: get_defhost(host) + + Get the default host + + :returns: the current default host specification + :rtype: str or None + :raises TypeError: too many arguments + +This method returns the current default host specification, +or ``None`` if the environment variables should be used. +Environment variables won't be looked up. + +.. function:: set_defhost(host) + + Set the default host + + :param host: the new default host specification + :type host: str or None + :returns: the previous default host specification + :rtype: str or None + :raises TypeError: bad argument type, or too many arguments + +This methods sets the default host value for new connections. +If ``None`` is supplied as parameter, environment variables will +be used in future connections. It returns the previous setting +for default host. + +get/set_defport -- default server port [DV] +------------------------------------------- + +.. function:: get_defport() + + Get the default port + + :returns: the current default port specification + :rtype: int + :raises TypeError: too many arguments + +This method returns the current default port specification, +or ``None`` if the environment variables should be used. +Environment variables won't be looked up. + +.. function:: set_defport(port) + + Set the default port + + :param port: the new default port + :type port: int + :returns: previous default port specification + :rtype: int or None + +This methods sets the default port value for new connections. If -1 is +supplied as parameter, environment variables will be used in future +connections. It returns the previous setting for default port. + +get/set_defopt -- default connection options [DV] +-------------------------------------------------- + +.. function:: get_defopt() + + Get the default connection options + + :returns: the current default options specification + :rtype: str or None + :raises TypeError: too many arguments + +This method returns the current default connection options specification, +or ``None`` if the environment variables should be used. Environment variables +won't be looked up. + +.. function:: set_defopt(options) + + Set the default connection options + + :param options: the new default connection options + :type options: str or None + :returns: previous default options specification + :rtype: str or None + :raises TypeError: bad argument type, or too many arguments + +This methods sets the default connection options value for new connections. +If ``None`` is supplied as parameter, environment variables will be used in +future connections. It returns the previous setting for default options. + +get/set_deftty -- default debug tty [DV] +---------------------------------------- + +.. function:: get_deftty() + + Get the default debug terminal + + :returns: the current default debug terminal specification + :rtype: str or None + :raises TypeError: too many arguments + +This method returns the current default debug terminal specification, or +``None`` if the environment variables should be used. Environment variables +won't be looked up. Note that this is ignored in newer PostgreSQL versions. + +.. function:: set_deftty(terminal) + + Set the default debug terminal + + :param terminal: the new default debug terminal + :type terminal: str or None + :returns: the previous default debug terminal specification + :rtype: str or None + :raises TypeError: bad argument type, or too many arguments + +This methods sets the default debug terminal value for new connections. +If ``None`` is supplied as parameter, environment variables will be used +in future connections. It returns the previous setting for default terminal. +Note that this is ignored in newer PostgreSQL versions. + +get/set_defbase -- default database name [DV] +--------------------------------------------- + +.. function:: get_defbase() + + Get the default database name + + :returns: the current default database name specification + :rtype: str or None + :raises TypeError: too many arguments + +This method returns the current default database name specification, or +``None`` if the environment variables should be used. Environment variables +won't be looked up. + +.. function:: set_defbase(base) + + Set the default database name + + :param base: the new default base name + :type base: str or None + :returns: the previous default database name specification + :rtype: str or None + :raises TypeError: bad argument type, or too many arguments + +This method sets the default database name value for new connections. If +``None`` is supplied as parameter, environment variables will be used in +future connections. It returns the previous setting for default host. + +get/set_defuser -- default database user [DV] +--------------------------------------------- + +.. function:: get_defuser() + + Get the default database user + + :returns: the current default database user specification + :rtype: str or None + :raises TypeError: too many arguments + +This method returns the current default database user specification, or +``None`` if the environment variables should be used. Environment variables +won't be looked up. + +.. function:: set_defuser(user) + + Set the default database user + + :param user: the new default database user + :type base: str or None + :returns: the previous default database user specification + :rtype: str or None + :raises TypeError: bad argument type, or too many arguments + +This method sets the default database user name for new connections. If +``None`` is supplied as parameter, environment variables will be used in +future connections. It returns the previous setting for default host. + +get/set_defpasswd -- default database password [DV] +--------------------------------------------------- + +.. function:: get_defpasswd() + + Get the default database password + + :returns: the current default database password specification + :rtype: str or None + :raises TypeError: too many arguments + +This method returns the current default database password specification, or +``None`` if the environment variables should be used. Environment variables +won't be looked up. + +.. function:: set_defpasswd(passwd) + + Set the default database password + + :param passwd: the new default database password + :type base: str or None + :returns: the previous default database password specification + :rtype: str or None + :raises TypeError: bad argument type, or too many arguments + +This method sets the default database password for new connections. If +``None`` is supplied as parameter, environment variables will be used in +future connections. It returns the previous setting for default host. + +escape_string -- escape a string for use within SQL +--------------------------------------------------- + +.. function:: escape_string(string) + + Escape a string for use within SQL + + :param str string: the string that is to be escaped + :returns: the escaped string + :rtype: str + :raises TypeError: bad argument type, or too many arguments + +This function escapes a string for use within an SQL command. +This is useful when inserting data values as literal constants +in SQL commands. Certain characters (such as quotes and backslashes) +must be escaped to prevent them from being interpreted specially +by the SQL parser. :func:`escape_string` performs this operation. +Note that there is also a :class:`pgobject` method with the same name +which takes connection properties into account. + +.. note:: + + It is especially important to do proper escaping when + handling strings that were received from an untrustworthy source. + Otherwise there is a security risk: you are vulnerable to "SQL injection" + attacks wherein unwanted SQL commands are fed to your database. + +Example:: + + name = raw_input("Name? ") + phone = con.query("select phone from employees where name='%s'" + % escape_string(name)).getresult() + +escape_bytea -- escape binary data for use within SQL +----------------------------------------------------- + +.. function:: escape_bytea(datastring) + + escape binary data for use within SQL as type ``bytea`` + + :param str datastring: string containing the binary data that is to be escaped + :returns: the escaped string + :rtype: str + :raises TypeError: bad argument type, or too many arguments + +Escapes binary data for use within an SQL command with the type ``bytea``. +As with :func:`escape_string`, this is only used when inserting data directly +into an SQL command string. +Note that there is also a :class:`pgobject` method with the same name +which takes connection properties into account. + +Example:: + + picture = open('garfield.gif', 'rb').read() + con.query("update pictures set img='%s' where name='Garfield'" + % escape_bytea(picture)) + +unescape_bytea -- unescape data that has been retrieved as text +--------------------------------------------------------------- + +.. function:: unescape_bytea(string) + + Unescape ``bytea`` data that has been retrieved as text + + :param str datastring: the ``bytea`` data string that has been retrieved as text + :returns: byte string containing the binary data + :rtype: str + :raises TypeError: bad argument type, or too many arguments + +Converts an escaped string representation of binary data into binary +data -- the reverse of :func:`escape_bytea`. This is needed when retrieving +``bytea`` data with one of the :meth:`pgqueryobject.getresult`, +:meth:`pgqueryobject.dictresult` or :meth:`pgqueryobject.namedresult` methods. + +Example:: + + picture = unescape_bytea(con.query( + "select img from pictures where name='Garfield'").getresult[0][0]) + open('garfield.gif', 'wb').write(picture) + +get/set_decimal -- decimal type to be used for numeric values +------------------------------------------------------------- + +.. function:: get_decimal() + + Get the decimal type to be used for numeric values + + :returns: the Python class used for PostgreSQL numeric values + :rtype: class + +This function returns the Python class that is used by PyGreSQL to hold +PostgreSQL numeric values. The default class is :class:`decimal.Decimal` +if available, otherwise the :class:`float` type is used. + +.. function:: set_decimal(cls) + + Set a decimal type to be used for numeric values + + :param class cls: the Python class to be used for PostgreSQL numeric values + +This function can be used to specify the Python class that shall +be used by PyGreSQL to hold PostgreSQL numeric values. +The default class is :class:`decimal.Decimal` if available, +otherwise the :class:`float` type is used. + +get/set_decimal_point -- decimal mark used for monetary values +-------------------------------------------------------------- + +.. function:: get_decimal_point() + + Get the decimal mark used for monetary values + + :returns: string with one character representing the decimal mark + :rtype: str + +This function returns the decimal mark used by PyGreSQL to interpret +PostgreSQL monetary values when converting them to decimal numbers. +The default setting is ``'.'`` as a decimal point. This setting is not +adapted automatically to the locale used by PostGreSQL, but you can +use ``set_decimal()`` to set a different decimal mark manually. A return +value of ``None`` means monetary values are not interpreted as decimal +numbers, but returned as strings including the formatting and currency. + +.. function:: set_decimal_point(string) + + Specify which decimal mark is used for interpreting monetary values + + :param str string: string with one character representing the decimal mark + +This function can be used to specify the decimal mark used by PyGreSQL +to interpret PostgreSQL monetary values. The default value is '.' as +a decimal point. This value is not adapted automatically to the locale +used by PostGreSQL, so if you are dealing with a database set to a +locale that uses a ``','`` instead of ``'.'`` as the decimal point, +then you need to call ``set_decimal(',')`` to have PyGreSQL interpret +monetary values correctly. If you don't want money values to be converted +to decimal numbers, then you can call ``set_decimal(None)``, which will +cause PyGreSQL to return monetary values as strings including their +formatting and currency. + +get/set_bool -- whether boolean values are returned as bool objects +------------------------------------------------------------------- + +.. function:: get_bool() + + Check whether boolean values are returned as bool objects + + :returns: whether or not bool objects will be returned + :rtype: bool + +This function checks whether PyGreSQL returns PostgreSQL boolean +values converted to Python bool objects, or as ``'f'`` and ``'t'`` +strings which are the values used internally by PostgreSQL. By default, +conversion to bool objects is not activated, but you can enable +this with the ``set_bool()`` method. + +.. function:: set_bool(on) + + Set whether boolean values are returned as bool objects + + :param on: whether or not bool objects shall be returned + +This function can be used to specify whether PyGreSQL shall return +PostgreSQL boolean values converted to Python bool objects, or as +``'f'`` and ``'t'`` strings which are the values used internally by PostgreSQL. +By default, conversion to bool objects is not activated, but you can +enable this by calling ``set_bool(True)``. + +get/set_namedresult -- conversion to named tuples +------------------------------------------------- + +.. function:: get_namedresult() + + Get the function that converts to named tuples + +This function returns the function used by PyGreSQL to construct the +result of the :meth:`pgqueryobject.namedresult` method. + +.. function:: set_namedresult(func) + + Set a function that will convert to named tuples + + :param func: the function to be used to convert results to named tuples + +You can use this if you want to create different kinds of named tuples +returned by the :meth:`pgqueryobject.namedresult` method. + + +Module constants +---------------- +Some constants are defined in the module dictionary. +They are intended to be used as parameters for methods calls. +You should refer to the libpq description in the PostgreSQL user manual +for more information about them. These constants are: + +.. data:: version, __version__ + + constants that give the current version + +.. data:: INV_READ, INV_WRITE + + large objects access modes, + used by :meth:`pgobject.locreate` and :meth:`pglarge.open` + +.. data:: SEEK_SET, SEEK_CUR, SEEK_END: + + positional flags, used by :meth:`pglarge.seek` diff --git a/docs/contents/pg/query.rst b/docs/contents/pg/query.rst new file mode 100644 index 00000000..e7738665 --- /dev/null +++ b/docs/contents/pg/query.rst @@ -0,0 +1,116 @@ +pgqueryobject methods +===================== + +.. class:: pgqueryobject + +The :class:`pgqueryobject` returned by :meth:`pgobject.query` and +:meth:`DB.query` provides the following methods for accessing +the results of the query: + +getresult -- get query values as list of tuples +----------------------------------------------- + +.. method:: pgqueryobject.getresult() + + Get query values as list of tuples + + :returns: result values as a list of tuples + :rtype: list + :raises TypeError: too many (any) parameters + :raises MemoryError: internal memory error + +This method returns the list of the values returned by the query. +More information about this result may be accessed using +:meth:`pgqueryobject.listfields`, :meth:`pgqueryobject.fieldname` +and :meth:`pgqueryobject.fieldnum` methods. + +dictresult -- get query values as list of dictionaries +------------------------------------------------------ + +.. method:: pgqueryobject.dictresult() + + Get query values as list of dictionaries + + :returns: result values as a list of dictionaries + :rtype: list + :raises TypeError: too many (any) parameters + :raises MemoryError: internal memory error + +This method returns the list of the values returned by the query +with each tuple returned as a dictionary with the field names +used as the dictionary index. + +namedresult -- get query values as list of named tuples +------------------------------------------------------- + +.. method:: pgqueryobject.namedresult() + + Get query values as list of named tuples + + :returns: result values as a list of named tuples + :rtype: list + :raises TypeError: too many (any) parameters + :raises TypeError: named tuples not supported + :raises MemoryError: internal memory error + +This method returns the list of the values returned by the query +with each row returned as a named tuple with proper field names. + +listfields -- list fields names of previous query result +-------------------------------------------------------- + +.. method:: pgqueryobject.listfields() + + List fields names of previous query result + + :returns: field names + :rtype: list + :raises TypeError: too many parameters + +This method returns the list of names of the fields defined for the +query result. The fields are in the same order as the result values. + +fieldname, fieldnum -- field name/number conversion +--------------------------------------------------- + +.. method:: pgqueryobject.fieldname(num) + + Get field name from its number + + :param int num: field number + :returns: field name + :rtype: str + :raises TypeError: invalid connection, bad parameter type, or too many parameters + :raises ValueError: invalid field number + +This method allows to find a field name from its rank number. It can be +useful for displaying a result. The fields are in the same order as the +result values. + +.. method:: pgqueryobject.fieldnum(name) + + Get field number from its name + + :param str name: field name + :returns: field number + :rtype: int + :raises TypeError: invalid connection, bad parameter type, or too many parameters + :raises ValueError: unknown field name + +This method returns a field number from its name. It can be used to +build a function that converts result list strings to their correct +type, using a hardcoded table definition. The number returned is the +field rank in the result values list. + +ntuples -- return number of tuples in query object +-------------------------------------------------- + +.. method:: pgqueryobject.ntuples() + + Return number of tuples in query object + + :returns: number of tuples in :class:`pgqueryobject` + :rtype: int + :raises TypeError: Too many arguments. + +This method returns the number of tuples found in a query. diff --git a/docs/contents/pgdb/connection.rst b/docs/contents/pgdb/connection.rst new file mode 100644 index 00000000..579c78c1 --- /dev/null +++ b/docs/contents/pgdb/connection.rst @@ -0,0 +1,61 @@ +pgdbCnx -- The connection object +================================ + +.. class:: pgdbCnx + +These connection objects respond to the following methods. + +Note that ``pgdb.pgdbCnx`` objects also implement the context manager protocol, +i.e. you can use them in a ``with`` statement. + +close -- close the connection +----------------------------- + +.. method:: pgdbCnx.close() + + Close the connection now (rather than whenever it is deleted) + + :rtype: None + +The connection will be unusable from this point forward; an :exc:`Error` +(or subclass) exception will be raised if any operation is attempted with +the connection. The same applies to all cursor objects trying to use the +connection. Note that closing a connection without committing the changes +first will cause an implicit rollback to be performed. + +commit -- commit the connection +------------------------------- + +.. method:: pgdbCnx.commit() + + Commit any pending transaction to the database + + :rtype: None + +Note that connections always use a transaction, there is no auto-commit. + +rollback -- roll back the connection +------------------------------------ + +.. method:: pgdbCnx.rollback() + + Roll back any pending transaction to the database + + :rtype: None + +This method causes the database to roll back to the start of any pending +transaction. Closing a connection without committing the changes first will +cause an implicit rollback to be performed. + +cursor -- return a new cursor object +------------------------------------ + +.. method:: pgdbCnx.cursor() + + Return a new cursor object using the connection + + :returns: a connection object + :rtype: :class:`pgdbCursor` + +This method returns a new :class:`pgdbCursor` object that can be used to +operate on the database in the way described in the next section. diff --git a/docs/contents/pgdb/cursor.rst b/docs/contents/pgdb/cursor.rst new file mode 100644 index 00000000..49e0e29d --- /dev/null +++ b/docs/contents/pgdb/cursor.rst @@ -0,0 +1,208 @@ +pgdbCursor -- The cursor object +=============================== + +.. class:: pgdbCursor + +These objects represent a database cursor, which is used to manage the context +of a fetch operation. Cursors created from the same connection are not +isolated, i.e., any changes done to the database by a cursor are immediately +visible by the other cursors. Cursors created from different connections can +or can not be isolated, depending on the level of transaction isolation. +The default PostgreSQL transaction isolation level is "read committed". + +Cursor objects respond to the following methods and attributes. + +Note that ``pgdbCursor`` objects also implement both the iterator and the +context manager protocol, i.e. you can iterate over them and you can use them +in a ``with`` statement. + +description -- details regarding the result columns +--------------------------------------------------- + +.. attribute:: pgdbCursor.description + + This read-only attribute is a sequence of 7-item tuples. + + Each of these tuples contains information describing one result column: + + - *name* + - *type_code* + - *display_size* + - *internal_size* + - *precision* + - *scale* + - *null_ok* + + Note that *display_size*, *precision*, *scale* and *null_ok* + are not implemented. + + This attribute will be ``None`` for operations that do not return rows + or if the cursor has not had an operation invoked via the + :meth:`pgdbCursor.execute` or :meth:`pgdbCursor.executemany` method yet. + +rowcount -- number of rows of the result +---------------------------------------- + +.. attribute:: pgdbCursor.rowcount + + This read-only attribute specifies the number of rows that the last + :meth:`pgdbCursor.execute` or :meth:`pgdbCursor.executemany` call produced + (for DQL statements like SELECT) or affected (for DML statements like + UPDATE or INSERT). The attribute is -1 in case no such method call has + been performed on the cursor or the rowcount of the last operation + cannot be determined by the interface. + +close -- close the cursor +------------------------- + +.. method:: pgdbCursor.close() + + Close the cursor now (rather than whenever it is deleted) + + :rtype: None + +The cursor will be unusable from this point forward; an :exc:`Error` +(or subclass) exception will be raised if any operation is attempted +with the cursor. + +execute -- execute a database operation +--------------------------------------- + +.. method:: pgdbCursor.execute(operation, [parameters]) + + Prepare and execute a database operation (query or command) + + :param str operation: the database operation + :param parameters: a sequence or mapping of parameters + :returns: the cursor, so you can chain commands + +Parameters may be provided as sequence or mapping and will be bound to +variables in the operation. Variables are specified using Python extended +format codes, e.g. ``" ... WHERE name=%(name)s"``. + +A reference to the operation will be retained by the cursor. If the same +operation object is passed in again, then the cursor can optimize its behavior. +This is most effective for algorithms where the same operation is used, +but different parameters are bound to it (many times). + +The parameters may also be specified as list of tuples to e.g. insert multiple +rows in a single operation, but this kind of usage is deprecated: +:meth:`pgdbCursor.executemany` should be used instead. + +executemany -- execute many similar database operations +------------------------------------------------------- + +.. method:: pgdbCursor.executemany(operation, [seq_of_parameters]) + + Prepare and execute many similar database operations (queries or commands) + + :param str operation: the database operation + :param seq_of_parameters: a sequence or mapping of parameter tuples or mappings + :returns: the cursor, so you can chain commands + +Prepare a database operation (query or command) and then execute it against +all parameter tuples or mappings found in the sequence *seq_of_parameters*. + +Parameters are bounded to the query using Python extended format codes, +e.g. ``" ... WHERE name=%(name)s"``. + +fetchone -- fetch next row of the query result +---------------------------------------------- + +.. method:: pgdbCursor.fetchone() + + Fetch the next row of a query result set + + :returns: the next row of the query result set + :rtype: list or None + +Fetch the next row of a query result set, returning a single list, +or ``None`` when no more data is available. + +An :exc:`Error` (or subclass) exception is raised if the previous call to +:meth:`pgdbCursor.execute` or :meth:`pgdbCursor.executemany` did not produce +any result set or no call was issued yet. + +fetchmany -- fetch next set of rows of the query result +------------------------------------------------------- + +.. method:: pgdbCursor.fetchmany([size=None], [keep=False]) + + Fetch the next set of rows of a query result + + :param size: the number of rows to be fetched + :type size: int or None + :param keep: if set to true, will keep the passed arraysize + :tpye keep: bool + :returns: the next set of rows of the query result + :rtype: list of lists + +Fetch the next set of rows of a query result, returning a list of lists. +An empty sequence is returned when no more rows are available. + +The number of rows to fetch per call is specified by the *size* parameter. +If it is not given, the cursor's :attr:`arraysize` determines the number of +rows to be fetched. If you set the *keep* parameter to True, this is kept as +new :attr:`arraysize`. + +The method tries to fetch as many rows as indicated by the *size* parameter. +If this is not possible due to the specified number of rows not being +available, fewer rows may be returned. + +An :exc:`Error` (or subclass) exception is raised if the previous call to +:meth:`pgdbCursor.execute` or :meth:`pgdbCursor.executemany` did not produce +any result set or no call was issued yet. + +Note there are performance considerations involved with the *size* parameter. +For optimal performance, it is usually best to use the :attr:`arraysize` +attribute. If the *size* parameter is used, then it is best for it to retain +the same value from one :meth:`pgdbCursor.fetchmany` call to the next. + +fetchall -- fetch all rows of the query result +---------------------------------------------- + +.. method:: pgdbCursor.fetchall() + + Fetch all (remaining) rows of a query result + + :returns: the set of all rows of the query result + :rtype: list of list + +Fetch all (remaining) rows of a query result, returning them as list of lists. +Note that the cursor's :attr:`arraysize` attribute can affect the performance +of this operation. + +row_factory -- process a row of the query result +------------------------------------------------ + +.. method:: pgdbCursor.row_factory(row) + + Process rows before they are returned + + :param list row: the currently processed row of the result set + :returns: the transformed row that the cursor methods shall return + +.. note:: + + This method is not part of the DB-API 2 standard. + +You can overwrite this method with a custom row factory, e.g. +if you want to return rows as dicts instead of lists:: + + class DictCursor(pgdb.pgdbCursor): + + def row_factory(self, row): + return dict((d[0], v) for d, v in zip(self.description, row)) + + cur = DictCursor(con) + +arraysize - the number of rows to fetch at a time +------------------------------------------------- + +.. attribute:: pgdbCursor.arraysize + + The number of rows to fetch at a time + +This read/write attribute specifies the number of rows to fetch at a time with +:meth:`pgdbCursor.fetchmany`. It defaults to 1 meaning to fetch a single row +at a time. diff --git a/docs/contents/pgdb/index.rst b/docs/contents/pgdb/index.rst new file mode 100644 index 00000000..5d3f2a90 --- /dev/null +++ b/docs/contents/pgdb/index.rst @@ -0,0 +1,15 @@ +---------------------------------------------- +:mod:`pgdb` --- The DB-API Compliant Interface +---------------------------------------------- + +.. module:: pgdb + +Contents +======== + +.. toctree:: + introduction + module + connection + cursor + types diff --git a/docs/contents/pgdb/introduction.rst b/docs/contents/pgdb/introduction.rst new file mode 100644 index 00000000..7c8bd42d --- /dev/null +++ b/docs/contents/pgdb/introduction.rst @@ -0,0 +1,19 @@ +Introduction +============ + +You may either choose to use the "classic" PyGreSQL interface provided by +the :mod:`pg` module or else the newer DB-API 2.0 compliant interface +provided by the :mod:`pgdb` module. + +The following part of the documentation covers only the newer :mod:`pgdb` API. + +**DB-API 2.0** (Python Database API Specification v2.0) +is a specification for connecting to databases (not only PostGreSQL) +from Python that has been developed by the Python DB-SIG in 1999. +The authoritative programming information for the DB-API is :pep:`0249`. + +.. seealso:: + + A useful tutorial-like `introduction to the DB-API + `_ + has been written by Andrew M. Kuchling for the LINUX Journal in 1998. diff --git a/docs/contents/pgdb/module.rst b/docs/contents/pgdb/module.rst new file mode 100644 index 00000000..415d56b5 --- /dev/null +++ b/docs/contents/pgdb/module.rst @@ -0,0 +1,109 @@ +Module functions and constants +============================== + +The :mod:`pgdb` module defines a :func:`connect` function that allows to +connect to a database, some global constants describing the capabilities +of the module as well as several exception classes. + +connect -- Open a PostgreSQL connection +--------------------------------------- + +.. function:: pgdb.connect([dsn], [user], [password], [host], [database]) + + Return a new connection to the database + + :param str dsn: data source name as string + :param str user: the database user name + :param str password: the database password + :param str host: the hostname of the database + :param database: the name of the database + :returns: a connection object + :rtype: :class:`pgdbCnx` + :raises pgdb.OperationalError: error connecting to the database + +This function takes parameters specifying how to connect to a PostgreSQL +database and returns a :class:`pgdbCnx` object using these parameters. +If specified, the *dsn* parameter must be a string with the format +``'host:base:user:passwd:opt:tty'``. All of the parts specified in the *dsn* +are optional. You can also specify the parameters individually using keyword +arguments, which always take precedence. The *host* can also contain a port +if specified in the format ``'host:port'``. In the *opt* part of the *dsn* +you can pass command-line options to the server, the *tty* part is used to +send server debug output. + +Example:: + + con = connect(dsn='myhost:mydb', user='guido', password='234$') + + +Module constants +---------------- + +.. data:: apilevel + + The string constant ``'2.0'``, stating that the module is DB-API 2.0 level + compliant. + +.. data:: threadsafety + + The integer constant 1, stating that the module itself is thread-safe, + but the connections are not thread-safe, and therefore must be protected + with a lock if you want to use them from different threads. + +.. data:: paramstyle + + The string constant ``pyformat``, stating that parameters should be passed + using Python extended format codes, e.g. ``" ... WHERE name=%(name)s"``. + +Errors raised by this module +---------------------------- + +The errors that can be raised by the :mod:`pgdb` module are the following: + +.. exception:: Warning + + Exception raised for important warnings like data truncations while + inserting. + +.. exception:: Error + + Exception that is the base class of all other error exceptions. You can + use this to catch all errors with one single except statement. + Warnings are not considered errors and thus do not use this class as base. + +.. exception:: InterfaceError + + Exception raised for errors that are related to the database interface + rather than the database itself. + +.. exception:: DatabaseError + + Exception raised for errors that are related to the database. + +.. exception:: DataError + + Exception raised for errors that are due to problems with the processed + data like division by zero or numeric value out of range. + +.. exception:: OperationalError + + Exception raised for errors that are related to the database's operation + and not necessarily under the control of the programmer, e.g. an unexpected + disconnect occurs, the data source name is not found, a transaction could + not be processed, or a memory allocation error occurred during processing. + +.. exception:: IntegrityError + + Exception raised when the relational integrity of the database is affected, + e.g. a foreign key check fails. + +.. exception:: ProgrammingError + + Exception raised for programming errors, e.g. table not found or already + exists, syntax error in the SQL statement or wrong number of parameters + specified. + +.. exception:: NotSupportedError + + Exception raised in case a method or database API was used which is not + supported by the database. diff --git a/docs/contents/pgdb/types.rst b/docs/contents/pgdb/types.rst new file mode 100644 index 00000000..87ca5dc8 --- /dev/null +++ b/docs/contents/pgdb/types.rst @@ -0,0 +1,108 @@ +pgdbType -- Type objects and constructors +========================================= + +.. class:: pgdbType + +The :attr:`pgdbCursor.description` attribute returns information about each +of the result columns of a query. The *type_code* must compare equal to one +of the :class:`pgdbType` objects defined below. Type objects can be equal to +more than one type code (e.g. :class:`DATETIME` is equal to the type codes +for date, time and timestamp columns). + +The :mod:`pgdb` module exports the following constructors and singletons: + +.. function:: Date(year, month, day) + + Construct an object holding a date value + +.. function:: Time(hour, minute=0, second=0, microsecond=0) + + Construct an object holding a time value + +.. function:: Timestamp(year, month, day, hour=0, minute=0, second=0, microsecond=0) + + Construct an object holding a time stamp value + +.. function:: DateFromTicks(ticks) + + Construct an object holding a date value from the given *ticks* value + +.. function:: TimeFromTicks(ticks) + + Construct an object holding a time value from the given *ticks* value + +.. function:: TimestampFromTicks(ticks) + + Construct an object holding a time stamp from the given *ticks* value + +.. function:: Binary(bytes) + + Construct an object capable of holding a (long) binary string value + +.. class:: STRING + + Used to describe columns that are string-based (e.g. ``char``, ``varchar``, ``text``) + +.. class:: BINARY type + + Used to describe (long) binary columns (``bytea``) + +.. class:: NUMBER + + Used to describe numeric columns (e.g. ``int``, ``float``, ``numeric``, ``money``) + +.. class:: DATETIME + + Used to describe date/time columns (e.g. ``date``, ``time``, ``timestamp``, ``interval``) + +.. class:: ROWID + + Used to describe the ``oid`` column of PostgreSQL database tables + +.. note: + + The following more specific types are not part of the DB-API 2 standard. + +.. class:: BOOL + + Used to describe ``boolean`` columns + +.. class:: SMALLINT + + Used to describe ``smallint`` columns + +.. class:: INTEGER + + Used to describe ``integer`` columns + +.. class:: LONG + + Used to describe ``bigint`` columns + +.. class:: FLOAT + + Used to describe ``float`` columns + +.. class:: NUMERIC + + Used to describe ``numeric`` columns + +.. class:: MONEY + + Used to describe ``money`` columns + +.. class:: DATE + + Used to describe ``date`` columns + +.. class:: TIME + + Used to describe ``time`` columns + +.. class:: TIMESTAMP + + Used to describe ``timestamp`` columns + +.. class:: INTERVAL + + Used to describe date and time ``interval`` columns diff --git a/docs/copyright.rst b/docs/copyright.rst index 4feb0192..04baf145 100644 --- a/docs/copyright.rst +++ b/docs/copyright.rst @@ -29,5 +29,3 @@ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE AUTHORS HAVE NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. - - diff --git a/docs/download/download.rst b/docs/download/download.rst new file mode 100644 index 00000000..fffb6433 --- /dev/null +++ b/docs/download/download.rst @@ -0,0 +1,20 @@ +Download PyGreSQL here: +----------------------- + +You can find PyGreSQL on the **Python Package Index** at + * http://pypi.python.org/pypi/PyGreSQL/ + +The **released version of the source code** is available at + * http://pygresql.org/files/PyGreSQL.tgz +You can also check the latest **pre-release version** at + * http://pygresql.org/files/PyGreSQL-beta.tgz +A **Linux RPM** can be picked up from + * http://pygresql.org/files/pygresql.i386.rpm +A **NetBSD package** is available in their pkgsrc collection + * ftp://ftp.netbsd.org/pub/NetBSD/packages/pkgsrc/databases/py-postgresql/README.html +A **FreeBSD package** is available in their ports collection + * http://www.freebsd.org/cgi/cvsweb.cgi/ports/databases/py-PyGreSQL/ +A **Win32 package** for various Python versions is available at + * http://pygresql.org/files/PyGreSQL-4.2.win32-py2.5.exe + * http://pygresql.org/files/PyGreSQL-4.2.win32-py2.6.exe + * http://pygresql.org/files/PyGreSQL-4.2.win32-py2.7.exe diff --git a/docs/download/files.rst b/docs/download/files.rst new file mode 100644 index 00000000..2723ed46 --- /dev/null +++ b/docs/download/files.rst @@ -0,0 +1,33 @@ +Distribution files +------------------ + +========== = + +pgmodule.c the C Python module (_pg) +pgfs.h PostgreSQL definitions for large objects +pgtypes.h PostgreSQL type definitions +pg.py the "classic" PyGreSQL module +pgdb.py a DB-SIG DB-API 2.0 compliant API wrapper for PygreSQL + +setup.py the Python setup script + + To install PyGreSQL, you can run "python setup.py install". + +setup.cfg the Python setup configuration + +docs/ documentation directory + + The documentation has been created with Sphinx. + All text files are in ReST format; a HTML version of + the documentation can be created with the command + "make html" or "gmake html". + +tests/ a suite of unit tests for PyGreSQL + +tutorial/ demos directory + + The samples contained in this directory have been taken + from the PostgreSQL manual and were used for module testing. + They demonstrate some PostgreSQL features. + +========== = diff --git a/docs/download/index.rst b/docs/download/index.rst new file mode 100644 index 00000000..c4735826 --- /dev/null +++ b/docs/download/index.rst @@ -0,0 +1,24 @@ +Download information +==================== + +.. include:: download.rst + +News, Changes and Future Development +------------------------------------ + +See the :doc:`../announce` for current news. + +For a list of all changes in the current version |version| +and in past versions, have a look at the :doc:`../contents/changelog`. + +The section on :doc:`../community/index` lists ideas for +future developments and ways to participate. + +Installation +------------ + +Please read the chapter on :doc:`../contents/install` in our documentation. + +.. include:: files.rst + +.. include:: ../community/homes.rst \ No newline at end of file diff --git a/docs/future.rst b/docs/future.rst deleted file mode 100644 index 1b293389..00000000 --- a/docs/future.rst +++ /dev/null @@ -1,48 +0,0 @@ -PyGreSQL future directions -========================== - -This list has been closed since tasks are now managed with the PyGreSQL -tracker that can be found at http://trac.vex.net:8000/pgtracker. -(ticket numbers have been added below): - -To Do ------ - -- Add docs for the pgdb module (everything specific to PyGreSQL) (#5). -- The fetch method should use real cursors (#7). -- The C module needs to be cleaned up and redundant code merged, - and should get its own unit test module (#8). -- The test suite for the classic module should also check that quoted - mixed-case identifiers can be used everywhere - currently they can't. - Improve pg.py accordingly, adding quotes etc. as needed (#10). -- What shall we do with the "tutorial" directory - it's rather a tutorial - for Postgres/SQL than for PyGreSQL, it's using only the query method from - the classic pg module and no other PyGreSQL functionality, it's rather - a demo than a tutorial (#11)? - -Proposed Patches ----------------- - -- Support for asynchronous command processing (#49). - -Wish List ---------- - -- Make use of PQexecParams() and PQprepare(). This could speed up - executemany() and allow retrieving binary data directly by setting - the resultFormat parameter to one (#16). -- Enhance cursor.description attribute, delivering more information - available with PQfmod() or PQftable() for instance (#17). -- Support optional "errorhandler" extension (#18). -- Support optional cursor and connection attribute "messages" (#19). -- Users should be able to register their own types with _pg (#21). -- pg has now got a new method namedresult() that returns named tuples. - pgdb should also support named tuples, maybe via a row_factory attribute - on the connection, similar to sqlite3 (see #22). -- New methods in the classic module, similar to getresult() and - dictresult(), but returning dictionaries of rows instead of lists - of rows (with primary key or oids as keys) (#23). -- Make PyGreSQL thread-safe on the connection level (#24). -- The API documentation could be created with Epydoc or Sphinx (#4). -- Write a tutorial for beginners and advanced use (#11). -- More and better documented examples (#4, #5, #11). diff --git a/docs/index.rst b/docs/index.rst index d94f1044..69a98c5e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,29 +3,16 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -PyGreSQL - PostgreSQL module for Python -======================================= - -Contents: +Welcome to PyGreSQL +=================== .. toctree:: - :maxdepth: 1 - - introduction - copyright - changelog - install - interface - pg - pgdb - source - mailinglist - future - examples + :hidden: -Indices and tables -================== + copyright + announce + download/index + contents/index + community/index -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` +.. include:: about.rst diff --git a/docs/interface.rst b/docs/interface.rst deleted file mode 100644 index f72a9259..00000000 --- a/docs/interface.rst +++ /dev/null @@ -1,17 +0,0 @@ -Programming Interface -===================== - -You may either choose to use the `"classic" PyGreSQL interface `_ -provided by the `pg` module or else the -`DB-API 2.0 compliant interface `_ provided by the `pgdb` module. - -`DB-API 2.0 `_ -(Python Database API Specification v2.0) -is a specification for connecting to databases (not only PostGreSQL) -from Python that has been developed by the Python DB-SIG in 1999. - -The authoritative programming information for the DB-API is availabe at - http://www.python.org/dev/peps/pep-0249/ - -A tutorial-like introduction to the DB-API can be found at - http://www2.linuxjournal.com/lj-issues/issue49/2605.html diff --git a/docs/mailinglist.rst b/docs/mailinglist.rst deleted file mode 100644 index 61cf875b..00000000 --- a/docs/mailinglist.rst +++ /dev/null @@ -1,8 +0,0 @@ -Mailing list -============ - -You can join -`the mailing list `_ -to discuss future development of the PyGreSQL interface. -This is usually a low volume list except when there are new features -being added. diff --git a/docs/pg.rst b/docs/pg.rst deleted file mode 100644 index a3e77087..00000000 --- a/docs/pg.rst +++ /dev/null @@ -1,1484 +0,0 @@ --------------------------------------------- -:mod:`pg` --- The Classic PyGreSQL Interface --------------------------------------------- - -.. module:: pg - -.. contents:: Contents - - -Introduction -============ - -You may either choose to use the "classic" PyGreSQL interface -provided by the :mod:`pg` module or else the -DB-API 2.0 compliant interface provided by the :mod:`pgdb` module. - -The following part of the documentation covers only the older :mod:`pg` API. - -The :mod:`pg` module handles three types of objects, - -- the :class:`pgobject`, which handles the connection - and all the requests to the database, -- the :class:`pglarge` object, which handles - all the accesses to PostgreSQL large objects, -- the :class:`pgqueryobject` that handles query results - -and it provides a convenient wrapper class :class:`DB` -for the :class:`pgobject`. - -If you want to see a simple example of the use of some of these functions, -see the :doc:`examples` page. - - -Module functions and constants -============================== - -The :mod:`pg` module defines a few functions that allow to connect -to a database and to define "default variables" that override -the environment variables used by PostgreSQL. - -These "default variables" were designed to allow you to handle general -connection parameters without heavy code in your programs. You can prompt the -user for a value, put it in the default variable, and forget it, without -having to modify your environment. The support for default variables can be -disabled by setting the ``-DNO_DEF_VAR`` option in the Python setup file. -Methods relative to this are specified by the tag [DV]. - -All variables are set to ``None`` at module initialization, specifying that -standard environment variables should be used. - -connect -- Open a PostgreSQL connection ---------------------------------------- - -.. function:: connect([dbname], [host], [port], [opt], [tty], [user], [passwd]) - - Open a :mod:`pg` connection - - :param dbname: name of connected database (*None* = :data:`defbase`) - :type str: str or None - :param host: name of the server host (*None* = :data:`defhost`) - :type host: str or None - :param port: port used by the database server (-1 = :data:`defport`) - :type port: int - :param opt: connection options (*None* = :data:`defopt`) - :type opt: str or None - :param tty: debug terminal (*None* = :data:`deftty`) - :type tty: str or None - :param user: PostgreSQL user (*None* = :data:`defuser`) - :type user: str or None - :param passwd: password for user (*None* = :data:`defpasswd`) - :type passwd: str or None - :returns: If successful, the :class:`pgobject` handling the connection - :rtype: :class:`pgobject` - :raises TypeError: bad argument type, or too many arguments - :raises SyntaxError: duplicate argument definition - :raises pg.InternalError: some error occurred during pg connection definition - :raises Exception: (all exceptions relative to object allocation) - -This function opens a connection to a specified database on a given -PostgreSQL server. You can use keywords here, as described in the -Python tutorial. The names of the keywords are the name of the -parameters given in the syntax line. For a precise description -of the parameters, please refer to the PostgreSQL user manual. - -Example:: - - import pg - - con1 = pg.connect('testdb', 'myhost', 5432, None, None, 'bob', None) - con2 = pg.connect(dbname='testdb', host='localhost', user='bob') - -get/set_defhost -- default server host [DV] -------------------------------------------- - -.. function:: get_defhost(host) - - Get the default host - - :returns: the current default host specification - :rtype: str or None - :raises TypeError: too many arguments - -This method returns the current default host specification, -or ``None`` if the environment variables should be used. -Environment variables won't be looked up. - -.. function:: set_defhost(host) - - Set the default host - - :param host: the new default host specification - :type host: str or None - :returns: the previous default host specification - :rtype: str or None - :raises TypeError: bad argument type, or too many arguments - -This methods sets the default host value for new connections. -If ``None`` is supplied as parameter, environment variables will -be used in future connections. It returns the previous setting -for default host. - -get/set_defport -- default server port [DV] -------------------------------------------- - -.. function:: get_defport() - - Get the default port - - :returns: the current default port specification - :rtype: int - :raises TypeError: too many arguments - -This method returns the current default port specification, -or ``None`` if the environment variables should be used. -Environment variables won't be looked up. - -.. function:: set_defport(port) - - Set the default port - - :param port: the new default port - :type port: int - :returns: previous default port specification - :rtype: int or None - -This methods sets the default port value for new connections. If -1 is -supplied as parameter, environment variables will be used in future -connections. It returns the previous setting for default port. - -get/set_defopt -- default connection options [DV] --------------------------------------------------- - -.. function:: get_defopt() - - Get the default connection options - - :returns: the current default options specification - :rtype: str or None - :raises TypeError: too many arguments - -This method returns the current default connection options specification, -or ``None`` if the environment variables should be used. Environment variables -won't be looked up. - -.. function:: set_defopt(options) - - Set the default connection options - - :param options: the new default connection options - :type options: str or None - :returns: previous default options specification - :rtype: str or None - :raises TypeError: bad argument type, or too many arguments - -This methods sets the default connection options value for new connections. -If ``None`` is supplied as parameter, environment variables will be used in -future connections. It returns the previous setting for default options. - -get/set_deftty -- default debug tty [DV] ----------------------------------------- - -.. function:: get_deftty() - - Get the default debug terminal - - :returns: the current default debug terminal specification - :rtype: str or None - :raises TypeError: too many arguments - -This method returns the current default debug terminal specification, or -``None`` if the environment variables should be used. Environment variables -won't be looked up. Note that this is ignored in newer PostgreSQL versions. - -.. function:: set_deftty(terminal) - - Set the default debug terminal - - :param terminal: the new default debug terminal - :type terminal: str or None - :returns: the previous default debug terminal specification - :rtype: str or None - :raises TypeError: bad argument type, or too many arguments - -This methods sets the default debug terminal value for new connections. -If ``None`` is supplied as parameter, environment variables will be used -in future connections. It returns the previous setting for default terminal. -Note that this is ignored in newer PostgreSQL versions. - -get/set_defbase -- default database name [DV] ---------------------------------------------- - -.. function:: get_defbase() - - Get the default database name - - :returns: the current default database name specification - :rtype: str or None - :raises TypeError: too many arguments - -This method returns the current default database name specification, or -``None`` if the environment variables should be used. Environment variables -won't be looked up. - -.. function:: set_defbase(base) - - Set the default database name - - :param base: the new default base name - :type base: str or None - :returns: the previous default database name specification - :rtype: str or None - :raises TypeError: bad argument type, or too many arguments - -This method sets the default database name value for new connections. If -``None`` is supplied as parameter, environment variables will be used in -future connections. It returns the previous setting for default host. - -get/set_defuser -- default database user [DV] ---------------------------------------------- - -.. function:: get_defuser() - - Get the default database user - - :returns: the current default database user specification - :rtype: str or None - :raises TypeError: too many arguments - -This method returns the current default database user specification, or -``None`` if the environment variables should be used. Environment variables -won't be looked up. - -.. function:: set_defuser(user) - - Set the default database user - - :param user: the new default database user - :type base: str or None - :returns: the previous default database user specification - :rtype: str or None - :raises TypeError: bad argument type, or too many arguments - -This method sets the default database user name for new connections. If -``None`` is supplied as parameter, environment variables will be used in -future connections. It returns the previous setting for default host. - -get/set_defpasswd -- default database password [DV] ---------------------------------------------------- - -.. function:: get_defpasswd() - - Get the default database password - - :returns: the current default database password specification - :rtype: str or None - :raises TypeError: too many arguments - -This method returns the current default database password specification, or -``None`` if the environment variables should be used. Environment variables -won't be looked up. - -.. function:: set_defpasswd(passwd) - - Set the default database password - - :param passwd: the new default database password - :type base: str or None - :returns: the previous default database password specification - :rtype: str or None - :raises TypeError: bad argument type, or too many arguments - -This method sets the default database password for new connections. If -``None`` is supplied as parameter, environment variables will be used in -future connections. It returns the previous setting for default host. - -escape_string -- escape a string for use within SQL ---------------------------------------------------- - -.. function:: escape_string(string) - - Escape a string for use within SQL - - :param str string: the string that is to be escaped - :returns: the escaped string - :rtype: str - :raises TypeError: bad argument type, or too many arguments - -This function escapes a string for use within an SQL command. -This is useful when inserting data values as literal constants -in SQL commands. Certain characters (such as quotes and backslashes) -must be escaped to prevent them from being interpreted specially -by the SQL parser. :func:`escape_string` performs this operation. -Note that there is also a :class:`pgobject` method with the same name -which takes connection properties into account. - -.. note:: - - It is especially important to do proper escaping when - handling strings that were received from an untrustworthy source. - Otherwise there is a security risk: you are vulnerable to "SQL injection" - attacks wherein unwanted SQL commands are fed to your database. - -Example:: - - name = raw_input("Name? ") - phone = con.query("select phone from employees where name='%s'" - % escape_string(name)).getresult() - -escape_bytea -- escape binary data for use within SQL ------------------------------------------------------ - -.. function:: escape_bytea(datastring) - - escape binary data for use within SQL as type ``bytea`` - - :param str datastring: string containing the binary data that is to be escaped - :returns: the escaped string - :rtype: str - :raises TypeError: bad argument type, or too many arguments - -Escapes binary data for use within an SQL command with the type ``bytea``. -As with :func:`escape_string`, this is only used when inserting data directly -into an SQL command string. -Note that there is also a :class:`pgobject` method with the same name -which takes connection properties into account. - -Example:: - - picture = open('garfield.gif', 'rb').read() - con.query("update pictures set img='%s' where name='Garfield'" - % escape_bytea(picture)) - -unescape_bytea -- unescape data that has been retrieved as text ---------------------------------------------------------------- - -.. function:: unescape_bytea(string) - - Unescape ``bytea`` data that has been retrieved as text - - :param str datastring: the ``bytea`` data string that has been retrieved as text - :returns: byte string containing the binary data - :rtype: str - :raises TypeError: bad argument type, or too many arguments - -Converts an escaped string representation of binary data into binary -data -- the reverse of :func:`escape_bytea`. This is needed when retrieving -``bytea`` data with one of the :meth:`pgqueryobject.getresult`, -:meth:`pgqueryobject.dictresult` or :meth:`pgqueryobject.namedresult` methods. - -Example:: - - picture = unescape_bytea(con.query( - "select img from pictures where name='Garfield'").getresult[0][0]) - open('garfield.gif', 'wb').write(picture) - -get/set_decimal -- decimal type to be used for numeric values -------------------------------------------------------------- - -.. function:: get_decimal() - - Get the decimal type to be used for numeric values - - :returns: the Python class used for PostgreSQL numeric values - :rtype: class - -This function returns the Python class that is used by PyGreSQL to hold -PostgreSQL numeric values. The default class is :class:`decimal.Decimal` -if available, otherwise the :class:`float` type is used. - -.. function:: set_decimal(cls) - - Set a decimal type to be used for numeric values - - :param class cls: the Python class to be used for PostgreSQL numeric values - -This function can be used to specify the Python class that shall -be used by PyGreSQL to hold PostgreSQL numeric values. -The default class is :class:`decimal.Decimal` if available, -otherwise the :class:`float` type is used. - -get/set_decimal_point -- decimal mark used for monetary values --------------------------------------------------------------- - -.. function:: get_decimal_point() - - Get the decimal mark used for monetary values - - :returns: string with one character representing the decimal mark - :rtype: str - -This function returns the decimal mark used by PyGreSQL to interpret -PostgreSQL monetary values when converting them to decimal numbers. -The default setting is ``'.'`` as a decimal point. This setting is not -adapted automatically to the locale used by PostGreSQL, but you can -use ``set_decimal()`` to set a different decimal mark manually. A return -value of ``None`` means monetary values are not interpreted as decimal -numbers, but returned as strings including the formatting and currency. - -.. function:: set_decimal_point(string) - - Specify which decimal mark is used for interpreting monetary values - - :param str string: string with one character representing the decimal mark - -This function can be used to specify the decimal mark used by PyGreSQL -to interpret PostgreSQL monetary values. The default value is '.' as -a decimal point. This value is not adapted automatically to the locale -used by PostGreSQL, so if you are dealing with a database set to a -locale that uses a ``','`` instead of ``'.'`` as the decimal point, -then you need to call ``set_decimal(',')`` to have PyGreSQL interpret -monetary values correctly. If you don't want money values to be converted -to decimal numbers, then you can call ``set_decimal(None)``, which will -cause PyGreSQL to return monetary values as strings including their -formatting and currency. - -get/set_bool -- whether boolean values are returned as bool objects -------------------------------------------------------------------- - -.. function:: get_bool() - - Check whether boolean values are returned as bool objects - - :returns: whether or not bool objects will be returned - :rtype: bool - -This function checks whether PyGreSQL returns PostgreSQL boolean -values converted to Python bool objects, or as ``'f'`` and ``'t'`` -strings which are the values used internally by PostgreSQL. By default, -conversion to bool objects is not activated, but you can enable -this with the ``set_bool()`` method. - -.. function:: set_bool(on) - - Set whether boolean values are returned as bool objects - - :param on: whether or not bool objects shall be returned - -This function can be used to specify whether PyGreSQL shall return -PostgreSQL boolean values converted to Python bool objects, or as -``'f'`` and ``'t'`` strings which are the values used internally by PostgreSQL. -By default, conversion to bool objects is not activated, but you can -enable this by calling ``set_bool(True)``. - -get/set_namedresult -- conversion to named tuples -------------------------------------------------- - -.. function:: get_namedresult() - - Get the function that converts to named tuples - -This function returns the function used by PyGreSQL to construct the -result of the :meth:`pgqueryobject.namedresult` method. - -.. function:: set_namedresult(func) - - Set a function that will convert to named tuples - - :param func: the function to be used to convert results to named tuples - -You can use this if you want to create different kinds of named tuples -returned by the :meth:`pgqueryobject.namedresult` method. - - -Module constants ----------------- -Some constants are defined in the module dictionary. -They are intended to be used as parameters for methods calls. -You should refer to the libpq description in the PostgreSQL user manual -for more information about them. These constants are: - -.. data:: version, __version__ - - constants that give the current version - -.. data:: INV_READ, INV_WRITE - - large objects access modes, - used by :meth:`pgobject.locreate` and :meth:`pglarge.open` - -.. data:: SEEK_SET, SEEK_CUR, SEEK_END: - - positional flags, used by :meth:`pglarge.seek` - - -pgobject -- The connection object -================================= - -.. class:: pgobject - -This object handles a connection to a PostgreSQL database. It embeds and -hides all the parameters that define this connection, thus just leaving really -significant parameters in function calls. - -.. note:: - - Some methods give direct access to the connection socket. - *Do not use them unless you really know what you are doing.* - If you prefer disabling them, - set the ``-DNO_DIRECT`` option in the Python setup file. - These methods are specified by the tag [DA]. - -.. note:: - - Some other methods give access to large objects - (refer to PostgreSQL user manual for more information about these). - If you want to forbid access to these from the module, - set the ``-DNO_LARGE`` option in the Python setup file. - These methods are specified by the tag [LO]. - -query -- execute a SQL command string -------------------------------------- - -.. method:: pgobject.query(command, [args]) - - Execute a SQL command string - - :param str command: SQL command - :param args: optional positional arguments - :returns: result values - :rtype: :class:`pgqueryobject`, None - :raises TypeError: bad argument type, or too many arguments - :raises TypeError: invalid connection - :raises ValueError: empty SQL query or lost connection - :raises pg.ProgrammingError: error in query - :raises pg.InternalError: error during query processing - -This method simply sends a SQL query to the database. If the query is an -insert statement that inserted exactly one row into a table that has OIDs, the -return value is the OID of the newly inserted row. If the query is an update -or delete statement, or an insert statement that did not insert exactly one -row in a table with OIDs, then the number of rows affected is returned as a -string. If it is a statement that returns rows as a result (usually a select -statement, but maybe also an ``"insert/update ... returning"`` statement), -this method returns a :class:`pgqueryobject` that can be accessed via the -:meth:`pgqueryobject.getresult`, :meth:`pgqueryobject.dictresult` or -:meth:`pgqueryobject.namedresult` methods or simply printed. -Otherwise, it returns ``None``. - -The query may optionally contain positional parameters of the form ``$1``, -``$2``, etc instead of literal data, and the values supplied as a tuple. -The values are substituted by the database in such a way that they don't -need to be escaped, making this an effective way to pass arbitrary or -unknown data without worrying about SQL injection or syntax errors. - -When the database could not process the query, a :exc:`pg.ProgrammingError` or -a :exc:`pg.InternalError` is raised. You can check the ``SQLSTATE`` code of -this error by reading its :attr:`sqlstate` attribute. - -Example:: - - name = raw_input("Name? ") - phone = con.query("select phone from employees where name=$1", - (name,)).getresult() - -reset -- reset the connection ------------------------------ - -.. method:: pgobject.reset() - - Reset the :mod:`pg` connection - - :rtype: None - :raises TypeError: too many (any) arguments - :raises TypeError: invalid connection - -This method resets the current database connection. - -cancel -- abandon processing of current SQL command ---------------------------------------------------- - -.. method:: pgobject.cancel() - - :rtype: None - :raises TypeError: too many (any) arguments - :raises TypeError: invalid connection - -This method requests that the server abandon processing -of the current SQL command. - -close -- close the database connection --------------------------------------- - -.. method:: pgobject.close() - - Close the :mod:`pg` connection - - :rtype: None - :raises TypeError: too many (any) arguments - -This method closes the database connection. The connection will -be closed in any case when the connection is deleted but this -allows you to explicitly close it. It is mainly here to allow -the DB-SIG API wrapper to implement a close function. - -fileno -- returns the socket used to connect to the database ------------------------------------------------------------- - -.. method:: pgobject.fileno() - - Return the socket used to connect to the database - - :returns: the socket id of the database connection - :rtype: int - :raises TypeError: too many (any) arguments - :raises TypeError: invalid connection - -This method returns the underlying socket id used to connect -to the database. This is useful for use in select calls, etc. - -getnotify -- get the last notify from the server ------------------------------------------------- - -.. method:: pgobject.getnotify() - - Get the last notify from the server - - :returns: last notify from server - :rtype: tuple, None - :raises TypeError: too many parameters - :raises TypeError: invalid connection - -This method tries to get a notify from the server (from the SQL statement -NOTIFY). If the server returns no notify, the methods returns None. -Otherwise, it returns a tuple (triplet) *(relname, pid, extra)*, where -*relname* is the name of the notify, *pid* is the process id of the -connection that triggered the notify, and *extra* is a payload string -that has been sent with the notification. Remember to do a listen query -first, otherwise :meth:`pgobject.getnotify` will always return ``None``. - -inserttable -- insert a list into a table ------------------------------------------ - -.. method:: pgobject.inserttable(table, values) - - Insert a Python list into a database table - - :param str table: the table name - :param list values: list of rows values - :rtype: None - :raises TypeError: invalid connection, bad argument type, or too many arguments - :raises MemoryError: insert buffer could not be allocated - :raises ValueError: unsupported values - -This method allows to *quickly* insert large blocks of data in a table: -It inserts the whole values list into the given table. Internally, it -uses the COPY command of the PostgreSQL database. The list is a list -of tuples/lists that define the values for each inserted row. The rows -values may contain string, integer, long or double (real) values. - -.. note:: - - **Be very careful**: - This method doesn't type check the fields according to the table definition; - it just look whether or not it knows how to handle such types. - -get/set_notice_receiver -- custom notice receiver -------------------------------------------------- - -.. method:: pgobject.get_notice_receiver() - - Get the current notice receiver - - :returns: the current notice receiver callable - :rtype: callable, None - :raises TypeError: too many (any) arguments - -This method gets the custom notice receiver callback function that has -been set with :meth:`pgobject.set_notice_receiver`, or ``None`` if no -custom notice receiver has ever been set on the connection. - -.. method:: pgobject.set_notice_receiver(proc) - - Set a custom notice receiver - - :param proc: the custom notice receiver callback function - :rtype: None - :raises TypeError: the specified notice receiver is not callable - -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 :class:`pgnotice` object, which provides the following read-only -attributes: - - .. attribute:: pgnotice.pgcnx - - the connection - - .. attribute:: pgnotice.message - - the full message with a trailing newline - - .. attribute:: pgnotice.severity - - the level of the message, e.g. 'NOTICE' or 'WARNING' - - .. attribute:: pgnotice.primary - - the primary human-readable error message - - .. attribute:: pgnotice.detail - - an optional secondary error message - - .. attribute:: pgnotice.hint - - an optional suggestion what to do about the problem - -putline -- write a line to the server socket [DA] -------------------------------------------------- - -.. method:: pgobject.putline(line) - - Write a line to the server socket - - :param str line: line to be written - :rtype: None - :raises TypeError: invalid connection, bad parameter type, or too many parameters - -This method allows to directly write a string to the server socket. - -getline -- get a line from server socket [DA] ---------------------------------------------- - -.. method:: pgobject.getline() - - Get a line from server socket - - :returns: the line read - :rtype: str - :raises TypeError: invalid connection - :raises TypeError: too many parameters - :raises MemoryError: buffer overflow - -This method allows to directly read a string from the server socket. - -endcopy -- synchronize client and server [DA] ---------------------------------------------- - -.. method:: pgobject.endcopy() - - Synchronize client and server - - :rtype: None - :raises TypeError: invalid connection - :raises TypeError: too many parameters - -The use of direct access methods may desynchronize client and server. -This method ensure that client and server will be synchronized. - -locreate -- create a large object in the database [LO] ------------------------------------------------------- - -.. method:: pgobject.locreate(mode) - - Create a large object in the database - - :param int mode: large object create mode - :returns: object handling the PostGreSQL large object - :rtype: :class:`pglarge` - :raises TypeError: invalid connection, bad parameter type, or too many parameters - :raises pg.OperationalError: creation error - -This method creates a large object in the database. The mode can be defined -by OR-ing the constants defined in the :mod:`pg` module (:const:`INV_READ`, -:const:`INV_WRITE` and :const:`INV_ARCHIVE`). Please refer to PostgreSQL -user manual for a description of the mode values. - -getlo -- build a large object from given oid [LO] -------------------------------------------------- - -.. method:: pgobject.getlo(oid) - - Create a large object in the database - - :param int oid: OID of the existing large object - :returns: object handling the PostGreSQL large object - :rtype: :class:`pglarge` - :raises TypeError: invalid connection, bad parameter type, or too many parameters - :raises ValueError: bad OID value (0 is invalid_oid) - -This method allows to reuse a formerly created large object through the -:class:`pglarge` interface, providing the user have its OID. - -loimport -- import a file to a large object [LO] ------------------------------------------------- - -.. method:: pgobject.loimport(name) - - Import a file to a large object - - :param str name: the name of the file to be imported - :returns: object handling the PostGreSQL large object - :rtype: :class:`pglarge` - :raises TypeError: invalid connection, bad argument type, or too many arguments - :raises pg.OperationalError: error during file import - -This methods allows to create large objects in a very simple way. You just -give the name of a file containing the data to be used. - -Object attributes ------------------ -Every :class:`pgobject` defines a set of read-only attributes that describe -the connection and its status. These attributes are: - -.. attribute:: pgobject.host - - the host name of the server (str) - -.. attribute:: pgobject.port - - the port of the server (int) - -.. attribute:: pgobject.db - - the selected database (str) - -.. attribute:: pgobject.options - - the connection options (str) - -.. attribute:: pgobject.tty - - the connection debug terminal (str) - -.. attribute:: pgobject.user - - user name on the database system (str) - -.. attribute:: pgobject.protocol_version - - the frontend/backend protocol being used (int) - -.. attribute:: pgobject.server_version - - the backend version (int, e.g. 80305 for 8.3.5) - -.. attribute:: pgobject.status - - the status of the connection (int: 1 = OK, 0 = bad) - -.. attribute:: pgobject.error - - the last warning/error message from the server (str) - - -The DB wrapper class -==================== - -.. class:: DB - -The :class:`pgobject` methods are wrapped in the class :class:`DB`. -The preferred way to use this module is as follows:: - - import pg - - db = pg.DB(...) # see below - - for r in db.query( # just for example - """SELECT foo,bar - FROM foo_bar_table - WHERE foo !~ bar""" - ).dictresult(): - - print '%(foo)s %(bar)s' % r - -This class can be subclassed as in this example:: - - import pg - - class DB_ride(pg.DB): - """Ride database wrapper - - This class encapsulates the database functions and the specific - methods for the ride database.""" - - def __init__(self): - """Open a database connection to the rides database""" - pg.DB.__init__(self, dbname='ride') - self.query("SET DATESTYLE TO 'ISO'") - - [Add or override methods here] - -The following describes the methods and variables of this class. - -Initialization --------------- -The :class:`DB` class is initialized with the same arguments as the -:func:`connect` function described above. It also initializes a few -internal variables. The statement ``db = DB()`` will open the local -database with the name of the user just like ``connect()`` does. - -You can also initialize the DB class with an existing :mod:`pg` or :mod:`pgdb` -connection. Pass this connection as a single unnamed parameter, or as a -single parameter named ``db``. This allows you to use all of the methods -of the DB class with a DB-API 2 compliant connection. Note that the -:meth:`pgobject.close` and :meth:`pgobject.reopen` methods are inoperative -in this case. - -pkey -- return the primary key of a table ------------------------------------------ - -.. method:: DB.pkey(table) - - Return the primary key of a table - - :param str table: name of table - :returns: Name of the field which is the primary key of the table - :rtype: str - -This method returns the primary key of a table. For composite primary -keys, the return value will be a frozenset. Note that this raises an -exception if the table does not have a primary key. - -get_databases -- get list of databases in the system ----------------------------------------------------- - -.. method:: DB.get_databases() - - Get the list of databases in the system - - :returns: all databases in the system - :rtype: list - -Although you can do this with a simple select, it is added here for -convenience. - -get_relations -- get list of relations in connected database ------------------------------------------------------------- - -.. method:: DB.get_relations(kinds) - - Get the list of relations in connected database - - :param str kinds: a string or sequence of type letters - :returns: all relations of the given kinds in the database - :rtype: list - -The type letters are ``r`` = ordinary table, ``i`` = index, ``S`` = sequence, -``v`` = view, ``c`` = composite type, ``s`` = special, ``t`` = TOAST table. -If `kinds` is None or an empty string, all relations are returned (this is -also the default). Although you can do this with a simple select, it is -added here for convenience. - -get_tables -- get list of tables in connected database ------------------------------------------------------- - -.. method:: DB.get_tables() - - Get the list of tables in connected database - - :returns: all tables in connected database - :rtype: list - -This is a shortcut for ``get_relations('r')`` that has been added for -convenience. - -get_attnames -- get the attribute names of a table --------------------------------------------------- - -.. method:: DB.get_attnames(table) - - Get the attribute names of a table - - :param str table: name of table - :returns: A dictionary -- the keys are the attribute names, - the values are the type names of the attributes. - -Given the name of a table, digs out the set of attribute names. - -has_table_privilege -- check whether current user has specified table privilege -------------------------------------------------------------------------------- - -.. method:: DB.has_table_privilege(table, privilege) - - Check whether current user has specified table privilege - - :param str table: the name of the table - :param str privilege: privilege to be checked -- default is 'select' - :returns: whether current user has specified table privilege - :rtype: bool - -Returns True if the current user has the specified privilege for the table. - -get -- get a row from a database table or view ----------------------------------------------- - -.. method:: DB.get(table, arg, [keyname]) - - Get a row from a database table or view - - :param str table: name of table or view - :param arg: either a dictionary or the value to be looked up - :param str keyname: name of field to use as key (optional) - :returns: A dictionary - the keys are the attribute names, - the values are the row values. - -This method is the basic mechanism to get a single row. It assumes -that the key specifies a unique row. If *keyname* is not specified, -then the primary key for the table is used. If *arg* is a dictionary -then the value for the key is taken from it and it is modified to -include the new values, replacing existing values where necessary. -For a composite key, *keyname* can also be a sequence of key names. -The OID is also put into the dictionary if the table has one, but in -order to allow the caller to work with multiple tables, it is munged -as ``oid(schema.table)``. - -insert -- insert a row into a database table --------------------------------------------- - -.. method:: DB.insert(table, [d,] [key = val, ...]) - - Insert a row into a database table - - :param str table: name of table - :param dict d: optional dictionary of values - :returns: the inserted values - :rtype: dict - -This method inserts a row into a table. If the optional dictionary is -not supplied then the required values must be included as keyword/value -pairs. If a dictionary is supplied then any keywords provided will be -added to or replace the entry in the dictionary. - -The dictionary is then, if possible, reloaded with the values actually -inserted in order to pick up values modified by rules, triggers, etc. - -Note: The method currently doesn't support insert into views -although PostgreSQL does. - -update -- update a row in a database table ------------------------------------------- - -.. method:: DB.update(table, [d,] [key = val, ...]) - - Update a row in a database table - - :param str table: name of table - :param dict d: optional dictionary of values - :returns: the new row - :rtype: dict - -Similar to insert but updates an existing row. The update is based on the -OID value as munged by get or passed as keyword, or on the primary key of -the table. The dictionary is modified, if possible, to reflect any changes -caused by the update due to triggers, rules, default values, etc. - -Like insert, the dictionary is optional and updates will be performed -on the fields in the keywords. There must be an OID or primary key -either in the dictionary where the OID must be munged, or in the keywords -where it can be simply the string 'oid'. - -query -- execute a SQL command string -------------------------------------- - -.. method:: DB.query(command, [arg1, [arg2, ...]]) - - Execute a SQL command string - - :param str command: SQL command - :param arg*: optional positional arguments - :returns: result values - :rtype: :class:`pgqueryobject`, None - :raises TypeError: bad argument type, or too many arguments - :raises TypeError: invalid connection - :raises ValueError: empty SQL query or lost connection - :raises pg.ProgrammingError: error in query - :raises pg.InternalError: error during query processing - -Similar to the :class:`pgobject` function with the same name, except that -positional arguments can be passed either as a single list or tuple, or as -individual positional arguments. - -Example:: - - name = raw_input("Name? ") - phone = raw_input("Phone? ") - rows = db.query("update employees set phone=$2 where name=$1", - (name, phone)).getresult()[0][0] - # or - rows = db.query("update employees set phone=$2 where name=$1", - name, phone).getresult()[0][0] - -clear -- clear row values in memory ------------------------------------ - -.. method:: DB.clear(table, [a]) - - Clear row values in memory - - :param str table: name of table - :param dict a: optional dictionary of values - :returns: an empty row - :rtype: dict - -This method clears all the attributes to values determined by the types. -Numeric types are set to 0, Booleans are set to ``'f'``, dates are set -to ``'now()'`` and everything else is set to the empty string. -If the array argument is present, it is used as the array and any entries -matching attribute names are cleared with everything else left unchanged. - -If the dictionary is not supplied a new one is created. - -delete -- delete a row from a database table --------------------------------------------- - -.. method:: DB.delete(table, [d,] [key = val, ...]) - - Delete a row from a database table - - :param str table: name of table - :param dict d: optional dictionary of values - :rtype: None - -This method deletes the row from a table. It deletes based on the OID value -as munged by get or passed as keyword, or on the primary key of the table. -The return value is the number of deleted rows (i.e. 0 if the row did not -exist and 1 if the row was deleted). - -escape_string -- escape a string for use within SQL ---------------------------------------------------- - -.. method:: DB.escape_string(string) - - Escape a string for use within SQL - - :param str string: the string that is to be escaped - :returns: the escaped string - :rtype: str - -Similar to the module function with the same name, but the -behavior of this method is adjusted depending on the connection properties -(such as character encoding). - -escape_bytea -- escape binary data for use within SQL ------------------------------------------------------ - -.. method:: DB.escape_bytea(datastring) - - Escape binary data for use within SQL as type ``bytea`` - - :param str datastring: string containing the binary data that is to be escaped - :returns: the escaped string - :rtype: str - -Similar to the module function with the same name, but the -behavior of this method is adjusted depending on the connection properties -(in particular, whether standard-conforming strings are enabled). - -unescape_bytea -- unescape data that has been retrieved as text ---------------------------------------------------------------- - -.. method:: DB.unescape_bytea(string) - - Unescape ``bytea`` data that has been retrieved as text - - :param datastring: the ``bytea`` data string that has been retrieved as text - :returns: byte string containing the binary data - :rtype: str - -See the module function with the same name. - - -pgqueryobject methods -===================== - -.. class:: pgqueryobject - -The :class:`pgqueryobject` returned by :meth:`pgobject.query` and -:meth:`DB.query` provides the following methods for accessing -the results of the query: - -getresult -- get query values as list of tuples ------------------------------------------------ - -.. method:: pgqueryobject.getresult() - - Get query values as list of tuples - - :returns: result values as a list of tuples - :rtype: list - :raises TypeError: too many (any) parameters - :raises MemoryError: internal memory error - -This method returns the list of the values returned by the query. -More information about this result may be accessed using -:meth:`pgqueryobject.listfields`, :meth:`pgqueryobject.fieldname` -and :meth:`pgqueryobject.fieldnum` methods. - -dictresult -- get query values as list of dictionaries ------------------------------------------------------- - -.. method:: pgqueryobject.dictresult() - - Get query values as list of dictionaries - - :returns: result values as a list of dictionaries - :rtype: list - :raises TypeError: too many (any) parameters - :raises MemoryError: internal memory error - -This method returns the list of the values returned by the query -with each tuple returned as a dictionary with the field names -used as the dictionary index. - -namedresult -- get query values as list of named tuples -------------------------------------------------------- - -.. method:: pgqueryobject.namedresult() - - Get query values as list of named tuples - - :returns: result values as a list of named tuples - :rtype: list - :raises TypeError: too many (any) parameters - :raises TypeError: named tuples not supported - :raises MemoryError: internal memory error - -This method returns the list of the values returned by the query -with each row returned as a named tuple with proper field names. - -listfields -- list fields names of previous query result --------------------------------------------------------- - -.. method:: pgqueryobject.listfields() - - List fields names of previous query result - - :returns: field names - :rtype: list - :raises TypeError: too many parameters - -This method returns the list of names of the fields defined for the -query result. The fields are in the same order as the result values. - -fieldname, fieldnum -- field name/number conversion ---------------------------------------------------- - -.. method:: pgqueryobject.fieldname(num) - - Get field name from its number - - :param int num: field number - :returns: field name - :rtype: str - :raises TypeError: invalid connection, bad parameter type, or too many parameters - :raises ValueError: invalid field number - -This method allows to find a field name from its rank number. It can be -useful for displaying a result. The fields are in the same order as the -result values. - -.. method:: pgqueryobject.fieldnum(name) - - Get field number from its name - - :param str name: field name - :returns: field number - :rtype: int - :raises TypeError: invalid connection, bad parameter type, or too many parameters - :raises ValueError: unknown field name - -This method returns a field number from its name. It can be used to -build a function that converts result list strings to their correct -type, using a hardcoded table definition. The number returned is the -field rank in the result values list. - -ntuples -- return number of tuples in query object --------------------------------------------------- - -.. method:: pgqueryobject.ntuples() - - Return number of tuples in query object - - :returns: number of tuples in :class:`pgqueryobject` - :rtype: int - :raises TypeError: Too many arguments. - -This method returns the number of tuples found in a query. - - -pglarge -- Large Objects -======================== - -.. class:: pglarge - -Objects that are instances of the class :class:`pglarge` are used to handle -all the requests concerning a PostgreSQL large object. These objects embed -and hide all the "recurrent" variables (object OID and connection), exactly -in the same way :class:`pgobject` instances do, thus only keeping significant -parameters in function calls. The class:`pglarge` object keeps a reference -to the :class:`pgobject` used for its creation, sending requests though with -its parameters. Any modification but dereferencing the :class:`pgobject` -will thus affect the :class:`pglarge` object. Dereferencing the initial -:class:`pgobject` is not a problem since Python won't deallocate it before -the :class:`pglarge` object dereferences it. All functions return a generic -error message on call error, whatever the exact error was. The :attr:`error` -attribute of the object allows to get the exact error message. - -See also the PostgreSQL programmer's guide for more information about the -large object interface. - -open -- open a large object ---------------------------- - -.. method:: pglarge.open(mode) - - Open a large object - - :param int mode: open mode definition - :rtype: None - :raises TypeError: invalid connection, bad parameter type, or too many parameters - :raises IOError: already opened object, or open error - -This method opens a large object for reading/writing, in the same way than the -Unix open() function. The mode value can be obtained by OR-ing the constants -defined in the :mod:`pg` module (:const:`INV_READ`, :const:`INV_WRITE`). - -close -- close a large object ------------------------------ - -.. method:: pglarge.close() - - Close a large object - - :rtype: None - :raises TypeError: invalid connection - :raises TypeError: too many parameters - :raises IOError: object is not opened, or close error - -This method closes a previously opened large object, in the same way than -the Unix close() function. - -read, write, tell, seek, unlink -- file-like large object handling ------------------------------------------------------------------- - -.. method:: pglarge.read(size) - - Read data from large object - - :param int size: maximal size of the buffer to be read - :returns: the read buffer - :rtype: str - :raises TypeError: invalid connection, invalid object, - bad parameter type, or too many parameters - :raises ValueError: if `size` is negative - :raises IOError: object is not opened, or read error - -This function allows to read data from a large object, starting at current -position. - -.. method:: pglarge.write(string) - - Read data to large object - - :param str string: string buffer to be written - :rtype: None - :raises TypeError: invalid connection, bad parameter type, or too many parameters - :raises IOError: object is not opened, or write error - -This function allows to write data to a large object, starting at current -position. - -.. method:: pglarge.seek(offset, whence) - - Change current position in large object - - :param int offset: position offset - :param int whence: positional parameter - :returns: new position in object - :rtype: int - :raises TypeError: invalid connection or invalid object, - bad parameter type, or too many parameters - :raises IOError: object is not opened, or seek error - -This method allows to move the position cursor in the large object. -The valid values for the whence parameter are defined as constants in the -:mod:`pg` module (:const:`SEEK_SET`, :const:`SEEK_CUR`, :const:`SEEK_END`). - -.. method:: pglarge.tell() - - Return current position in large object - - :returns: current position in large object - :rtype: int - :raises TypeError: invalid connection or invalid object - :raises TypeError: too many parameters - :raises IOError: object is not opened, or seek error - -This method allows to get the current position in the large object. - -.. method:: pglarge.unlink() - - Delete large object - - :rtype: None - :raises TypeError: invalid connection or invalid object - :raises TypeError: too many parameters - :raises IOError: object is not closed, or unlink error - -This methods unlinks (deletes) the PostgreSQL large object. - -size -- get the large object size ---------------------------------- - -.. method:: pglarge.size() - - Return the large object size - - :returns: the large object size - :rtype: int - :raises TypeError: invalid connection or invalid object - :raises TypeError: too many parameters - :raises IOError: object is not opened, or seek/tell error - -This (composite) method allows to get the size of a large object. It was -implemented because this function is very useful for a web interfaced -database. Currently, the large object needs to be opened first. - -export -- save a large object to a file ---------------------------------------- - -.. method:: pglarge.export(name) - - Export a large object to a file - - :param str name: file to be created - :rtype: None - :raises TypeError: invalid connection or invalid object, - bad parameter type, or too many parameters - :raises IOError: object is not closed, or export error - -This methods allows to dump the content of a large object in a very simple -way. The exported file is created on the host of the program, not the -server host. - -Object attributes ------------------ -:class:`pglarge` objects define a read-only set of attributes that allow -to get some information about it. These attributes are: - -.. attribute:: pglarge.oid - - the OID associated with the object (int) - -.. attribute:: pglarge.pgcnx - - the :class:`pgobject` associated with the object - -.. attribute:: pglarge.error - - the last warning/error message of the connection - -.. note:: - - **Be careful**: - In multithreaded environments, :attr:`pglarge.error` may be modified by - another thread using the same :class:`pgobject`. Remember these object - are shared, not duplicated. You should provide some locking to be able - if you want to check this. The :attr:`pglarge.oid` attribute is very - interesting, because it allows you to reuse the OID later, creating the - :class:`pglarge` object with a :meth:`pgobject.getlo` method call. diff --git a/docs/pgdb.rst b/docs/pgdb.rst deleted file mode 100644 index 6916ba5f..00000000 --- a/docs/pgdb.rst +++ /dev/null @@ -1,522 +0,0 @@ ----------------------------------------------- -:mod:`pgdb` --- The DB-API Compliant Interface ----------------------------------------------- - -.. module:: pgdb - -.. contents:: Contents - - -Introduction -============ - -You may either choose to use the "classic" PyGreSQL interface -provided by the :mod:`pg` module or else the -DB-API 2.0 compliant interface provided by the :mod:`pgdb` module. - -`DB-API 2.0 `_ -(Python Database API Specification v2.0) -is a specification for connecting to databases (not only PostGreSQL) -from Python that has been developed by the Python DB-SIG in 1999. - -The following documentation covers only the newer :mod:`pgdb` API. - -The authoritative programming information for the DB-API is :pep:`0249` - -A useful tutorial-like `introduction to the DB-API -`_ -has been written by Andrew M. Kuchling for the LINUX Journal in 1998. - - -Module functions and constants -============================== - -The :mod:`pgdb` module defines a :func:`connect` function that allows to -connect to a database, some global constants describing the capabilities -of the module as well as several exception classes. - -connect -- Open a PostgreSQL connection ---------------------------------------- - -.. function:: connect([dsn], [user], [password], [host], [database]) - - Return a new connection to the database - - :param str dsn: data source name as string - :param str user: the database user name - :param str password: the database password - :param str host: the hostname of the database - :param database: the name of the database - :returns: a connection object - :rtype: :class:`pgdbCnx` - :raises pgdb.OperationalError: error connecting to the database - -This function takes parameters specifying how to connect to a PostgreSQL -database and returns a :class:`pgdbCnx` object using these parameters. -If specified, the *dsn* parameter must be a string with the format -``'host:base:user:passwd:opt:tty'``. All of the parts specified in the *dsn* -are optional. You can also specify the parameters individually using keyword -arguments, which always take precedence. The *host* can also contain a port -if specified in the format ``'host:port'``. In the *opt* part of the *dsn* -you can pass command-line options to the server, the *tty* part is used to -send server debug output. - -Example:: - - con = connect(dsn='myhost:mydb', user='guido', password='234$') - - -Module constants ----------------- - -.. data:: apilevel - - The string constant ``'2.0'``, stating that the module is DB-API 2.0 level - compliant. - -.. data:: threadsafety - - The integer constant 1, stating that the module itself is thread-safe, - but the connections are not thread-safe, and therefore must be protected - with a lock if you want to use them from different threads. - -.. data:: paramstyle - - The string constant ``pyformat``, stating that parameters should be passed - using Python extended format codes, e.g. ``" ... WHERE name=%(name)s"``. - -Errors raised by this module ----------------------------- - -The errors that can be raised by the :mod:`pgdb` module are the following: - -.. exception:: Warning - - Exception raised for important warnings like data truncations while - inserting. - -.. exception:: Error - - Exception that is the base class of all other error exceptions. You can - use this to catch all errors with one single except statement. - Warnings are not considered errors and thus do not use this class as base. - -.. exception:: InterfaceError - - Exception raised for errors that are related to the database interface - rather than the database itself. - -.. exception:: DatabaseError - - Exception raised for errors that are related to the database. - -.. exception:: DataError - - Exception raised for errors that are due to problems with the processed - data like division by zero or numeric value out of range. - -.. exception:: OperationalError - - Exception raised for errors that are related to the database's operation - and not necessarily under the control of the programmer, e.g. an unexpected - disconnect occurs, the data source name is not found, a transaction could - not be processed, or a memory allocation error occurred during processing. - -.. exception:: IntegrityError - - Exception raised when the relational integrity of the database is affected, - e.g. a foreign key check fails. - -.. exception:: ProgrammingError - - Exception raised for programming errors, e.g. table not found or already - exists, syntax error in the SQL statement or wrong number of parameters - specified. - -.. exception:: NotSupportedError - - Exception raised in case a method or database API was used which is not - supported by the database. - - -pgdbCnx -- The connection object -================================ - -.. class:: pgdbCnx - -These connection objects respond to the following methods. - -Note that ``pgdb.pgdbCnx`` objects also implement the context manager protocol, -i.e. you can use them in a ``with`` statement. - -close -- close the connection ------------------------------ - -.. method:: pgdbCnx.close() - - Close the connection now (rather than whenever it is deleted) - - :rtype: None - -The connection will be unusable from this point forward; an :exc:`Error` -(or subclass) exception will be raised if any operation is attempted with -the connection. The same applies to all cursor objects trying to use the -connection. Note that closing a connection without committing the changes -first will cause an implicit rollback to be performed. - -commit -- commit the connection -------------------------------- - -.. method:: pgdbCnx.commit() - - Commit any pending transaction to the database - - :rtype: None - -Note that connections always use a transaction, there is no auto-commit. - -rollback -- roll back the connection ------------------------------------- - -.. method:: pgdbCnx.rollback() - - Roll back any pending transaction to the database - - :rtype: None - -This method causes the database to roll back to the start of any pending -transaction. Closing a connection without committing the changes first will -cause an implicit rollback to be performed. - -cursor -- return a new cursor object ------------------------------------- - -.. method:: pgdbCnx.cursor() - - Return a new cursor object using the connection - - :returns: a connection object - :rtype: :class:`pgdbCursor` - -This method returns a new :class:`pgdbCursor` object that can be used to -operate on the database in the way described in the next section. - - -pgdbCursor -- The cursor object -=============================== - -.. class:: pgdbCursor - -These objects represent a database cursor, which is used to manage the context -of a fetch operation. Cursors created from the same connection are not -isolated, i.e., any changes done to the database by a cursor are immediately -visible by the other cursors. Cursors created from different connections can -or can not be isolated, depending on the level of transaction isolation. -The default PostgreSQL transaction isolation level is "read committed". - -Cursor objects respond to the following methods and attributes. - -Note that ``pgdbCursor`` objects also implement both the iterator and the -context manager protocol, i.e. you can iterate over them and you can use them -in a ``with`` statement. - -description -- details regarding the result columns ---------------------------------------------------- - -.. attribute:: pgdbCursor.description - - This read-only attribute is a sequence of 7-item tuples. - - Each of these tuples contains information describing one result column: - - - *name* - - *type_code* - - *display_size* - - *internal_size* - - *precision* - - *scale* - - *null_ok* - - Note that *display_size*, *precision*, *scale* and *null_ok* - are not implemented. - - This attribute will be ``None`` for operations that do not return rows - or if the cursor has not had an operation invoked via the - :meth:`pgdbCursor.execute` or :meth:`pgdbCursor.executemany` method yet. - -rowcount -- number of rows of the result ----------------------------------------- - -.. attribute:: pgdbCursor.rowcount - - This read-only attribute specifies the number of rows that the last - :meth:`pgdbCursor.execute` or :meth:`pgdbCursor.executemany` call produced - (for DQL statements like SELECT) or affected (for DML statements like - UPDATE or INSERT ). The attribute is -1 in case no such method call has - been performed on the cursor or the rowcount of the last operation - cannot be determined by the interface. - -close -- close the cursor -------------------------- - -.. method:: pgdbCursor.close() - - Close the cursor now (rather than whenever it is deleted) - - :rtype: None - -The cursor will be unusable from this point forward; an :exc:`Error` -(or subclass) exception will be raised if any operation is attempted -with the cursor. - -execute -- execute a database operation ---------------------------------------- - -.. method:: pgdbCursor.execute(operation, [parameters]) - - Prepare and execute a database operation (query or command) - - :param str operation: the database operation - :param parameters: a sequence or mapping of parameters - :returns: the cursor, so you can chain commands - -Parameters may be provided as sequence or mapping and will be bound to -variables in the operation. Variables are specified using Python extended -format codes, e.g. ``" ... WHERE name=%(name)s"``. - -A reference to the operation will be retained by the cursor. If the same -operation object is passed in again, then the cursor can optimize its behavior. -This is most effective for algorithms where the same operation is used, -but different parameters are bound to it (many times). - -The parameters may also be specified as list of tuples to e.g. insert multiple -rows in a single operation, but this kind of usage is deprecated: -:meth:`pgdbCursor.executemany` should be used instead. - -executemany -- execute many similar database operations -------------------------------------------------------- - -.. method:: pgdbCursor.executemany(operation, [seq_of_parameters]) - - Prepare and execute many similar database operations (queries or commands) - - :param str operation: the database operation - :param seq_of_parameters: a sequence or mapping of parameter tuples or mappings - :returns: the cursor, so you can chain commands - -Prepare a database operation (query or command) and then execute it against -all parameter tuples or mappings found in the sequence *seq_of_parameters*. - -Parameters are bounded to the query using Python extended format codes, -e.g. ``" ... WHERE name=%(name)s"``. - -fetchone -- fetch next row of the query result ----------------------------------------------- - -.. method:: pgdbCursor.fetchone() - - Fetch the next row of a query result set - - :returns: the next row of the query result set - :rtype: list or None - -Fetch the next row of a query result set, returning a single list, -or ``None`` when no more data is available. - -An :exc:`Error` (or subclass) exception is raised if the previous call to -:meth:`pgdbCursor.execute` or :meth:`pgdbCursor.executemany` did not produce -any result set or no call was issued yet. - -fetchmany -- fetch next set of rows of the query result -------------------------------------------------------- - -.. method:: pgdbCursor.fetchmany([size=None], [keep=False]) - - Fetch the next set of rows of a query result - - :param size: the number of rows to be fetched - :type size: int or None - :param keep: if set to true, will keep the passed arraysize - :tpye keep: bool - :returns: the next set of rows of the query result - :rtype: list of lists - -Fetch the next set of rows of a query result, returning a list of lists. -An empty sequence is returned when no more rows are available. - -The number of rows to fetch per call is specified by the *size* parameter. -If it is not given, the cursor's :attr:`arraysize` determines the number of -rows to be fetched. If you set the *keep* parameter to True, this is kept as -new :attr:`arraysize`. - -The method tries to fetch as many rows as indicated by the *size* parameter. -If this is not possible due to the specified number of rows not being -available, fewer rows may be returned. - -An :exc:`Error` (or subclass) exception is raised if the previous call to -:meth:`pgdbCursor.execute` or :meth:`pgdbCursor.executemany` did not produce -any result set or no call was issued yet. - -Note there are performance considerations involved with the *size* parameter. -For optimal performance, it is usually best to use the :attr:`arraysize` -attribute. If the *size* parameter is used, then it is best for it to retain -the same value from one :meth:`pgdbCursor.fetchmany` call to the next. - -fetchall -- fetch all rows of the query result ----------------------------------------------- - -.. method:: pgdbCursor.fetchall() - - Fetch all (remaining) rows of a query result - - :returns: the set of all rows of the query result - :rtype: list of list - -Fetch all (remaining) rows of a query result, returning them as list of lists. -Note that the cursor's :attr:`arraysize` attribute can affect the performance -of this operation. - -row_factory -- process a row of the query result ------------------------------------------------- - -.. method:: pgdbCursor.row_factory(row) - - Process rows before they are returned - - :param list row: the currently processed row of the result set - :returns: the transformed row that the cursor methods shall return - -.. note:: - - This method is not part of the DB-API 2 standard. - -You can overwrite this method with a custom row factory, e.g. -if you want to return rows as dicts instead of lists:: - - class DictCursor(pgdb.pgdbCursor): - - def row_factory(self, row): - return dict((d[0], v) for d, v in zip(self.description, row)) - - cur = DictCursor(con) - -arraysize - the number of rows to fetch at a time -------------------------------------------------- - -.. attribute:: pgdbCursor.arraysize - - The number of rows to fetch at a time - -This read/write attribute specifies the number of rows to fetch at a time with -:meth:`pgdbCursor.fetchmany`. It defaults to 1 meaning to fetch a single row -at a time. - - -pgdbType -- Type objects and constructors -========================================= - -.. class:: pgdbType - -The :attr:`pgdbCursor.description` attribute returns information about each -of the result columns of a query. The *type_code* must compare equal to one -of the :class:`pgdbType` objects defined below. Type objects can be equal to -more than one type code (e.g. :class:`DATETIME` is equal to the type codes -for date, time and timestamp columns). - -The :mod:`pgdb` module exports the following constructors and singletons: - -.. function:: Date(year, month, day) - - Construct an object holding a date value - -.. function:: Time(hour, minute=0, second=0, microsecond=0) - - Construct an object holding a time value - -.. function:: Timestamp(year, month, day, hour=0, minute=0, second=0, microsecond=0) - - Construct an object holding a time stamp value - -.. function:: DateFromTicks(ticks) - - Construct an object holding a date value from the given *ticks* value - -.. function:: TimeFromTicks(ticks) - - Construct an object holding a time value from the given *ticks* value - -.. function:: TimestampFromTicks(ticks) - - Construct an object holding a time stamp from the given *ticks* value - -.. function:: Binary(bytes) - - Construct an object capable of holding a (long) binary string value - -.. class:: STRING - - Used to describe columns that are string-based (e.g. ``char``, ``varchar``, ``text``) - -.. class:: BINARY type - - Used to describe (long) binary columns (``bytea``) - -.. class:: NUMBER - - Used to describe numeric columns (e.g. ``int``, ``float``, ``numeric``, ``money``) - -.. class:: DATETIME - - Used to describe date/time columns (e.g. ``date``, ``time``, ``timestamp``, ``interval``) - -.. class:: ROWID - - Used to describe the ``oid`` column of PostgreSQL database tables - -.. note: - - The following more specific types are not part of the DB-API 2 standard. - -.. class:: BOOL - - Used to describe ``boolean`` columns - -.. class:: SMALLINT - - Used to describe ``smallint`` columns - -.. class:: INTEGER - - Used to describe ``integer`` columns - -.. class:: LONG - - Used to describe ``bigint`` columns - -.. class:: FLOAT - - Used to describe ``float`` columns - -.. class:: NUMERIC - - Used to describe ``numeric`` columns - -.. class:: MONEY - - Used to describe ``money`` columns - -.. class:: DATE - - Used to describe ``date`` columns - -.. class:: TIME - - Used to describe ``time`` columns - -.. class:: TIMESTAMP - - Used to describe ``timestamp`` columns - -.. class:: INTERVAL - - Used to describe date and time ``interval`` columns diff --git a/docs/readme.rst b/docs/readme.rst deleted file mode 100644 index 0b08e27b..00000000 --- a/docs/readme.rst +++ /dev/null @@ -1,208 +0,0 @@ -========================================== -PyGreSQL - Python interface for PostgreSQL -========================================== - --------------------- -PyGreSQL version 4.2 --------------------- - -.. meta:: - :description: PyGreSQL - Python interface for PostgreSQL - :keywords: PyGreSQL, PostGreSQL, Python - -.. contents:: Contents - - -Copyright notice -================ - -Written by D'Arcy J.M. Cain (darcy@druid.net) - -Based heavily on code written by Pascal Andre (andre@chimay.via.ecp.fr) - -Copyright (c) 1995, Pascal Andre - -Further modifications copyright (c) 1997-2008 by D'Arcy J.M. Cain -(darcy@PyGreSQL.org) - -Further modifications copyright (c) 2009-2016 by the PyGreSQL team. - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose, without fee, and without a written agreement -is hereby granted, provided that the above copyright notice and this -paragraph and the following two paragraphs appear in all copies. In -this license the term "AUTHORS" refers to anyone who has contributed code -to PyGreSQL. - -IN NO EVENT SHALL THE AUTHORS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, -ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF -AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -THE AUTHORS SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED -TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE -AUTHORS HAVE NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, -ENHANCEMENTS, OR MODIFICATIONS. - - -Introduction -============ - -**PostgreSQL** is a highly scalable, SQL compliant, open source -object-relational database management system. With more than 15 years -of development history, it is quickly becoming the de facto database -for enterprise level open source solutions. -Best of all, PostgreSQL's source code is available under the most liberal -open source license: the BSD license. - -**Python** Python is an interpreted, interactive, object-oriented -programming language. It is often compared to Tcl, Perl, Scheme or Java. -Python combines remarkable power with very clear syntax. It has modules, -classes, exceptions, very high level dynamic data types, and dynamic typing. -There are interfaces to many system calls and libraries, as well as to -various windowing systems (X11, Motif, Tk, Mac, MFC). New built-in modules -are easily written in C or C++. Python is also usable as an extension -language for applications that need a programmable interface. -The Python implementation is copyrighted but freely usable and distributable, -even for commercial use. - -**PyGreSQL** is a Python module that interfaces to a PostgreSQL database. -It embeds the PostgreSQL query library to allow easy use of the powerful -PostgreSQL features from a Python script. - -PyGreSQL is developed and tested on a NetBSD system, but it should also -run on most other platforms where PostgreSQL and Python is running. -It is based on the PyGres95 code written by Pascal Andre (andre@chimay.via.ecp.fr). -D'Arcy (darcy@druid.net) renamed it to PyGreSQL starting with -version 2.0 and serves as the "BDFL" of PyGreSQL. - -The current version PyGreSQL 4.2 needs PostgreSQL 8.3 and Python 2.5 or above. - - -Where to get ... ? -================== - -Home sites of the different packages ------------------------------------- -**Python**: - http://www.python.org - -**PostgreSQL**: - http://www.postgresql.org - -**PyGreSQL**: - http://www.pygresql.org - -Download PyGreSQL here ----------------------- -The **released version of the source code** is available at - * http://pygresql.org/files/PyGreSQL.tgz -You can also check the latest **pre-release version** at - * http://pygresql.org/files/PyGreSQL-beta.tgz -A **Linux RPM** can be picked up from - * http://pygresql.org/files/pygresql.i386.rpm -A **NetBSD package** is available in their pkgsrc collection - * ftp://ftp.netbsd.org/pub/NetBSD/packages/pkgsrc/databases/py-postgresql/README.html -A **FreeBSD package** is available in their ports collection - * http://www.freebsd.org/cgi/cvsweb.cgi/ports/databases/py-PyGreSQL/ -A **Win32 package** for various Python versions is available at - * http://pygresql.org/files/PyGreSQL-4.2.win32-py2.5.exe - * http://pygresql.org/files/PyGreSQL-4.2.win32-py2.6.exe - * http://pygresql.org/files/PyGreSQL-4.2.win32-py2.7.exe -You can also find PyGreSQL on the **Python Package Index** at - * http://pypi.python.org/pypi/PyGreSQL/ - - -Distribution files -================== - -========== = -pgmodule.c the C Python module (_pg) -pg.py the "classic" PyGreSQL module -pgdb.py DB-SIG DB-API 2.0 compliant API wrapper for PygreSQL -docs/ documentation directory - - Contains: readme.txt, announce.txt, install.txt, - changelog.txt, future.txt, pg.txt and pgdb.txt. - - All text files are in ReST format, so HTML versions - can be easily created with buildhtml.py from docutils. -tutorial/ demos directory - - Contains: basics.py, syscat.py, advanced.py and func.py. - - The samples here have been taken from the - PostgreSQL manual and were used for module testing. - They demonstrate some PostgreSQL features. -========== = - - -Installation -============ -You will find the installing instructions in -`install.txt `_. - - -Information and support -======================= - -For general information ------------------------ -**Python**: - http://www.python.org - -**PostgreSQL**: - http://www.postgresql.org - -**PyGreSQL**: - http://www.pygresql.org - -For support ------------ -**Python**: - see http://www.python.org/community/ - -**PostgreSQL**: - see http://www.postgresql.org/support/ - -**PyGreSQL**: - Contact the PyGreSQL mailing list - concerning PyGreSQL 2.0 and up. - - If you would like to proposes changes, please join the - PyGreSQL mailing list and send context diffs there. - - See https://mail.vex.net/mailman/listinfo.cgi/pygresql - to join the mailing list. - -Please note that messages to individual developers will generally not be -answered directly. All questions, comments and code changes must be -submitted to the mailing list for peer review and archiving purposes. - -PyGreSQL programming information --------------------------------- -You may either choose to use the "classic" PyGreSQL interface -provided by the `pg` module or else the newer DB-API 2.0 -compliant interface provided by the `pgdb` module. - -`DB-API 2.0 `_ -(Python Database API Specification v2.0) -is a specification for connecting to databases (not only PostGreSQL) -from Python that has been developed by the Python DB-SIG in 1999. - -The programming information is available in the files -`pg.txt `_ and `pgdb.txt `_. - -Note that PyGreSQL is not thread-safe on the connection level. Therefore -we recommend using `DBUtils ` -for multi-threaded environments, which supports both PyGreSQL interfaces. - - -ChangeLog and Future -==================== -The ChangeLog with past changes is in the file -`changelog.txt `_. - -A to do list and wish list is in the file -`future.txt `_. diff --git a/docs/source.rst b/docs/source.rst deleted file mode 100644 index 622c8710..00000000 --- a/docs/source.rst +++ /dev/null @@ -1,7 +0,0 @@ -Access to the source repository -=============================== - -The SVN repository can be checked out from -:file:`svn://svn.pygresql.org/pygresql`. -It is also available through the -`online SVN repository `_. diff --git a/mkdocs b/mkdocs index a113611d..2992db80 100755 --- a/mkdocs +++ b/mkdocs @@ -13,4 +13,5 @@ fi echo "Making Sphinx docs..." cd docs +${MAKE} clean ${MAKE} html diff --git a/mktar b/mktar index 76af22b4..467a3e68 100755 --- a/mktar +++ b/mktar @@ -1,13 +1,19 @@ #! /bin/sh VERSION=4.2 +DISTDIR=/u/pyg/files -# small safety test +# small safety tests if [ ! -f module/pgmodule.c ] then echo "Hmmm. Are you sure you are in the right directory?" exit 1 fi +if [ ! -d $DISTDIR ] +then + echo "Hmmm. Are you sure you are on the right server?" + exit 1 +fi if [ -f BETA ] then @@ -23,7 +29,6 @@ fi # Note that this does essentially the same as "python setup.py sdist", # except this also makes the docs and bundles them as source and html. -DISTDIR=/u/pyg/files TD=PyGreSQL-$VERSION TF=$DISTDIR/$TD.tgz @@ -31,8 +36,9 @@ MODFILES="module/pg.py module/pgdb.py module/pgmodule.c module/pgfs.h module/pgtypes.h module/setup.py module/setup.cfg" DOCFILES="docs/Makefile docs/make.bat docs/*.rst - docs/_build/html/*.html docs/_build/html/*.js - docs/_build/html/_static" + docs/contents docs/download docs/community + docs/_static docs/_templates" +HTMLFILES="docs/_build/html" TESTFILES="module/tests/*.py" TUTFILES="tutorial/*.py" @@ -42,11 +48,12 @@ echo "Making source tarball..." rm -rf $TD mkdir $TD -mkdir -p $TD/docs/_static +mkdir -p $TD/docs/_build/html mkdir $TD/tests mkdir $TD/tutorial cp $MODFILES $TD cp -r $DOCFILES $TD/docs +cp -r $HTMLFILES $TD/docs/_build cp $TESTFILES $TD/tests cp $TUTFILES $TD/tutorial tar -cvzf $TF $TD From 80df3e7435c4be7aa1c8ab89b5dada1bdca98682 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 7 Jan 2016 16:26:45 +0000 Subject: [PATCH 059/144] Minor doc fixes, remove index.html The index.html file is not needed any more as the whole website is now created by Sphinx. --- docs/contents/index.rst | 1 - docs/contents/pgdb/cursor.rst | 2 +- docs/contents/pgdb/types.rst | 2 +- docs/index.html | 183 ---------------------------------- docs/index.rst | 5 +- 5 files changed, 3 insertions(+), 190 deletions(-) delete mode 100644 docs/index.html diff --git a/docs/contents/index.rst b/docs/contents/index.rst index 392d8e11..3c381538 100644 --- a/docs/contents/index.rst +++ b/docs/contents/index.rst @@ -1,4 +1,3 @@ - The PyGreSQL documentation ========================== diff --git a/docs/contents/pgdb/cursor.rst b/docs/contents/pgdb/cursor.rst index 49e0e29d..d4acfa03 100644 --- a/docs/contents/pgdb/cursor.rst +++ b/docs/contents/pgdb/cursor.rst @@ -180,7 +180,7 @@ row_factory -- process a row of the query result Process rows before they are returned :param list row: the currently processed row of the result set - :returns: the transformed row that the cursor methods shall return + :returns: the transformed row that the fetch methods shall return .. note:: diff --git a/docs/contents/pgdb/types.rst b/docs/contents/pgdb/types.rst index 87ca5dc8..e7cd5062 100644 --- a/docs/contents/pgdb/types.rst +++ b/docs/contents/pgdb/types.rst @@ -59,7 +59,7 @@ The :mod:`pgdb` module exports the following constructors and singletons: Used to describe the ``oid`` column of PostgreSQL database tables -.. note: +.. note:: The following more specific types are not part of the DB-API 2 standard. diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index a462b7cf..00000000 --- a/docs/index.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - - PyGreSQL - PostgreSQL module for Python - - - - - - - - -
PyGreSQL
-
Version 4.2
- -
:: PostgreSQL module for Python ::
- -
- -

PyGreSQL – PostgreSQL module for Python

- -

PyGreSQL is an open-source - Python module - that interfaces to a PostgreSQL database. - It embeds the PostgreSQL query library to allow easy use of the powerful PostgreSQL - features from a Python script.

- -

This software is copyright © 1995, Pascal Andre.
- Further modifications are copyright © 1997-2008 by D'Arcy J.M. Cain.
- Further modifications are copyright © 2009-2016 by the PyGreSQL team

- -

See the - copyright notice - for detailed information.

- - -

Documentation

- -

The following information is also available in the docs folder of the distribution:

- - - - -

SVN Access

- -

- The SVN repository can be checked out from svn://svn.PyGreSQL.org/pygresql. - It is also available through the - online - SVN repository

- - -

Mailing list

- -

You can join - the mailing - list to discuss future development of the PyGreSQL interface. - This is usually a low volume list except when there are new features - being added.

- - -

Examples

- -

I am starting to collect examples of applications that use PyGreSQL. - So far I only have a few but if you have an example for me, you can - either send me the files or the URL for me to point to.

- -

Here is a List of motorcycle - rides in Ontario that uses a PostgreSQL database to store the - rides. There is a link at the bottom of the page to view the source code.

- -

- Oleg Broytmann has written a simple example - RGB - database demo. -

- -
- - - - diff --git a/docs/index.rst b/docs/index.rst index 69a98c5e..73686160 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,4 @@ -.. PyGreSQL documentation master file, created by - sphinx-quickstart on Thu Nov 1 07:47:06 2012. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. +.. PyGreSQL documentation master file Welcome to PyGreSQL =================== From 8453a43826d790f5dfd2463883c8d07b417a029f Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 7 Jan 2016 22:00:27 +0000 Subject: [PATCH 060/144] Skip over known libpq issue under Windows --- module/tests/test_classic_connection.py | 19 +++++++++++++++++-- module/tests/test_classic_dbwrapper.py | 9 +++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/module/tests/test_classic_connection.py b/module/tests/test_classic_connection.py index 04218ae5..4ae295c7 100755 --- a/module/tests/test_classic_connection.py +++ b/module/tests/test_classic_connection.py @@ -19,6 +19,7 @@ import tempfile import threading import time +import os import pg # the module under test @@ -39,6 +40,13 @@ except ImportError: pass +windows = os.name == 'nt' + +# There is a known a bug in libpq under Windows which can cause +# the interface to crash when calling PQhost(): +do_not_ask_for_host = windows +do_not_ask_for_host_reason = 'libpq issue on Windows' + def connect(): """Create a basic pg connection to the test database.""" @@ -73,11 +81,17 @@ def tearDown(self): except pg.InternalError: pass + def is_method(self, attribute): + """Check if given attribute on the connection is a method.""" + if do_not_ask_for_host and attribute == 'host': + return False + return callable(getattr(self.connection, attribute)) + def testAllConnectAttributes(self): attributes = '''db error host options port protocol_version server_version status tty user'''.split() connection_attributes = [a for a in dir(self.connection) - if not callable(eval("self.connection." + a))] + if not a.startswith('__') and not self.is_method(a)] self.assertEqual(attributes, connection_attributes) def testAllConnectMethods(self): @@ -90,7 +104,7 @@ def testAllConnectMethods(self): methods.remove('escape_identifier') methods.remove('escape_literal') connection_methods = [a for a in dir(self.connection) - if callable(eval("self.connection." + a))] + if not a.startswith('__') and self.is_method(a)] self.assertEqual(methods, connection_methods) def testAttributeDb(self): @@ -100,6 +114,7 @@ def testAttributeError(self): error = self.connection.error self.assertTrue(not error or 'krb5_' in error) + @unittest.skipIf(do_not_ask_for_host, do_not_ask_for_host_reason) def testAttributeHost(self): def_host = 'localhost' self.assertIsInstance(self.connection.host, str) diff --git a/module/tests/test_classic_dbwrapper.py b/module/tests/test_classic_dbwrapper.py index a74517a0..5934d1f0 100755 --- a/module/tests/test_classic_dbwrapper.py +++ b/module/tests/test_classic_dbwrapper.py @@ -15,6 +15,7 @@ import unittest2 as unittest # for Python < 2.7 except ImportError: import unittest +import os import sys @@ -39,6 +40,13 @@ except ImportError: pass +windows = os.name == 'nt' + +# There is a known a bug in libpq under Windows which can cause +# the interface to crash when calling PQhost(): +do_not_ask_for_host = windows +do_not_ask_for_host_reason = 'libpq issue on Windows' + def DB(): """Create a DB wrapper object connecting to the test database.""" @@ -138,6 +146,7 @@ def testAttributeError(self): self.assertTrue(not error or 'krb5_' in error) self.assertEqual(self.db.error, self.db.db.error) + @unittest.skipIf(do_not_ask_for_host, do_not_ask_for_host_reason) def testAttributeHost(self): def_host = 'localhost' host = self.db.host From 75c68946a409146f1ef34418a1bf0c5409bcd5f0 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 7 Jan 2016 22:25:05 +0000 Subject: [PATCH 061/144] Amend the large obj tests for Windows --- module/tests/test_classic_largeobj.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/module/tests/test_classic_largeobj.py b/module/tests/test_classic_largeobj.py index ba0f4ce7..58ec6316 100755 --- a/module/tests/test_classic_largeobj.py +++ b/module/tests/test_classic_largeobj.py @@ -382,15 +382,25 @@ def testExport(self): self.assertRaises(IOError, export, f.name) self.obj.close() export(fname) + if windows: + f.close() + f = open(fname, 'rb') r = f.read() f.close() + if windows: + os.remove(fname) self.assertEqual(r, data) def testPrint(self): self.obj.open(pg.INV_WRITE) data = 'some object to be printed' self.obj.write(data) - f = tempfile.TemporaryFile() + if windows: + # TemporaryFiles don't work well here + fname = 'temp_test_pg_largeobj_export.txt' + f = open(fname, 'wb') + else: + f = tempfile.TemporaryFile() stdout, sys.stdout = sys.stdout, f try: print self.obj @@ -399,9 +409,15 @@ def testPrint(self): except Exception: pass sys.stdout = stdout - f.seek(0) + if windows: + f.close() + f = open(fname, 'rb') + else: + f.seek(0) r = f.read() f.close() + if windows: + os.remove(fname) oid = self.obj.oid self.assertEqual(r, 'Opened large object, oid %d\n' From 140b91b648ef496159768a99da39793e40265879 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 8 Jan 2016 13:18:36 +0000 Subject: [PATCH 062/144] Use appropriate compiler options with Visual Studio The MSVC compiler does not recognize some of the GNU compiler options. So when MSVC is detected, we replace them with equivalent options. --- module/setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/module/setup.py b/module/setup.py index 04128922..cc2db054 100755 --- a/module/setup.py +++ b/module/setup.py @@ -88,7 +88,7 @@ def pg_version(): library_dirs = [get_python_lib(), pg_config('libdir')] define_macros = [('PYGRESQL_VERSION', version)] undef_macros = [] -extra_compile_args = ['-O2', '-Wall', '-Werror', '-funsigned-char'] +extra_compile_args = ['-O2', '-funsigned-char', '-Wall', '-Werror'] class build_pg_ext(build_ext): @@ -160,6 +160,7 @@ def finalize_options(self): define_macros.append(('MS_WIN64', None)) elif compiler == 'msvc': # Microsoft Visual C++ libraries[0] = 'lib' + libraries[0] + extra_compile_args[1:] = ['-J', '-W3', '-WX'] setup( From ccc67639ab77f1a8a2d8c877835fe975dfa6faeb Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 8 Jan 2016 14:17:48 +0000 Subject: [PATCH 063/144] Confirmed that PyGreSQL runs with PostgreSQL 9.5 All tests pass with PostgreSQL 9.5 on Ubuntu 64bit and Windows 7 32/64 bit. --- docs/contents/changelog.rst | 2 +- module/setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contents/changelog.rst b/docs/contents/changelog.rst index 71d090db..95e350e4 100644 --- a/docs/contents/changelog.rst +++ b/docs/contents/changelog.rst @@ -5,7 +5,7 @@ Version 4.2 ----------- - Set a better default for the user option "escaping-funcs". - The supported Python versions are 2.4 to 2.7. -- PostgreSQL is supported in all versions from 8.3 to 9.4. +- PostgreSQL is supported in all versions from 8.3 to 9.5. - Force build to compile with no errors. - Fix decimal point handling. - Add option to return boolean values as bool objects. diff --git a/module/setup.py b/module/setup.py index cc2db054..6329a6f6 100755 --- a/module/setup.py +++ b/module/setup.py @@ -21,7 +21,7 @@ * PostgreSQL pg_config tool (usually included in the devel package) (the Windows installer has it as part of the database server feature) -The supported versions are Python 2.4-2.7 and PostgreSQL 8.3-9.4. +The supported versions are Python 2.4-2.7 and PostgreSQL 8.3-9.5. Use as follows: python setup.py build # to build the module From 93a89cd89359caca5ddc2b51b73e17de81dd6370 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 9 Jan 2016 22:53:44 +0000 Subject: [PATCH 064/144] Make sure DB methods respect the new bool option Two DB methods assumed that booleans are always returned as strings, which is no longer true when the set_bool() option is activated. Added a test run with different global options to make sure that no DB methods make such tacit assumptions about these options. --- module/pg.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/module/pg.py b/module/pg.py index 0870e817..93fdcfad 100644 --- a/module/pg.py +++ b/module/pg.py @@ -338,6 +338,13 @@ def _do_debug(self, s): else: print(s) + def _make_bool(d): + """Get boolean value corresponding to d.""" + if get_bool(): + return bool(d) + return d and 't' or 'f' + _make_bool = staticmethod(_make_bool) + def _quote_text(self, d): """Quote text value.""" if not isinstance(d, basestring): @@ -352,9 +359,7 @@ def _quote_bool(self, d): if not d: return 'NULL' d = d.lower() in self._bool_true - else: - d = bool(d) - return ("'f'", "'t'")[d] + return d and "'t'" or "'f'" _date_literals = frozenset('current_date current_time' ' current_timestamp localtime localtimestamp'.split()) @@ -716,7 +721,7 @@ def has_table_privilege(self, cl, privilege='select'): return self._privileges[(qcl, privilege)] except KeyError: q = "SELECT has_table_privilege('%s', '%s')" % (qcl, privilege) - ret = self.db.query(q).getresult()[0][0] == 't' + ret = self.db.query(q).getresult()[0][0] == self._make_bool(True) self._privileges[(qcl, privilege)] = ret return ret @@ -895,7 +900,7 @@ def update(self, cl, d=None, **kw): def clear(self, cl, a=None): """Clear all the attributes to values determined by the types. - Numeric types are set to 0, Booleans are set to 'f', and everything + Numeric types are set to 0, Booleans are set to false, and everything else is set to the empty string. If the array argument is present, it is used as the array and any entries matching attribute names are cleared with everything else left unchanged. @@ -914,7 +919,7 @@ def clear(self, cl, a=None): 'num', 'numeric', 'money'): a[n] = 0 elif t in ('bool', 'boolean'): - a[n] = 'f' + a[n] = self._make_bool(False) else: a[n] = '' return a From 6ad8604dc3bd93b2622eefe6fa14f353fe210693 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 10 Jan 2016 11:43:51 +0000 Subject: [PATCH 065/144] Add version information for new/changed features to docs --- docs/conf.py | 2 +- docs/contents/pg/connection.rst | 11 +++++++ docs/contents/pg/db_wrapper.rst | 55 ++++++++++++++++++++++++++++----- docs/contents/pg/module.rst | 8 +++++ docs/contents/pg/query.rst | 2 ++ docs/contents/pgdb/cursor.rst | 2 ++ docs/copyright.rst | 2 +- 7 files changed, 73 insertions(+), 9 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index bb7ed2ee..cbc40438 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ # General information about the project. project = u'PyGreSQL' author = u'The PyGreSQL Team' -copyright = u'2015, ' + author +copyright = u'2016, ' + author # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/contents/pg/connection.rst b/docs/contents/pg/connection.rst index ea3ade99..39ccc4cd 100644 --- a/docs/contents/pg/connection.rst +++ b/docs/contents/pg/connection.rst @@ -143,6 +143,9 @@ connection that triggered the notify, and *extra* is a payload string that has been sent with the notification. Remember to do a listen query first, otherwise :meth:`pgobject.getnotify` will always return ``None``. +.. versionchanged:: 4.1 + Support for payload strings was added in version 4.1. + inserttable -- insert a list into a table ----------------------------------------- @@ -184,6 +187,8 @@ This method gets the custom notice receiver callback function that has been set with :meth:`pgobject.set_notice_receiver`, or ``None`` if no custom notice receiver has ever been set on the connection. +.. versionadded:: 4.1 + .. method:: pgobject.set_notice_receiver(proc) Set a custom notice receiver @@ -224,6 +229,8 @@ attributes: an optional suggestion what to do about the problem +.. versionadded:: 4.1 + putline -- write a line to the server socket [DA] ------------------------------------------------- @@ -349,10 +356,14 @@ the connection and its status. These attributes are: the frontend/backend protocol being used (int) +.. versionadded:: 4.0 + .. attribute:: pgobject.server_version the backend version (int, e.g. 80305 for 8.3.5) +.. versionadded:: 4.0 + .. attribute:: pgobject.status the status of the connection (int: 1 = OK, 0 = bad) diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index 31d6c22e..904e40ee 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -271,20 +271,42 @@ as munged by get or passed as keyword, or on the primary key of the table. The return value is the number of deleted rows (i.e. 0 if the row did not exist and 1 if the row was deleted). -escape_string -- escape a string for use within SQL ---------------------------------------------------- +escape_literal -- escape a literal string for use within SQL +------------------------------------------------------------ -.. method:: DB.escape_string(string) +.. method:: DB.escape_literal(string) - Escape a string for use within SQL + Escape a string for use within SQL as a literal constant :param str string: the string that is to be escaped :returns: the escaped string :rtype: str -Similar to the module function with the same name, but the -behavior of this method is adjusted depending on the connection properties -(such as character encoding). +This method escapes a string for use within an SQL command. This is useful +when inserting data values as literal constants in SQL commands. Certain +characters (such as quotes and backslashes) must be escaped to prevent them +from being interpreted specially by the SQL parser. + +.. versionadded:: 4.1 + +escape_identifier -- escape an identifier string for use within SQL +------------------------------------------------------------------- + +.. method:: DB.escape_identifier(string) + + Escape a string for use within SQL as an identifier + + :param str string: the string that is to be escaped + :returns: the escaped string + :rtype: str + +This method escapes a string for use as an SQL identifier, such as a table, +column, or function name. This is useful when a user-supplied identifier +might contain special characters that would otherwise not be interpreted +as part of the identifier by the SQL parser, or when the identifier might +contain upper case characters whose case should be preserved. + +.. versionadded:: 4.1 escape_bytea -- escape binary data for use within SQL ----------------------------------------------------- @@ -313,3 +335,22 @@ unescape_bytea -- unescape data that has been retrieved as text :rtype: str See the module function with the same name. + +use_regtypes -- determine use of regular type names +--------------------------------------------------- + +.. method:: DB.use_regtypes([regtypes]) + + Determine whether regular type names shall be used + + :param bool regtypes: if passed, set whether regular type names shall be used + :returns: whether regular type names are used + +The :meth:`DB.get_attnames` method can return either simplified "classic" +type names (the default) or more specific "regular" type names. Which kind +of type names is used can be changed by calling :meth:`DB.get_regtypes`. +If you pass a boolean, it sets whether regular type names shall be used. +The method can also be used to check through its return value whether +currently regular type names are used. + +.. versionadded:: 4.1 diff --git a/docs/contents/pg/module.rst b/docs/contents/pg/module.rst index 2cf6d2e7..02ff35f8 100644 --- a/docs/contents/pg/module.rst +++ b/docs/contents/pg/module.rst @@ -383,6 +383,8 @@ use ``set_decimal()`` to set a different decimal mark manually. A return value of ``None`` means monetary values are not interpreted as decimal numbers, but returned as strings including the formatting and currency. +.. versionadded:: 4.1.1 + .. function:: set_decimal_point(string) Specify which decimal mark is used for interpreting monetary values @@ -400,6 +402,8 @@ to decimal numbers, then you can call ``set_decimal(None)``, which will cause PyGreSQL to return monetary values as strings including their formatting and currency. +.. versionadded:: 4.1.1 + get/set_bool -- whether boolean values are returned as bool objects ------------------------------------------------------------------- @@ -416,6 +420,8 @@ strings which are the values used internally by PostgreSQL. By default, conversion to bool objects is not activated, but you can enable this with the ``set_bool()`` method. +.. versionadded:: 4.2 + .. function:: set_bool(on) Set whether boolean values are returned as bool objects @@ -428,6 +434,8 @@ PostgreSQL boolean values converted to Python bool objects, or as By default, conversion to bool objects is not activated, but you can enable this by calling ``set_bool(True)``. +.. versionadded:: 4.2 + get/set_namedresult -- conversion to named tuples ------------------------------------------------- diff --git a/docs/contents/pg/query.rst b/docs/contents/pg/query.rst index e7738665..b1582e14 100644 --- a/docs/contents/pg/query.rst +++ b/docs/contents/pg/query.rst @@ -56,6 +56,8 @@ namedresult -- get query values as list of named tuples This method returns the list of the values returned by the query with each row returned as a named tuple with proper field names. +.. versionadded:: 4.1 + listfields -- list fields names of previous query result -------------------------------------------------------- diff --git a/docs/contents/pgdb/cursor.rst b/docs/contents/pgdb/cursor.rst index d4acfa03..414e1f83 100644 --- a/docs/contents/pgdb/cursor.rst +++ b/docs/contents/pgdb/cursor.rst @@ -196,6 +196,8 @@ if you want to return rows as dicts instead of lists:: cur = DictCursor(con) +.. versionadded:: 4.0 + arraysize - the number of rows to fetch at a time ------------------------------------------------- diff --git a/docs/copyright.rst b/docs/copyright.rst index 04baf145..42f72525 100644 --- a/docs/copyright.rst +++ b/docs/copyright.rst @@ -10,7 +10,7 @@ Copyright (c) 1995, Pascal Andre Further modifications copyright (c) 1997-2008 by D'Arcy J.M. Cain (darcy@PyGreSQL.org) -Further modifications copyright (c) 2009-2015 by the PyGreSQL team. +Further modifications copyright (c) 2009-2016 by the PyGreSQL team. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement From f13a53970e24b8d9ba3d1ecc6daa6ba7ce91b66a Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 10 Jan 2016 20:59:01 +0000 Subject: [PATCH 066/144] Move tutorial into a chapter of the docs Converted the tutorial files from Python scripts to Sphinx docs and made them a chapter of the overall documentation. Some of the examples were pretty outdated and did not even run any more on the PostgreSQL versions supported by PyGreSQL; these have been modernized. Also added some explanations of some of the classic PyGreSQL methods. --- docs/contents/changelog.rst | 1 + docs/contents/general.rst | 2 +- docs/contents/index.rst | 5 +- docs/contents/pg/connection.rst | 2 + docs/contents/pg/db_wrapper.rst | 2 + docs/contents/pg/large_objects.rst | 2 + docs/contents/pg/module.rst | 4 +- docs/contents/pg/query.rst | 2 + docs/contents/pgdb/connection.rst | 2 + docs/contents/pgdb/cursor.rst | 2 + docs/contents/pgdb/module.rst | 4 +- docs/contents/pgdb/types.rst | 2 + docs/contents/postgres/advanced.rst | 152 ++++++++++++ docs/contents/postgres/basic.rst | 359 ++++++++++++++++++++++++++++ docs/contents/postgres/func.rst | 160 +++++++++++++ docs/contents/postgres/index.rst | 17 ++ docs/contents/postgres/syscat.rst | 132 ++++++++++ docs/download/files.rst | 6 - mktar | 4 - tutorial/advanced.py | 198 --------------- tutorial/basics.py | 296 ----------------------- tutorial/func.py | 205 ---------------- tutorial/syscat.py | 149 ------------ 23 files changed, 845 insertions(+), 863 deletions(-) create mode 100644 docs/contents/postgres/advanced.rst create mode 100644 docs/contents/postgres/basic.rst create mode 100644 docs/contents/postgres/func.rst create mode 100644 docs/contents/postgres/index.rst create mode 100644 docs/contents/postgres/syscat.rst delete mode 100755 tutorial/advanced.py delete mode 100755 tutorial/basics.py delete mode 100755 tutorial/func.py delete mode 100755 tutorial/syscat.py diff --git a/docs/contents/changelog.rst b/docs/contents/changelog.rst index 95e350e4..527f0968 100644 --- a/docs/contents/changelog.rst +++ b/docs/contents/changelog.rst @@ -12,6 +12,7 @@ Version 4.2 - Add option to return money values as string. - Fix notification handler (Thanks Patrick TJ McPhee). - Fix a small issue with large objects. +- The tutorial files have become a chapter in the documentation. - Greatly improve unit testing, tests run with Python 2.4 to 2.7 again. Version 4.1.1 (2013-01-08) diff --git a/docs/contents/general.rst b/docs/contents/general.rst index 7ac2108e..aced233d 100644 --- a/docs/contents/general.rst +++ b/docs/contents/general.rst @@ -1,4 +1,4 @@ -General PyGreSQL programming information +General PyGreSQL Programming Information ---------------------------------------- PyGreSQL consists of two parts: the "classic" PyGreSQL interface diff --git a/docs/contents/index.rst b/docs/contents/index.rst index 3c381538..0857882a 100644 --- a/docs/contents/index.rst +++ b/docs/contents/index.rst @@ -8,10 +8,11 @@ Contents :maxdepth: 1 Installing PyGreSQL - What's new and history of changes - General PyGreSQL programming information + What's New and History of Changes + General PyGreSQL Programming Information The Classic PyGreSQL Interface The DB-API Compliant Interface + A PostgreSQL Primer Examples for using PyGreSQL Indices and tables diff --git a/docs/contents/pg/connection.rst b/docs/contents/pg/connection.rst index 39ccc4cd..46c6e8c4 100644 --- a/docs/contents/pg/connection.rst +++ b/docs/contents/pg/connection.rst @@ -1,6 +1,8 @@ pgobject -- The connection object ================================= +.. py:currentmodule:: pg + .. class:: pgobject This object handles a connection to a PostgreSQL database. It embeds and diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index 904e40ee..fed4eb32 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -1,6 +1,8 @@ The DB wrapper class ==================== +.. py:currentmodule:: pg + .. class:: DB The :class:`pgobject` methods are wrapped in the class :class:`DB`. diff --git a/docs/contents/pg/large_objects.rst b/docs/contents/pg/large_objects.rst index b0bd013d..2150606f 100644 --- a/docs/contents/pg/large_objects.rst +++ b/docs/contents/pg/large_objects.rst @@ -1,6 +1,8 @@ pglarge -- Large Objects ======================== +.. py:currentmodule:: pg + .. class:: pglarge Objects that are instances of the class :class:`pglarge` are used to handle diff --git a/docs/contents/pg/module.rst b/docs/contents/pg/module.rst index 02ff35f8..83deb878 100644 --- a/docs/contents/pg/module.rst +++ b/docs/contents/pg/module.rst @@ -1,6 +1,8 @@ Module functions and constants ============================== +.. py:currentmodule:: pg + The :mod:`pg` module defines a few functions that allow to connect to a database and to define "default variables" that override the environment variables used by PostgreSQL. @@ -18,7 +20,7 @@ standard environment variables should be used. connect -- Open a PostgreSQL connection --------------------------------------- -.. function:: pg.connect([dbname], [host], [port], [opt], [tty], [user], [passwd]) +.. function:: connect([dbname], [host], [port], [opt], [tty], [user], [passwd]) Open a :mod:`pg` connection diff --git a/docs/contents/pg/query.rst b/docs/contents/pg/query.rst index b1582e14..10423a29 100644 --- a/docs/contents/pg/query.rst +++ b/docs/contents/pg/query.rst @@ -1,6 +1,8 @@ pgqueryobject methods ===================== +.. py:currentmodule:: pg + .. class:: pgqueryobject The :class:`pgqueryobject` returned by :meth:`pgobject.query` and diff --git a/docs/contents/pgdb/connection.rst b/docs/contents/pgdb/connection.rst index 579c78c1..de0bdafa 100644 --- a/docs/contents/pgdb/connection.rst +++ b/docs/contents/pgdb/connection.rst @@ -1,6 +1,8 @@ pgdbCnx -- The connection object ================================ +.. py:currentmodule:: pgdb + .. class:: pgdbCnx These connection objects respond to the following methods. diff --git a/docs/contents/pgdb/cursor.rst b/docs/contents/pgdb/cursor.rst index 414e1f83..835528b2 100644 --- a/docs/contents/pgdb/cursor.rst +++ b/docs/contents/pgdb/cursor.rst @@ -1,6 +1,8 @@ pgdbCursor -- The cursor object =============================== +.. py:currentmodule:: pgdb + .. class:: pgdbCursor These objects represent a database cursor, which is used to manage the context diff --git a/docs/contents/pgdb/module.rst b/docs/contents/pgdb/module.rst index 415d56b5..b0891ac6 100644 --- a/docs/contents/pgdb/module.rst +++ b/docs/contents/pgdb/module.rst @@ -1,6 +1,8 @@ Module functions and constants ============================== +.. py:currentmodule:: pgdb + The :mod:`pgdb` module defines a :func:`connect` function that allows to connect to a database, some global constants describing the capabilities of the module as well as several exception classes. @@ -8,7 +10,7 @@ of the module as well as several exception classes. connect -- Open a PostgreSQL connection --------------------------------------- -.. function:: pgdb.connect([dsn], [user], [password], [host], [database]) +.. function:: connect([dsn], [user], [password], [host], [database]) Return a new connection to the database diff --git a/docs/contents/pgdb/types.rst b/docs/contents/pgdb/types.rst index e7cd5062..ed03c175 100644 --- a/docs/contents/pgdb/types.rst +++ b/docs/contents/pgdb/types.rst @@ -1,6 +1,8 @@ pgdbType -- Type objects and constructors ========================================= +.. py:currentmodule:: pgdb + .. class:: pgdbType The :attr:`pgdbCursor.description` attribute returns information about each diff --git a/docs/contents/postgres/advanced.rst b/docs/contents/postgres/advanced.rst new file mode 100644 index 00000000..7fb25ac4 --- /dev/null +++ b/docs/contents/postgres/advanced.rst @@ -0,0 +1,152 @@ +Examples for advanced features +============================== + +In this section, we show how to use some advanced features of PostgreSQL +using the classic PyGreSQL interface. + +We assume that you have already created a connection to the PostgreSQL +database, as explained in the :doc:`basic`:: + + >>> from pg import DB + >>> db = DB() + >>> query = query + +Inheritance +----------- + +A table can inherit from zero or more tables. A query can reference either +all rows of a table or all rows of a table plus all of its descendants. + +For example, the capitals table inherits from cities table (it inherits +all data fields from cities):: + + >>> data = [('cities', [ + ... "'San Francisco', 7.24E+5, 63", + ... "'Las Vegas', 2.583E+5, 2174", + ... "'Mariposa', 1200, 1953"]), + ... ('capitals', [ + ... "'Sacramento',3.694E+5,30,'CA'", + ... "'Madison', 1.913E+5, 845, 'WI'"])] + +Now, let's populate the tables:: + + >>> data = ['cities', [ + ... "'San Francisco', 7.24E+5, 63" + ... "'Las Vegas', 2.583E+5, 2174" + ... "'Mariposa', 1200, 1953"], + ... 'capitals', [ + ... "'Sacramento',3.694E+5,30,'CA'", + ... "'Madison', 1.913E+5, 845, 'WI'"]] + >>> for table, rows in data: + ... for row in rows: + ... query("INSERT INTO %s VALUES (%s)" % (table, row)) + >>> print query("SELECT * FROM cities") + name |population|altitude + -------------+----------+-------- + San Francisco| 724000| 63 + Las Vegas | 258300| 2174 + Mariposa | 1200| 1953 + Sacramento | 369400| 30 + Madison | 191300| 845 + (5 rows) + >>> print query("SELECT * FROM capitals") + name |population|altitude|state + ----------+----------+--------+----- + Sacramento| 369400| 30|CA + Madison | 191300| 845|WI + (2 rows) + +You can find all cities, including capitals, that are located at an altitude +of 500 feet or higher by:: + + >>> print query("""SELECT c.name, c.altitude + ... FROM cities + ... WHERE altitude > 500""") + name |altitude + ---------+-------- + Las Vegas| 2174 + Mariposa | 1953 + Madison | 845 + (3 rows) + +On the other hand, the following query references rows of the base table only, +i.e. it finds all cities that are not state capitals and are situated at an +altitude of 500 feet or higher:: + + >>> print query("""SELECT name, altitude + ... FROM ONLY cities + ... WHERE altitude > 500""") + name |altitude + ---------+-------- + Las Vegas| 2174 + Mariposa | 1953 + (2 rows) + +Arrays +------ + +Attributes can be arrays of base types or user-defined types:: + + >>> query("""CREATE TABLE sal_emp ( + ... name text, + ... pay_by_quarter int4[], + ... pay_by_extra_quarter int8[], + ... schedule text[][])""") + + +Insert instances with array attributes. Note the use of braces:: + + >>> query("""INSERT INTO sal_emp VALUES ( + ... 'Bill', '{10000,10000,10000,10000}', + ... '{9223372036854775800,9223372036854775800,9223372036854775800}', + ... '{{"meeting", "lunch"}, {"training", "presentation"}}')""") + >>> query("""INSERT INTO sal_emp VALUES ( + ... 'Carol', '{20000,25000,25000,25000}', + ... '{9223372036854775807,9223372036854775807,9223372036854775807}', + ... '{{"breakfast", "consulting"}, {"meeting", "lunch"}}')""") + + +Queries on array attributes:: + + >>> query("""SELECT name FROM sal_emp WHERE + ... sal_emp.pay_by_quarter[1] != sal_emp.pay_by_quarter[2]""") + name + ----- + Carol + (1 row) + +Retrieve third quarter pay of all employees:: + + >>> query("SELECT sal_emp.pay_by_quarter[3] FROM sal_emp") + pay_by_quarter + -------------- + 10000 + 25000 + (2 rows) + +Retrieve third quarter extra pay of all employees:: + + >>> query("SELECT sal_emp.pay_by_extra_quarter[3] FROM sal_emp") + pay_by_extra_quarter + -------------------- + 9223372036854775800 + 9223372036854775807 + (2 rows) + +Retrieve first two quarters of extra quarter pay of all employees:: + + >>> query("SELECT sal_emp.pay_by_extra_quarter[1:2] FROM sal_emp") + pay_by_extra_quarter + ----------------------------------------- + {9223372036854775800,9223372036854775800} + {9223372036854775807,9223372036854775807} + (2 rows) + +Select subarrays:: + + >>> query("""SELECT sal_emp.schedule[1:2][1:1] FROM sal_emp + ... WHERE sal_emp.name = 'Bill'""") + schedule + ---------------------- + {{meeting},{training}} + (1 row) diff --git a/docs/contents/postgres/basic.rst b/docs/contents/postgres/basic.rst new file mode 100644 index 00000000..c7a50fc3 --- /dev/null +++ b/docs/contents/postgres/basic.rst @@ -0,0 +1,359 @@ +Basic examples +============== + +In this section, we demonstrate how to use some of the very basic features +of PostgreSQL using the classic PyGreSQL interface. + +Creating a connection to the database +------------------------------------- + +We start by creating a **connection** to the PostgreSQL database:: + + >>> from pg import DB + >>> db = DB() + +If you pass no parameters when creating the :class:`DB` instance, then +PyGreSQL will try to connect to the database on the local host that has +the same name as the current user, and also use that name for login. + +You can also pass the database name, host, port and login information +as parameters when creating the :class:`DB` instance:: + + >>> db = DB(dbname='testdb', host='pgserver', port=5432, + ... user='fred', passwd='tiger') + +The :class:`DB` class of which ``db`` is an object is a wrapper around +the lower level :class:`pgobject` class of the :mod:`pg` module. +The most important method of such connection objects is the ``query`` +method that allows you to send SQL commands to the database. + +Creating tables +--------------- + +The first thing you would want to do in an empty database is creating a +table. To do this, you need to send a **CREATE TABLE** command to the +database. PostgreSQL has its own set of built-in types that can be used +for the table columns. Let us create two tables "weather" and "cities":: + + >>> db.query("""CREATE TABLE weather ( + ... city varchar(80), + ... temp_lo int, temp_hi int, + ... prcp float8, + ... date date)""") + >>> db.query("""CREATE TABLE cities ( + ... name varchar(80), + ... location point)""") + +.. note:: + Keywords are case-insensitive but identifiers are case-sensitive. + +You can get a list of all tables in the database with:: + + >>> db.get_tables() + ['public.cities', 'public.weather'] + + +Insert data +----------- + +Now we want to fill our tables with data. An **INSERT** statement is used +to insert a new row into a table. There are several ways you can specify +what columns the data should go to. + +Let us insert a row into each of these tables. Tthe simplest case is when +the list of values corresponds to the order of the columns specified in the +CREATE TABLE command:: + + >>> db.query("""INSERT INTO weather + ... VALUES ('San Francisco', 46, 50, 0.25, '11/27/1994')""") + >>> db.query("""INSERT INTO cities + ... VALUES ('San Francisco', '(-194.0, 53.0)')""") + +You can also specify what column the values correspond to. The columns can +be specified in any order. You may also omit any number of columns, +unknown precipitation below:: + + >>> db.query("""INSERT INTO weather (date, city, temp_hi, temp_lo) + ... VALUES ('11/29/1994', 'Hayward', 54, 37)""") + + +If you get errors regarding the format of the date values, your database +is probably set to a different date style. In this case you must change +the date style like this:: + + >>> db.query("set datestyle = MDY") + +Instead of explicitly writing the INSERT statement and sending it to the +database with the :meth:`DB.query` method, you can also use the more +convenient :meth:`DB.insert` method that does the same under the hood:: + + >>> db.insert('weather', + ... date='11/29/1994', city='Hayward', temp_hi=54, temp_lo=37) + +And instead of using keyword parameters, you can also pass the values +to the :meth:`DB.insert` method in a single Python dictionary. + +If you have a Python list with many rows that shall be used to fill +a database table quickly, you can use the :meth:`DB.inserttable` method. + +Retrieving data +--------------- + +After having entered some data into our tables, let's see how we can get +the data out again. A **SELECT** statement is used for retrieving data. +The basic syntax is: + +.. code-block:: psql + + SELECT columns FROM tables WHERE predicates + +A simple one would be the following query:: + + >>> q = db.query("SELECT * FROM weather") + >>> print q + city |temp_lo|temp_hi|prcp| date + -------------+-------+-------+----+---------- + San Francisco| 46| 50|0.25|1994-11-27 + Hayward | 37| 54| |1994-11-29 + (2 rows) + +You may also specify expressions in the target list. +(The 'AS column' specifies the column name of the result. It is optional.) + +:: + + >>> print db.query("""SELECT city, (temp_hi+temp_lo)/2 AS temp_avg, date + ... FROM weather""") + city |temp_avg| date + -------------+--------+---------- + San Francisco| 48|1994-11-27 + Hayward | 45|1994-11-29 + (2 rows) + +If you want to retrieve rows that satisfy certain condition (i.e. a +restriction), specify the condition in a WHERE clause. The following +retrieves the weather of San Francisco on rainy days:: + + >>> print db.query("""SELECT * FROM weather + ... WHERE city = 'San Francisco' AND prcp > 0.0""") + city |temp_lo|temp_hi|prcp| date + -------------+-------+-------+----+---------- + San Francisco| 46| 50|0.25|1994-11-27 + (1 row) + +Here is a more complicated one. Duplicates are removed when DISTINCT is +specified. ORDER BY specifies the column to sort on. (Just to make sure the +following won't confuse you, DISTINCT and ORDER BY can be used separately.) + +:: + + >>> print db.query("SELECT DISTINCT city FROM weather ORDER BY city") + city + ------------- + Hayward + San Francisco + (2 rows) + +So far we have only printed the output of a SELECT query. The object that +is returned by the query is an instance of the :class:`pgqueryobject` class +that can print itself in the nicely formatted way we saw above. But you can +also retrieve the results as a list of tuples, by using the +:meth:`pgqueryobject.getresult` method:: + + >>> from pprint import pprint + >>> q = db.query("SELECT * FROM weather") + >>> pprint(q.getresult()) + [('San Francisco', 46, 50, 0.25, '1994-11-27'), + ('Hayward', 37, 54, None, '1994-11-29')] + +Here we used pprint to print out the returned list in a nicely formatted way. + +If you want to retrieve the results as a list of dictionaries instead of +tuples, use the :meth:`pgqueryobject.dictresult` method instead:: + + >>> pprint(q.dictresult()) + [{'city': 'San Francisco', + 'date': '1994-11-27', + 'prcp': 0.25, + 'temp_hi': 50, + 'temp_lo': 46}, + {'city': 'Hayward', + 'date': '1994-11-29', + 'prcp': None, + 'temp_hi': 54, + 'temp_lo': 37}] + +Finally, in Python 2.5 and above you can also retrieve the results as a list +of named tuples, using the :meth:`pgqueryobject.namedresult` method. +This can be a good compromise between simple tuples and the more memory +intensive dictionaries: + + >>> for row in q.namedresult(): + ... print row.city, row.date + ... + San Francisco 1994-11-27 + Hayward 1994-11-29 + +If you only want to retrieve a single row of data, you can use the more +convenient :meth:`DB.get` method that does the same under the hood:: + + >>> d = dict(city='Hayward') + >>> db.get('weather', d, 'city') + >>> pprint(d) + {'city': 'Hayward', + 'date': '1994-11-29', + 'prcp': None, + 'temp_hi': 54, + 'temp_lo': 37} + +As you see, the :meth:`DB.get` method returns a dictionary with the column +names as keys. In the third parameter you can specify which column should +be looked up in the WHERE statement of the SELECT statement that is executed +by the :meth:`DB.get` method. You normally don't need it when the table was +created with a primary key. + +Retrieving data into other tables +--------------------------------- + +A SELECT ... INTO statement can be used to retrieve data into another table:: + + >>> db.query("""SELECT * INTO TEMPORARY TABLE temptab FROM weather + ... WHERE city = 'San Francisco' and prcp > 0.0""") + +This fills a temporary table "temptab" with a subset of the data in the +original "weather" table. It can be listed with:: + + >>> print db.query("SELECT * from temptab") + city |temp_lo|temp_hi|prcp| date + -------------+-------+-------+----+---------- + San Francisco| 46| 50|0.25|1994-11-27 + (1 row) + +Aggregates +---------- + +Let's try the following query:: + + >>> print db.query("SELECT max(temp_lo) FROM weather") + max + --- + 46 + (1 row) + +You can also use aggregates with the GROUP BY clause:: + + >>> print db.query("SELECT city, max(temp_lo) FROM weather GROUP BY city") + city |max + -------------+--- + Hayward | 37 + San Francisco| 46 + (2 rows) + +Joining tables +-------------- + +Queries can access multiple tables at once or access the same table in such a +way that multiple instances of the table are being processed at the same time. + +Suppose we want to find all the records that are in the temperature range of +other records. W1 and W2 are aliases for weather. We can use the following +query to achieve that:: + + >>> print db.query("""SELECT W1.city, W1.temp_lo, W1.temp_hi, + ... W2.city, W2.temp_lo, W2.temp_hi FROM weather W1, weather W2 + ... WHERE W1.temp_lo < W2.temp_lo and W1.temp_hi > W2.temp_hi""") + city |temp_lo|temp_hi| city |temp_lo|temp_hi + -------+-------+-------+-------------+-------+------- + Hayward| 37| 54|San Francisco| 46| 50 + (1 row) + +Now let's join two tables. The following joins the "weather" table and the +"cities" table:: + + >>> print db.query("""SELECT city, location, prcp, date FROM weather, cities + ... WHERE name = city""") + city |location |prcp| date + -------------+---------+----+---------- + San Francisco|(-194,53)|0.25|1994-11-27 + (1 row) + +Since the column names are all different, we don't have to specify the table +name. If you want to be clear, you can do the following. They give identical +results, of course:: + + >>> print db.query("""SELECT w.city, c.location, w.prcp, w.date + ... FROM weather w, cities c WHERE c.name = w.city""") + city |location |prcp| date + -------------+---------+----+---------- + San Francisco|(-194,53)|0.25|1994-11-27 + (1 row) + +Updating data +------------- + +It you want to change the data that has already been inserted into a database +table, you will need the **UPDATE** statement. + +Suppose you discover the temperature readings are all off by 2 degrees as of +Nov 28, you may update the data as follow:: + + >>> db.query("""UPDATE weather + ... SET temp_hi = temp_hi - 2, temp_lo = temp_lo - 2 + ... WHERE date > '11/28/1994'""") + '1' + >>> print db.query("SELECT * from weather") + city |temp_lo|temp_hi|prcp| date + -------------+-------+-------+----+---------- + San Francisco| 46| 50|0.25|1994-11-27 + Hayward | 35| 52| |1994-11-29 + (2 rows) + +Note that the UPDATE statement returned the string ``'1'``, indicating that +exactly one row of data has been affected by the update. + +If you retrieved one row of data as a dictionary using the :meth:`DB.get` +method, then you can also update that row with the :meth:`DB.update` method. + +Deleting data +------------- + +To delete rows from a table, a **DELETE** statement can be used. + +Suppose you are no longer interested in the weather of Hayward, you can do +the following to delete those rows from the table:: + + >>> db.query("DELETE FROM weather WHERE city = 'Hayward'") + '1' + +Again, you get the string ``'1'`` as return value, indicating that exactly +one row of data has been deleted. + +You can also delete all the rows in a table by doing the following. +This is different from DROP TABLE which removes the table itself in addition +to the removing the rows, as explained in the next section. + +:: + + >>> db.query("DELETE FROM weather") + '1' + >>> print db.query("SELECT * from weather") + city|temp_lo|temp_hi|prcp|date + ----+-------+-------+----+---- + (0 rows) + +Since only one row was left in the table, the DELETE query again returns the +string ``'1'``. The SELECT query now gives an empty result. + +If you retrieved a row of data as a dictionary using the :meth:`DB.get` +method, then you can also delete that row with the :meth:`DB.delete` method. + + +Removing the tables +------------------- +The **DROP TABLE** command is used to remove tables. After you have done this, +you can no longer use those tables:: + + >>> db.query("DROP TABLE weather, cities") + >>> db.query("select * from weather") + pg.ProgrammingError: Error: Relation "weather" does not exist + diff --git a/docs/contents/postgres/func.rst b/docs/contents/postgres/func.rst new file mode 100644 index 00000000..7ad3dcc5 --- /dev/null +++ b/docs/contents/postgres/func.rst @@ -0,0 +1,160 @@ +Examples for using SQL functions +================================ + +We assume that you have already created a connection to the PostgreSQL +database, as explained in the :doc:`basic`:: + + >>> from pg import DB + >>> db = DB() + >>> query = db.query + +Creating SQL Functions on Base Types +------------------------------------ + +A **CREATE FUNCTION** statement lets you create a new function that can be +used in expressions (in SELECT, INSERT, etc.). We will start with functions +that return values of base types. + +Let's create a simple SQL function that takes no arguments and returns 1:: + + >>> query("""CREATE FUNCTION one() RETURNS int4 + ... AS 'SELECT 1 as ONE' LANGUAGE SQL""") + +Functions can be used in any expressions (eg. in the target"list or +qualifications):: + + >>> print db.query("SELECT one() AS answer") + answer + ------ + 1 + (1 row) + + +Here's how you create a function that takes arguments. The following function +returns the sum of its two arguments:: + + >>> query("""CREATE FUNCTION add_em(int4, int4) RETURNS int4 + ... AS $$ SELECT $1 + $2 $$ LANGUAGE SQL""") + >>> print query("SELECT add_em(1, 2) AS answer") + answer + ------ + 3 + (1 row) + + +Creating SQL Functions on Composite Types +----------------------------------------- + +It is also possible to create functions that return values of composite types. + +Before we create more sophisticated functions, let's populate an EMP table:: + + >>> query("""CREATE TABLE EMP ( + ... name text, + ... salary int4, + ... age f int4, + ... dept varchar(16))""") + >>> emps = ["'Sam', 1200, 16, 'toy'", + ... "'Claire', 5000, 32, 'shoe'", + ... "'Andy', -1000, 2, 'candy'", + ... "'Bill', 4200, 36, 'shoe'", + ... "'Ginger', 4800, 30, 'candy'"] + >>> for emp in emps: + ... query("INSERT INTO EMP VALUES (%s)" % emp) + +Every INSERT statement will return a '1' indicating that it has inserted +one row into the EMP table. + +The argument of a function can also be a tuple. For instance, *double_salary* +takes a tuple of the EMP table:: + + >>> query("""CREATE FUNCTION double_salary(EMP) RETURNS int4 + ... AS $$ SELECT $1.salary * 2 AS salary $$ LANGUAGE SQL""") + >>> print query("""SELECT name, double_salary(EMP) AS dream + ... FROM EMP WHERE EMP.dept = 'toy'""") + name|dream + ----+----- + Sam | 2400 + (1 row) + +The return value of a function can also be a tuple. However, make sure that the +expressions in the target list are in the same order as the columns of EMP:: + + >>> query("""CREATE FUNCTION new_emp() RETURNS EMP AS $$ + ... SELECT 'None'::text AS name, + ... 1000 AS salary, + ... 25 AS age, + ... 'None'::varchar(16) AS dept + ... $$ LANGUAGE SQL""") + +You can then project a column out of resulting the tuple by using the +"function notation" for projection columns (i.e. ``bar(foo)`` is equivalent +to ``foo.bar``). Note that ``new_emp().name`` isn't supported:: + + >>> print query("SELECT name(new_emp()) AS nobody") + nobody + ------ + None + (1 row) + +Let's try one more function that returns tuples:: + + >>> query("""CREATE FUNCTION high_pay() RETURNS setof EMP + ... AS 'SELECT * FROM EMP where salary > 1500' + ... LANGUAGE SQL""") + >>> query("SELECT name(high_pay()) AS overpaid") + overpaid + -------- + Claire + Bill + Ginger + (3 rows) + + +Creating SQL Functions with multiple SQL statements +--------------------------------------------------- + +You can also create functions that do more than just a SELECT. + +You may have noticed that Andy has a negative salary. We'll create a function +that removes employees with negative salaries:: + + >>> query("SELECT * FROM EMP") + name |salary|age|dept + ------+------+---+----- + Sam | 1200| 16|toy + Claire| 5000| 32|shoe + Andy | -1000| 2|candy + Bill | 4200| 36|shoe + Ginger| 4800| 30|candy + (5 rows) + >>> query("""CREATE FUNCTION clean_EMP () RETURNS int4 AS + ... 'DELETE FROM EMP WHERE EMP.salary <= 0; + ... SELECT 1 AS ignore_this' + ... LANGUAGE SQL""") + >>> query("SELECT clean_EMP()") + clean_emp + --------- + 1 + (1 row) + >>> query("SELECT * FROM EMP") + name |salary|age|dept + ------+------+---+----- + Sam | 1200| 16|toy + Claire| 5000| 32|shoe + Bill | 4200| 36|shoe + Ginger| 4800| 30|candy + (4 rows) + +Remove functions that were created in this example +-------------------------------------------------- + +We can remove the functions that we have created in this example and the +table EMP, by using the DROP command:: + + query("DROP FUNCTION clean_EMP()") + query("DROP FUNCTION high_pay()") + query("DROP FUNCTION new_emp()") + query("DROP FUNCTION add_em(int4, int4)") + query("DROP FUNCTION one()") + query("DROP TABLE EMP CASCADE") diff --git a/docs/contents/postgres/index.rst b/docs/contents/postgres/index.rst new file mode 100644 index 00000000..409a7042 --- /dev/null +++ b/docs/contents/postgres/index.rst @@ -0,0 +1,17 @@ +------------------- +A PostgreSQL Primer +------------------- + +The examples in this chapter of the documentation have been taken +from the PostgreSQL manual. They demonstrate some PostgreSQL features +using the classic PyGreSQL interface. They can serve as an introduction +to PostgreSQL, but not so much as examples for the use of PyGreSQL. + +Contents +======== + +.. toctree:: + basic + advanced + func + syscat diff --git a/docs/contents/postgres/syscat.rst b/docs/contents/postgres/syscat.rst new file mode 100644 index 00000000..95ba95ae --- /dev/null +++ b/docs/contents/postgres/syscat.rst @@ -0,0 +1,132 @@ +Examples for using the system catalogs +====================================== + +The system catalogs are regular tables where PostgreSQL stores schema metadata, +such as information about tables and columns, and internal bookkeeping +information. You can drop and recreate the tables, add columns, insert and +update values, and severely mess up your system that way. Normally, one +should not change the system catalogs by hand, there are always SQL commands +to do that. For example, CREATE DATABASE inserts a row into the *pg_database* +catalog — and actually creates the database on disk. + +It this section we want to show examples for how to parse some of the system +catalogs, making queries with the classic PyGreSQL interface. + +We assume that you have already created a connection to the PostgreSQL +database, as explained in the :doc:`basic`:: + + >>> from pg import DB + >>> db = DB() + >>> query = query + +Lists indices +------------- + +This query lists all simple indices in the database:: + + print query("""SELECT bc.relname AS class_name, + ic.relname AS index_name, a.attname + FROM pg_class bc, pg_class ic, pg_index i, pg_attribute a + WHERE i.indrelid = bc.oid AND i.indexrelid = ic.oid + AND i.indkey[0] = a.attnum AND a.attrelid = bc.oid + AND NOT a.attisdropped + ORDER BY class_name, index_name, attname""") + + +List user defined attributes +---------------------------- + +This query lists all user defined attributes and their type +in user-defined classes:: + + print query("""SELECT c.relname, a.attname, t.typname + FROM pg_class c, pg_attribute a, pg_type t + WHERE c.relkind = 'r' and c.relname !~ '^pg_' + AND c.relname !~ '^Inv' and a.attnum > 0 + AND a.attrelid = c.oid and a.atttypid = t.oid + AND NOT a.attisdropped + ORDER BY relname, attname""") + +List user defined base types +---------------------------- + +This query lists all user defined base types:: + + print query("""SELECT r.rolname, t.typname + FROM pg_type t, pg_authid r + WHERE r.oid = t.typowner + AND t.typrelid = '0'::oid and t.typelem = '0'::oid + AND r.rolname != 'postgres' + ORDER BY rolname, typname""") + + +List operators +--------------- + +This query lists all right-unary operators:: + + print query("""SELECT o.oprname AS right_unary, + lt.typname AS operand, result.typname AS return_type + FROM pg_operator o, pg_type lt, pg_type result + WHERE o.oprkind='r' and o.oprleft = lt.oid + AND o.oprresult = result.oid + ORDER BY operand""") + + +This query lists all left-unary operators:: + + print query("""SELECT o.oprname AS left_unary, + rt.typname AS operand, result.typname AS return_type + FROM pg_operator o, pg_type rt, pg_type result + WHERE o.oprkind='l' AND o.oprright = rt.oid + AND o.oprresult = result.oid + ORDER BY operand""") + + +And this one lists all of the binary operators:: + + print query("""SELECT o.oprname AS binary_op, + rt.typname AS right_opr, lt.typname AS left_opr, + result.typname AS return_type + FROM pg_operator o, pg_type rt, pg_type lt, pg_type result + WHERE o.oprkind = 'b' AND o.oprright = rt.oid + AND o.oprleft = lt.oid AND o.oprresult = result.oid""") + + +List functions of a language +---------------------------- + +Given a programming language, this query returns the name, args and return +type from all functions of a language:: + + language = 'sql' + print query("""SELECT p.proname, p.pronargs, t.typname + FROM pg_proc p, pg_language l, pg_type t + WHERE p.prolang = l.oid AND p.prorettype = t.oid + AND l.lanname = $1 + ORDER BY proname""", (language,)) + + +List aggregate functions +------------------------ + +This query lists all of the aggregate functions and the type to which +they can be applied:: + + print query("""SELECT p.proname, t.typname + FROM pg_aggregate a, pg_proc p, pg_type t + WHERE a.aggfnoid = p.oid + and p.proargtypes[0] = t.oid + ORDER BY proname, typname""") + +List operator families +---------------------- + +The following query lists all defined operator families and all the operators +included in each family:: + + print query("""SELECT am.amname, opf.opfname, amop.amopopr::regoperator + FROM pg_am am, pg_opfamily opf, pg_amop amop + WHERE opf.opfmethod = am.oid + AND amop.amopfamily = opf.oid + ORDER BY amname, opfname, amopopr""") diff --git a/docs/download/files.rst b/docs/download/files.rst index 2723ed46..2ebcff38 100644 --- a/docs/download/files.rst +++ b/docs/download/files.rst @@ -24,10 +24,4 @@ docs/ documentation directory tests/ a suite of unit tests for PyGreSQL -tutorial/ demos directory - - The samples contained in this directory have been taken - from the PostgreSQL manual and were used for module testing. - They demonstrate some PostgreSQL features. - ========== = diff --git a/mktar b/mktar index 467a3e68..e210e82b 100755 --- a/mktar +++ b/mktar @@ -40,7 +40,6 @@ DOCFILES="docs/Makefile docs/make.bat docs/*.rst docs/_static docs/_templates" HTMLFILES="docs/_build/html" TESTFILES="module/tests/*.py" -TUTFILES="tutorial/*.py" echo "Making source tarball..." @@ -50,12 +49,10 @@ rm -rf $TD mkdir $TD mkdir -p $TD/docs/_build/html mkdir $TD/tests -mkdir $TD/tutorial cp $MODFILES $TD cp -r $DOCFILES $TD/docs cp -r $HTMLFILES $TD/docs/_build cp $TESTFILES $TD/tests -cp $TUTFILES $TD/tutorial tar -cvzf $TF $TD chmod 644 $TF rm -rf $TD @@ -63,4 +60,3 @@ rm -f $DISTDIR/$SYMLINK ln -s $TD.tgz $DISTDIR/$SYMLINK echo "$TF has been built" - diff --git a/tutorial/advanced.py b/tutorial/advanced.py deleted file mode 100755 index 9bb7ffe1..00000000 --- a/tutorial/advanced.py +++ /dev/null @@ -1,198 +0,0 @@ -#! /usr/bin/python -# advanced.py - demo of advanced features of PostGres. Some may not be ANSI. -# inspired from the Postgres tutorial -# adapted to Python 1995 by Pascal Andre - -print """ -__________________________________________________________________ -MODULE ADVANCED.PY : ADVANCED POSTGRES SQL COMMANDS TUTORIAL - -This module is designed for being imported from python prompt - -In order to run the samples included here, first create a connection -using : cnx = advanced.DB(...) - -The "..." should be replaced with whatever arguments you need to open an -existing database. Usually all you need is the name of the database and, -in fact, if it is the same as your login name, you can leave it empty. - -then start the demo with: advanced.demo(cnx) -__________________________________________________________________ -""" - -from pg import DB -import sys - -# waits for a key -def wait_key(): - print "Press " - sys.stdin.read(1) - -# inheritance features -def inherit_demo(pgcnx): - print "-----------------------------" - print "-- Inheritance:" - print "-- a table can inherit from zero or more tables. A query" - print "-- can reference either all rows of a table or all rows " - print "-- of a table plus all of its descendants." - print "-----------------------------" - print - print "-- For example, the capitals table inherits from cities table." - print "-- (It inherits all data fields from cities.)" - print - print "CREATE TABLE cities (" - print " name text," - print " population float8," - print " altitude int" - print ")" - print - print "CREATE TABLE capitals (" - print " state varchar(2)" - print ") INHERITS (cities)" - pgcnx.query("""CREATE TABLE cities ( - name text, - population float8, - altitude int)""") - pgcnx.query("""CREATE TABLE capitals ( - state varchar(2)) INHERITS (cities)""") - wait_key() - print - print "-- now, let's populate the tables" - print - print "INSERT INTO cities VALUES ('San Francisco', 7.24E+5, 63)" - print "INSERT INTO cities VALUES ('Las Vegas', 2.583E+5, 2174)" - print "INSERT INTO cities VALUES ('Mariposa', 1200, 1953)" - print - print "INSERT INTO capitals VALUES ('Sacramento', 3.694E+5, 30, 'CA')" - print "INSERT INTO capitals VALUES ('Madison', 1.913E+5, 845, 'WI')" - print - pgcnx.query("INSERT INTO cities VALUES ('San Francisco', 7.24E+5, 63)") - pgcnx.query("INSERT INTO cities VALUES ('Las Vegas', 2.583E+5, 2174)") - pgcnx.query("INSERT INTO cities VALUES ('Mariposa', 1200, 1953)") - pgcnx.query("INSERT INTO capitals VALUES ('Sacramento',3.694E+5,30,'CA')") - pgcnx.query("INSERT INTO capitals VALUES ('Madison', 1.913E+5, 845, 'WI')") - print - print "SELECT * FROM cities" - print pgcnx.query("SELECT * FROM cities") - print "SELECT * FROM capitals" - print pgcnx.query("SELECT * FROM capitals") - print - print "-- like before, a regular query references rows of the base" - print "-- table only" - print - print "SELECT name, altitude" - print "FROM cities" - print "WHERE altitude > 500;" - print pgcnx.query("""SELECT name, altitude - FROM cities - WHERE altitude > 500""") - print - print "-- on the other hand, you can find all cities, including " - print "-- capitals, that are located at an altitude of 500 'ft " - print "-- or higher by:" - print - print "SELECT c.name, c.altitude" - print "FROM cities* c" - print "WHERE c.altitude > 500" - print pgcnx.query("""SELECT c.name, c.altitude - FROM cities* c - WHERE c.altitude > 500""") - -# arrays attributes -def array_demo(pgcnx): - print "----------------------" - print "-- Arrays:" - print "-- attributes can be arrays of base types or user-defined " - print "-- types" - print "----------------------" - print - print "CREATE TABLE sal_emp (" - print " name text," - print " pay_by_quarter int4[]," - print " pay_by_extra_quarter int8[]," - print " schedule text[][]" - print ")" - pgcnx.query("""CREATE TABLE sal_emp ( - name text, - pay_by_quarter int4[], - pay_by_extra_quarter int8[], - schedule text[][])""") - wait_key() - print - print "-- insert instances with array attributes. " - print " Note the use of braces" - print - print "INSERT INTO sal_emp VALUES (" - print " 'Bill'," - print " '{10000,10000,10000,10000}'," - print " '{9223372036854775800,9223372036854775800,9223372036854775800}'," - print " '{{\"meeting\", \"lunch\"}, {}}')" - print - print "INSERT INTO sal_emp VALUES (" - print " 'Carol'," - print " '{20000,25000,25000,25000}'," - print " '{9223372036854775807,9223372036854775807,9223372036854775807}'," - print " '{{\"talk\", \"consult\"}, {\"meeting\"}}')" - print - pgcnx.query("""INSERT INTO sal_emp VALUES ( - 'Bill', '{10000,10000,10000,10000}', - '{9223372036854775800,9223372036854775800,9223372036854775800}', - '{{\"meeting\", \"lunch\"}, {}}')""") - pgcnx.query("""INSERT INTO sal_emp VALUES ( - 'Carol', '{20000,25000,25000,25000}', - '{9223372036854775807,9223372036854775807,9223372036854775807}', - '{{\"talk\", \"consult\"}, {\"meeting\"}}')""") - wait_key() - print - print "----------------------" - print "-- queries on array attributes" - print "----------------------" - print - print "SELECT name FROM sal_emp WHERE" - print " sal_emp.pay_by_quarter[1] <> sal_emp.pay_by_quarter[2]" - print - print pgcnx.query("""SELECT name FROM sal_emp WHERE - sal_emp.pay_by_quarter[1] <> sal_emp.pay_by_quarter[2]""") - print - print pgcnx.query("""SELECT name FROM sal_emp WHERE - sal_emp.pay_by_extra_quarter[1] <> sal_emp.pay_by_extra_quarter[2]""") - print - print "-- retrieve third quarter pay of all employees" - print - print "SELECT sal_emp.pay_by_quarter[3] FROM sal_emp" - print - print pgcnx.query("SELECT sal_emp.pay_by_quarter[3] FROM sal_emp") - print - print "-- retrieve third quarter extra pay of all employees" - print - print "SELECT sal_emp.pay_by_extra_quarter[3] FROM sal_emp" - print pgcnx.query("SELECT sal_emp.pay_by_extra_quarter[3] FROM sal_emp") - print - print "-- retrieve first two quarters of extra quarter pay of all employees" - print - print "SELECT sal_emp.pay_by_extra_quarter[1:2] FROM sal_emp" - print - print pgcnx.query("SELECT sal_emp.pay_by_extra_quarter[1:2] FROM sal_emp") - print - print "-- select subarrays" - print - print "SELECT sal_emp.schedule[1:2][1:1] FROM sal_emp WHERE" - print " sal_emp.name = 'Bill'" - print pgcnx.query("SELECT sal_emp.schedule[1:2][1:1] FROM sal_emp WHERE " \ - "sal_emp.name = 'Bill'") - -# base cleanup -def demo_cleanup(pgcnx): - print "-- clean up (you must remove the children first)" - print "DROP TABLE sal_emp" - print "DROP TABLE capitals" - print "DROP TABLE cities;" - pgcnx.query("DROP TABLE sal_emp") - pgcnx.query("DROP TABLE capitals") - pgcnx.query("DROP TABLE cities") - -# main demo function -def demo(pgcnx): - inherit_demo(pgcnx) - array_demo(pgcnx) - demo_cleanup(pgcnx) diff --git a/tutorial/basics.py b/tutorial/basics.py deleted file mode 100755 index 60012cc6..00000000 --- a/tutorial/basics.py +++ /dev/null @@ -1,296 +0,0 @@ -#! /usr/bin/python -# basics.py - basic SQL commands tutorial -# inspired from the Postgres95 tutorial -# adapted to Python 1995 by Pascal ANDRE - -print """ -__________________________________________________________________ -MODULE BASICS.PY : BASIC POSTGRES SQL COMMANDS TUTORIAL - -This module is designed for being imported from python prompt - -In order to run the samples included here, first create a connection -using : cnx = basics.DB(...) - -The "..." should be replaced with whatever arguments you need to open an -existing database. Usually all you need is the name of the database and, -in fact, if it is the same as your login name, you can leave it empty. - -then start the demo with: basics.demo(cnx) -__________________________________________________________________ -""" - -from pg import DB -import sys - -# waits for a key -def wait_key(): - print "Press " - sys.stdin.read(1) - -# table creation commands -def create_table(pgcnx): - print "-----------------------------" - print "-- Creating a table:" - print "-- a CREATE TABLE is used to create base tables. POSTGRES" - print "-- SQL has its own set of built-in types. (Note that" - print "-- keywords are case-insensitive but identifiers are " - print "-- case-sensitive.)" - print "-----------------------------" - print - print "Sending query :" - print "CREATE TABLE weather (" - print " city varchar(80)," - print " temp_lo int," - print " temp_hi int," - print " prcp float8," - print " date date" - print ")" - pgcnx.query("""CREATE TABLE weather (city varchar(80), temp_lo int, - temp_hi int, prcp float8, date date)""") - print - print "Sending query :" - print "CREATE TABLE cities (" - print " name varchar(80)," - print " location point" - print ")" - pgcnx.query("""CREATE TABLE cities ( - name varchar(80), - location point)""") - -# data insertion commands -def insert_data(pgcnx): - print "-----------------------------" - print "-- Inserting data:" - print "-- an INSERT statement is used to insert a new row into" - print "-- a table. There are several ways you can specify what" - print "-- columns the data should go to." - print "-----------------------------" - print - print "-- 1. the simplest case is when the list of value correspond to" - print "-- the order of the columns specified in CREATE TABLE." - print - print "Sending query :" - print "INSERT INTO weather " - print " VALUES ('San Francisco', 46, 50, 0.25, '11/27/1994')" - pgcnx.query("""INSERT INTO weather - VALUES ('San Francisco', 46, 50, 0.25, '11/27/1994')""") - print - print "Sending query :" - print "INSERT INTO cities " - print " VALUES ('San Francisco', '(-194.0, 53.0)')" - pgcnx.query("""INSERT INTO cities - VALUES ('San Francisco', '(-194.0, 53.0)')""") - print - wait_key() - print "-- 2. you can also specify what column the values correspond " - print " to. (The columns can be specified in any order. You may " - print " also omit any number of columns. eg. unknown precipitation" - print " below)" - print "Sending query :" - print "INSERT INTO weather (city, temp_lo, temp_hi, prcp, date)" - print " VALUES ('San Francisco', 43, 57, 0.0, '11/29/1994')" - pgcnx.query("INSERT INTO weather (date, city, temp_hi, temp_lo)" \ - "VALUES ('11/29/1994', 'Hayward', 54, 37)") - -# direct selection commands -def select_data1(pgcnx): - print "-----------------------------" - print "-- Retrieving data:" - print "-- a SELECT statement is used for retrieving data. The " - print "-- basic syntax is:" - print "-- SELECT columns FROM tables WHERE predicates" - print "-----------------------------" - print - print "-- a simple one would be the query:" - print "SELECT * FROM weather" - print - print "The result is :" - q = pgcnx.query("SELECT * FROM weather") - print q - print - print "-- you may also specify expressions in the target list (the " - print "-- 'AS column' specifies the column name of the result. It is " - print "-- optional.)" - print "The query :" - print " SELECT city, (temp_hi+temp_lo)/2 AS temp_avg, date " - print " FROM weather" - print "Gives :" - print pgcnx.query("""SELECT city, (temp_hi+temp_lo)/2 - AS temp_avg, date FROM weather""") - print - print "-- if you want to retrieve rows that satisfy certain condition" - print "-- (ie. a restriction), specify the condition in WHERE. The " - print "-- following retrieves the weather of San Francisco on rainy " - print "-- days." - print "SELECT *" - print "FROM weather" - print "WHERE city = 'San Francisco' " - print " and prcp > 0.0" - print pgcnx.query("""SELECT * FROM weather WHERE city = 'San Francisco' - AND prcp > 0.0""") - print - print "-- here is a more complicated one. Duplicates are removed when " - print "-- DISTINCT is specified. ORDER BY specifies the column to sort" - print "-- on. (Just to make sure the following won't confuse you, " - print "-- DISTINCT and ORDER BY can be used separately.)" - print "SELECT DISTINCT city" - print "FROM weather" - print "ORDER BY city;" - print pgcnx.query("SELECT DISTINCT city FROM weather ORDER BY city") - -# selection to a temporary table -def select_data2(pgcnx): - print "-----------------------------" - print "-- Retrieving data into other classes:" - print "-- a SELECT ... INTO statement can be used to retrieve " - print "-- data into another class." - print "-----------------------------" - print - print "The query :" - print "SELECT * INTO TABLE temptab " - print "FROM weather" - print "WHERE city = 'San Francisco' " - print " and prcp > 0.0" - pgcnx.query("""SELECT * INTO TABLE temptab FROM weather - WHERE city = 'San Francisco' and prcp > 0.0""") - print "Fills the table temptab, that can be listed with :" - print "SELECT * from temptab" - print pgcnx.query("SELECT * from temptab") - -# aggregate creation commands -def create_aggregate(pgcnx): - print "-----------------------------" - print "-- Aggregates" - print "-----------------------------" - print - print "Let's consider the query :" - print "SELECT max(temp_lo)" - print "FROM weather;" - print pgcnx.query("SELECT max(temp_lo) FROM weather") - print - print "-- Aggregate with GROUP BY" - print "SELECT city, max(temp_lo)" - print "FROM weather " - print "GROUP BY city;" - print pgcnx.query( """SELECT city, max(temp_lo) - FROM weather GROUP BY city""") - -# table join commands -def join_table(pgcnx): - print "-----------------------------" - print "-- Joining tables:" - print "-- queries can access multiple tables at once or access" - print "-- the same table in such a way that multiple instances" - print "-- of the table are being processed at the same time." - print "-----------------------------" - print - print "-- suppose we want to find all the records that are in the " - print "-- temperature range of other records. W1 and W2 are aliases " - print "--for weather." - print - print "SELECT W1.city, W1.temp_lo, W1.temp_hi, " - print " W2.city, W2.temp_lo, W2.temp_hi" - print "FROM weather W1, weather W2" - print "WHERE W1.temp_lo < W2.temp_lo " - print " and W1.temp_hi > W2.temp_hi" - print - print pgcnx.query("""SELECT W1.city, W1.temp_lo, W1.temp_hi, - W2.city, W2.temp_lo, W2.temp_hi FROM weather W1, weather W2 - WHERE W1.temp_lo < W2.temp_lo and W1.temp_hi > W2.temp_hi""") - print - print "-- let's join two tables. The following joins the weather table" - print "-- and the cities table." - print - print "SELECT city, location, prcp, date" - print "FROM weather, cities" - print "WHERE name = city" - print - print pgcnx.query("""SELECT city, location, prcp, date FROM weather, cities - WHERE name = city""") - print - print "-- since the column names are all different, we don't have to " - print "-- specify the table name. If you want to be clear, you can do " - print "-- the following. They give identical results, of course." - print - print "SELECT w.city, c.location, w.prcp, w.date" - print "FROM weather w, cities c" - print "WHERE c.name = w.city;" - print - print pgcnx.query("""SELECT w.city, c.location, w.prcp, w.date - FROM weather w, cities c WHERE c.name = w.city""") - -# data updating commands -def update_data(pgcnx): - print "-----------------------------" - print "-- Updating data:" - print "-- an UPDATE statement is used for updating data. " - print "-----------------------------" - print - print "-- suppose you discover the temperature readings are all off by" - print "-- 2 degrees as of Nov 28, you may update the data as follow:" - print - print "UPDATE weather" - print " SET temp_hi = temp_hi - 2, temp_lo = temp_lo - 2" - print " WHERE date > '11/28/1994'" - print - pgcnx.query("""UPDATE weather - SET temp_hi = temp_hi - 2, temp_lo = temp_lo - 2 - WHERE date > '11/28/1994'""") - print - print "SELECT * from weather" - print pgcnx.query("SELECT * from weather") - -# data deletion commands -def delete_data(pgcnx): - print "-----------------------------" - print "-- Deleting data:" - print "-- a DELETE statement is used for deleting rows from a " - print "-- table." - print "-----------------------------" - print - print "-- suppose you are no longer interested in the weather of " - print "-- Hayward, you can do the following to delete those rows from" - print "-- the table" - print - print "DELETE FROM weather WHERE city = 'Hayward'" - pgcnx.query("DELETE FROM weather WHERE city = 'Hayward'") - print - print "SELECT * from weather" - print - print pgcnx.query("SELECT * from weather") - print - print "-- you can also delete all the rows in a table by doing the " - print "-- following. (This is different from DROP TABLE which removes " - print "-- the table in addition to the removing the rows.)" - print - print "DELETE FROM weather" - pgcnx.query("DELETE FROM weather") - print - print "SELECT * from weather" - print pgcnx.query("SELECT * from weather") - -# table removal commands -def remove_table(pgcnx): - print "-----------------------------" - print "-- Removing the tables:" - print "-- DROP TABLE is used to remove tables. After you have" - print "-- done this, you can no longer use those tables." - print "-----------------------------" - print - print "DROP TABLE weather, cities, temptab" - pgcnx.query("DROP TABLE weather, cities, temptab") - -# main demo function -def demo(pgcnx): - create_table(pgcnx) - wait_key() - insert_data(pgcnx) - wait_key() - select_data1(pgcnx) - select_data2(pgcnx) - create_aggregate(pgcnx) - join_table(pgcnx) - update_data(pgcnx) - delete_data(pgcnx) - remove_table(pgcnx) diff --git a/tutorial/func.py b/tutorial/func.py deleted file mode 100755 index af2b412b..00000000 --- a/tutorial/func.py +++ /dev/null @@ -1,205 +0,0 @@ -# func.py - demonstrate the use of SQL functions -# inspired from the PostgreSQL tutorial -# adapted to Python 1995 by Pascal ANDRE - -print """ -__________________________________________________________________ -MODULE FUNC.PY : SQL FUNCTION DEFINITION TUTORIAL - -This module is designed for being imported from python prompt - -In order to run the samples included here, first create a connection -using : cnx = func.DB(...) - -The "..." should be replaced with whatever arguments you need to open an -existing database. Usually all you need is the name of the database and, -in fact, if it is the same as your login name, you can leave it empty. - -then start the demo with: func.demo(cnx) -__________________________________________________________________ -""" - -from pg import DB -import sys - -# waits for a key -def wait_key(): - print "Press " - sys.stdin.read(1) - -# basic functions declaration -def base_func(pgcnx): - print "-----------------------------" - print "-- Creating SQL Functions on Base Types" - print "-- a CREATE FUNCTION statement lets you create a new " - print "-- function that can be used in expressions (in SELECT, " - print "-- INSERT, etc.). We will start with functions that " - print "-- return values of base types." - print "-----------------------------" - print - print "--" - print "-- let's create a simple SQL function that takes no arguments" - print "-- and returns 1" - print - print "CREATE FUNCTION one() RETURNS int4" - print " AS 'SELECT 1 as ONE' LANGUAGE 'sql'" - pgcnx.query("""CREATE FUNCTION one() RETURNS int4 - AS 'SELECT 1 as ONE' LANGUAGE 'sql'""") - wait_key() - print - print "--" - print "-- functions can be used in any expressions (eg. in the target" - print "-- list or qualifications)" - print - print "SELECT one() AS answer" - print pgcnx.query("SELECT one() AS answer") - print - print "--" - print "-- here's how you create a function that takes arguments. The" - print "-- following function returns the sum of its two arguments:" - print - print "CREATE FUNCTION add_em(int4, int4) RETURNS int4" - print " AS 'SELECT $1 + $2' LANGUAGE 'sql'" - pgcnx.query("""CREATE FUNCTION add_em(int4, int4) RETURNS int4 - AS 'SELECT $1 + $2' LANGUAGE 'sql'""") - print - print "SELECT add_em(1, 2) AS answer" - print pgcnx.query("SELECT add_em(1, 2) AS answer") - -# functions on composite types -def comp_func(pgcnx): - print "-----------------------------" - print "-- Creating SQL Functions on Composite Types" - print "-- it is also possible to create functions that return" - print "-- values of composite types." - print "-----------------------------" - print - print "-- before we create more sophisticated functions, let's " - print "-- populate an EMP table" - print - print "CREATE TABLE EMP (" - print " name text," - print " salary int4," - print " age int4," - print " dept varchar(16)" - print ")" - pgcnx.query("""CREATE TABLE EMP ( - name text, - salary int4, - age int4, - dept varchar(16))""") - print - print "INSERT INTO EMP VALUES ('Sam', 1200, 16, 'toy')" - print "INSERT INTO EMP VALUES ('Claire', 5000, 32, 'shoe')" - print "INSERT INTO EMP VALUES ('Andy', -1000, 2, 'candy')" - print "INSERT INTO EMP VALUES ('Bill', 4200, 36, 'shoe')" - print "INSERT INTO EMP VALUES ('Ginger', 4800, 30, 'candy')" - pgcnx.query("INSERT INTO EMP VALUES ('Sam', 1200, 16, 'toy')") - pgcnx.query("INSERT INTO EMP VALUES ('Claire', 5000, 32, 'shoe')") - pgcnx.query("INSERT INTO EMP VALUES ('Andy', -1000, 2, 'candy')") - pgcnx.query("INSERT INTO EMP VALUES ('Bill', 4200, 36, 'shoe')") - pgcnx.query("INSERT INTO EMP VALUES ('Ginger', 4800, 30, 'candy')") - wait_key() - print - print "-- the argument of a function can also be a tuple. For " - print "-- instance, double_salary takes a tuple of the EMP table" - print - print "CREATE FUNCTION double_salary(EMP) RETURNS int4" - print " AS 'SELECT $1.salary * 2 AS salary' LANGUAGE 'sql'" - pgcnx.query("""CREATE FUNCTION double_salary(EMP) RETURNS int4 - AS 'SELECT $1.salary * 2 AS salary' LANGUAGE 'sql'""") - print - print "SELECT name, double_salary(EMP) AS dream" - print "FROM EMP" - print "WHERE EMP.dept = 'toy'" - print pgcnx.query("""SELECT name, double_salary(EMP) AS dream - FROM EMP WHERE EMP.dept = 'toy'""") - print - print "-- the return value of a function can also be a tuple. However," - print "-- make sure that the expressions in the target list is in the " - print "-- same order as the columns of EMP." - print - print "CREATE FUNCTION new_emp() RETURNS EMP" - print " AS 'SELECT \'None\'::text AS name," - print " 1000 AS salary," - print " 25 AS age," - print " \'none\'::varchar(16) AS dept'" - print " LANGUAGE 'sql'" - pgcnx.query("""CREATE FUNCTION new_emp() RETURNS EMP - AS 'SELECT \\\'None\\\'::text AS name, - 1000 AS salary, - 25 AS age, - \\\'none\\\'::varchar(16) AS dept' - LANGUAGE 'sql'""") - wait_key() - print - print "-- you can then project a column out of resulting the tuple by" - print "-- using the \"function notation\" for projection columns. " - print "-- (ie. bar(foo) is equivalent to foo.bar) Note that we don't" - print "-- support new_emp().name at this moment." - print - print "SELECT name(new_emp()) AS nobody" - print pgcnx.query("SELECT name(new_emp()) AS nobody") - print - print "-- let's try one more function that returns tuples" - print "CREATE FUNCTION high_pay() RETURNS setof EMP" - print " AS 'SELECT * FROM EMP where salary > 1500'" - print " LANGUAGE 'sql'" - pgcnx.query("""CREATE FUNCTION high_pay() RETURNS setof EMP - AS 'SELECT * FROM EMP where salary > 1500' - LANGUAGE 'sql'""") - print - print "SELECT name(high_pay()) AS overpaid" - print pgcnx.query("SELECT name(high_pay()) AS overpaid") - -# function with multiple SQL commands -def mult_func(pgcnx): - print "-----------------------------" - print "-- Creating SQL Functions with multiple SQL statements" - print "-- you can also create functions that do more than just a" - print "-- SELECT." - print "-----------------------------" - print - print "-- you may have noticed that Andy has a negative salary. We'll" - print "-- create a function that removes employees with negative " - print "-- salaries." - print - print "SELECT * FROM EMP" - print pgcnx.query("SELECT * FROM EMP") - print - print "CREATE FUNCTION clean_EMP () RETURNS int4" - print " AS 'DELETE FROM EMP WHERE EMP.salary <= 0" - print " SELECT 1 AS ignore_this'" - print " LANGUAGE 'sql'" - pgcnx.query("CREATE FUNCTION clean_EMP () RETURNS int4 AS 'DELETE FROM EMP WHERE EMP.salary <= 0; SELECT 1 AS ignore_this' LANGUAGE 'sql'") - print - print "SELECT clean_EMP()" - print pgcnx.query("SELECT clean_EMP()") - print - print "SELECT * FROM EMP" - print pgcnx.query("SELECT * FROM EMP") - -# base cleanup -def demo_cleanup(pgcnx): - print "-- remove functions that were created in this file" - print - print "DROP FUNCTION clean_EMP()" - print "DROP FUNCTION high_pay()" - print "DROP FUNCTION new_emp()" - print "DROP FUNCTION add_em(int4, int4)" - print "DROP FUNCTION one()" - print - print "DROP TABLE EMP CASCADE" - pgcnx.query("DROP FUNCTION clean_EMP()") - pgcnx.query("DROP FUNCTION high_pay()") - pgcnx.query("DROP FUNCTION new_emp()") - pgcnx.query("DROP FUNCTION add_em(int4, int4)") - pgcnx.query("DROP FUNCTION one()") - pgcnx.query("DROP TABLE EMP CASCADE") - -# main demo function -def demo(pgcnx): - base_func(pgcnx) - comp_func(pgcnx) - mult_func(pgcnx) - demo_cleanup(pgcnx) diff --git a/tutorial/syscat.py b/tutorial/syscat.py deleted file mode 100755 index 1ab1d584..00000000 --- a/tutorial/syscat.py +++ /dev/null @@ -1,149 +0,0 @@ -# syscat.py - parses some system catalogs -# inspired from the PostgreSQL tutorial -# adapted to Python 1995 by Pascal ANDRE - -print """ -__________________________________________________________________ -MODULE SYSCAT.PY : PARSES SOME POSTGRESQL SYSTEM CATALOGS - -This module is designed for being imported from python prompt - -In order to run the samples included here, first create a connection -using : cnx = syscat.DB(...) - -The "..." should be replaced with whatever arguments you need to open an -existing database. Usually all you need is the name of the database and, -in fact, if it is the same as your login name, you can leave it empty. - -then start the demo with: syscat.demo(cnx) - -Some results may be empty, depending on your base status." - -__________________________________________________________________ -""" - -from pg import DB -import sys - -# waits for a key -def wait_key(): - print "Press " - sys.stdin.read(1) - -# lists all simple indices -def list_simple_ind(pgcnx): - result = pgcnx.query("""SELECT bc.relname AS class_name, - ic.relname AS index_name, a.attname - FROM pg_class bc, pg_class ic, pg_index i, pg_attribute a - WHERE i.indrelid = bc.oid AND i.indexrelid = bc.oid - AND i.indkey[0] = a.attnum AND a.attrelid = bc.oid - AND i.indproc = '0'::oid AND a.attisdropped = 'f' - ORDER BY class_name, index_name, attname""") - return result - -# list all user defined attributes and their type in user-defined classes -def list_all_attr(pgcnx): - result = pgcnx.query("""SELECT c.relname, a.attname, t.typname - FROM pg_class c, pg_attribute a, pg_type t - WHERE c.relkind = 'r' and c.relname !~ '^pg_' - AND c.relname !~ '^Inv' and a.attnum > 0 - AND a.attrelid = c.oid and a.atttypid = t.oid - AND a.attisdropped = 'f' - ORDER BY relname, attname""") - return result - -# list all user defined base type -def list_user_base_type(pgcnx): - result = pgcnx.query("""SELECT u.usename, t.typname - FROM pg_type t, pg_user u - WHERE u.usesysid = int2in(int4out(t.typowner)) - AND t.typrelid = '0'::oid and t.typelem = '0'::oid - AND u.usename <> 'postgres' order by usename, typname""") - return result - -# list all right-unary operators -def list_right_unary_operator(pgcnx): - result = pgcnx.query("""SELECT o.oprname AS right_unary, - lt.typname AS operand, result.typname AS return_type - FROM pg_operator o, pg_type lt, pg_type result - WHERE o.oprkind='r' and o.oprleft = lt.oid - AND o.oprresult = result.oid - ORDER BY operand""") - return result - -# list all left-unary operators -def list_left_unary_operator(pgcnx): - result = pgcnx.query("""SELECT o.oprname AS left_unary, - rt.typname AS operand, result.typname AS return_type - FROM pg_operator o, pg_type rt, pg_type result - WHERE o.oprkind='l' AND o.oprright = rt.oid - AND o.oprresult = result.oid - ORDER BY operand""") - return result - -# list all binary operators -def list_binary_operator(pgcnx): - result = pgcnx.query("""SELECT o.oprname AS binary_op, - rt.typname AS right_opr, lt.typname AS left_opr, - result.typname AS return_type - FROM pg_operator o, pg_type rt, pg_type lt, pg_type result - WHERE o.oprkind = 'b' AND o.oprright = rt.oid - AND o.oprleft = lt.oid AND o.oprresult = result.oid""") - return result - -# returns the name, args and return type from all function of lang l -def list_lang_func(pgcnx, l): - result = pgcnx.query("""SELECT p.proname, p.pronargs, t.typname - FROM pg_proc p, pg_language l, pg_type t - WHERE p.prolang = l.oid AND p.prorettype = t.oid - AND l.lanname = '%s' - ORDER BY proname""" % l) - return result - -# lists all the aggregate functions and the type to which they can be applied -def list_agg_func(pgcnx): - result = pgcnx.query("""SELECT p.proname, t.typname - FROM pg_aggregate a, pg_proc p, pg_type t - WHERE a.aggfnoid = p.oid - and p.proargtypes[0] = t.oid - ORDER BY proname, typname""") - return result - -# lists all the operator classes that can be used with each access method as -# well as the operators that can be used with the respective operator classes -def list_op_class(pgcnx): - result = pgcnx.query("""SELECT am.amname, opc.opcname, opr.oprname - FROM pg_am am, pg_amop amop, pg_opclass opc, pg_operator opr - WHERE amop.amopid = am.oid and amop.amopclaid = opc.oid - AND amop.amopopr = opr.oid order by amname, opcname, oprname""") - return result - -# demo function - runs all examples -def demo(pgcnx): - import sys, os - save_stdout = sys.stdout - sys.stdout = os.popen("more", "w") - print "Listing simple indices ..." - print list_simple_ind(pgcnx) - print "Listing all attributes ..." - print list_all_attr(pgcnx) - print "Listing all user-defined base types ..." - print list_user_base_type(pgcnx) - print "Listing all left-unary operators defined ..." - print list_left_unary_operator(pgcnx) - print "Listing all right-unary operators defined ..." - print list_right_unary_operator(pgcnx) - print "Listing all binary operators ..." - print list_binary_operator(pgcnx) - print "Listing C external function linked ..." - print list_lang_func(pgcnx, 'C') - print "Listing C internal functions ..." - print list_lang_func(pgcnx, 'internal') - print "Listing SQL functions defined ..." - print list_lang_func(pgcnx, 'sql') - print "Listing 'aggregate functions' ..." - print list_agg_func(pgcnx) - print "Listing 'operator classes' ..." - print list_op_class(pgcnx) - del sys.stdout - sys.stdout = save_stdout From 4ce81656a1375534a842c4084b44409cf6c32dff Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 10 Jan 2016 22:12:03 +0000 Subject: [PATCH 067/144] get_tables() should not list the information schema tables Since get_tables() does not return the other system tables starting with pg_, so it should not return the information schema tables either. Also removed an ancient check for tables starting with ^Inv that is not relevant any more since PostgreSQL 7.1 or so. --- docs/contents/changelog.rst | 1 + module/pg.py | 12 ++++++------ module/tests/test_classic_dbwrapper.py | 9 +++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/contents/changelog.rst b/docs/contents/changelog.rst index 527f0968..885b1319 100644 --- a/docs/contents/changelog.rst +++ b/docs/contents/changelog.rst @@ -10,6 +10,7 @@ Version 4.2 - Fix decimal point handling. - Add option to return boolean values as bool objects. - Add option to return money values as string. +- get_tables() does not list information schema tables any more. - Fix notification handler (Thanks Patrick TJ McPhee). - Fix a small issue with large objects. - The tutorial files have become a chapter in the documentation. diff --git a/module/pg.py b/module/pg.py index 93fdcfad..edd9a27d 100644 --- a/module/pg.py +++ b/module/pg.py @@ -622,12 +622,12 @@ def get_relations(self, kinds=None): where = kinds and "pg_class.relkind IN (%s) AND" % ','.join( ["'%s'" % x for x in kinds]) or '' return [_join_parts(x) for x in self.db.query( - "SELECT pg_namespace.nspname, pg_class.relname " - "FROM pg_class " - "JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace " - "WHERE %s pg_class.relname !~ '^Inv' AND " - "pg_class.relname !~ '^pg_' " - "ORDER BY 1, 2" % where).getresult()] + "SELECT pg_namespace.nspname, pg_class.relname" + " FROM pg_class " + " JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace " + " WHERE %s pg_namespace.nspname != 'information_schema'" + " AND pg_namespace.nspname !~ '^pg_' " + " ORDER BY 1, 2" % where).getresult()] def get_tables(self): """Return list of tables in connected database.""" diff --git a/module/tests/test_classic_dbwrapper.py b/module/tests/test_classic_dbwrapper.py index 5934d1f0..f853d509 100755 --- a/module/tests/test_classic_dbwrapper.py +++ b/module/tests/test_classic_dbwrapper.py @@ -557,6 +557,15 @@ def testGetDatabases(self): def testGetTables(self): get_tables = self.db.get_tables result1 = get_tables() + self.assertIsInstance(result1, list) + for t in result1: + t = t.split('.', 1) + self.assertGreaterEqual(len(t), 2) + if len(t) > 2: + self.assertTrue(t[1].startswith('"')) + t = t[0] + self.assertNotEqual(t, 'information_schema') + self.assertFalse(t.startswith('pg_')) tables = ('"A very Special Name"', '"A_MiXeD_quoted_NaMe"', 'a1', 'a2', 'A_MiXeD_NaMe', '"another special name"', From cbdb6fd70849750b6c672c8f1756c66f44b3d25a Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 11 Jan 2016 08:45:05 +0000 Subject: [PATCH 068/144] Flatten the directory structure of the project Simplified the directory structure by flattening the "module" subdirectory out to the root directory. That way, the setup.py script can now also access the top-level docs subdirectory, so it could also install or build the docs. There was nothing else in the root directory anyway, except the mkdocs and mktar scripts which could be made unnecessary through setup.py. Also made the setup script a bit clearer. Removed the note about MinGW for Windows since the Microsoft compiler for Python 2.7 and Visual Studio Community are now freely available including 64bit compilers, and produce less problems. Note that the usual structure would have been to use a "pygresql" package directory instead of the "module" directory. But since we install PyGreSQL as two top-level modules "pg" and "pgdb" instead of a package "pygresql", the flattened structure reflects that much better. For historical reasons and people don't want to rewrite import statements, we will keep it that way. --- module/GNUmakefile => GNUmakefile | 0 module/MANIFEST.in => MANIFEST.in | 0 module/PyGreSQL.spec => PyGreSQL.spec | 0 module/Setup.in.raw => Setup.in.raw | 0 mktar | 8 +++---- module/pg.py => pg.py | 0 module/pgdb.py => pgdb.py | 0 module/pgfs.h => pgfs.h | 0 module/pgmodule.c => pgmodule.c | 0 module/pgtypes.h => pgtypes.h | 0 module/setup.cfg => setup.cfg | 0 module/setup.py => setup.py | 23 +++++++++++-------- {module/tests => tests}/__init__.py | 0 {module/tests => tests}/dbapi20.py | 0 {module/tests => tests}/test_classic.py | 0 .../test_classic_connection.py | 0 .../tests => tests}/test_classic_dbwrapper.py | 0 .../tests => tests}/test_classic_functions.py | 0 .../tests => tests}/test_classic_largeobj.py | 0 {module/tests => tests}/test_dbapi20.py | 0 module/tox.ini => tox.ini | 0 21 files changed, 18 insertions(+), 13 deletions(-) rename module/GNUmakefile => GNUmakefile (100%) rename module/MANIFEST.in => MANIFEST.in (100%) rename module/PyGreSQL.spec => PyGreSQL.spec (100%) rename module/Setup.in.raw => Setup.in.raw (100%) rename module/pg.py => pg.py (100%) rename module/pgdb.py => pgdb.py (100%) rename module/pgfs.h => pgfs.h (100%) rename module/pgmodule.c => pgmodule.c (100%) rename module/pgtypes.h => pgtypes.h (100%) rename module/setup.cfg => setup.cfg (100%) rename module/setup.py => setup.py (92%) rename {module/tests => tests}/__init__.py (100%) rename {module/tests => tests}/dbapi20.py (100%) rename {module/tests => tests}/test_classic.py (100%) rename {module/tests => tests}/test_classic_connection.py (100%) rename {module/tests => tests}/test_classic_dbwrapper.py (100%) rename {module/tests => tests}/test_classic_functions.py (100%) rename {module/tests => tests}/test_classic_largeobj.py (100%) rename {module/tests => tests}/test_dbapi20.py (100%) rename module/tox.ini => tox.ini (100%) diff --git a/module/GNUmakefile b/GNUmakefile similarity index 100% rename from module/GNUmakefile rename to GNUmakefile diff --git a/module/MANIFEST.in b/MANIFEST.in similarity index 100% rename from module/MANIFEST.in rename to MANIFEST.in diff --git a/module/PyGreSQL.spec b/PyGreSQL.spec similarity index 100% rename from module/PyGreSQL.spec rename to PyGreSQL.spec diff --git a/module/Setup.in.raw b/Setup.in.raw similarity index 100% rename from module/Setup.in.raw rename to Setup.in.raw diff --git a/mktar b/mktar index e210e82b..99074a9b 100755 --- a/mktar +++ b/mktar @@ -32,14 +32,14 @@ fi TD=PyGreSQL-$VERSION TF=$DISTDIR/$TD.tgz -MODFILES="module/pg.py module/pgdb.py module/pgmodule.c - module/pgfs.h module/pgtypes.h - module/setup.py module/setup.cfg" +MODFILES="pg.py pgdb.py pgmodule.c + pgfs.h pgtypes.h + setup.py setup.cfg" DOCFILES="docs/Makefile docs/make.bat docs/*.rst docs/contents docs/download docs/community docs/_static docs/_templates" HTMLFILES="docs/_build/html" -TESTFILES="module/tests/*.py" +TESTFILES="tests/*.py" echo "Making source tarball..." diff --git a/module/pg.py b/pg.py similarity index 100% rename from module/pg.py rename to pg.py diff --git a/module/pgdb.py b/pgdb.py similarity index 100% rename from module/pgdb.py rename to pgdb.py diff --git a/module/pgfs.h b/pgfs.h similarity index 100% rename from module/pgfs.h rename to pgfs.h diff --git a/module/pgmodule.c b/pgmodule.c similarity index 100% rename from module/pgmodule.c rename to pgmodule.c diff --git a/module/pgtypes.h b/pgtypes.h similarity index 100% rename from module/pgtypes.h rename to pgtypes.h diff --git a/module/setup.cfg b/setup.cfg similarity index 100% rename from module/setup.cfg rename to setup.cfg diff --git a/module/setup.py b/setup.py similarity index 92% rename from module/setup.py rename to setup.py index 6329a6f6..b8e42e5e 100755 --- a/module/setup.py +++ b/setup.py @@ -10,10 +10,10 @@ Authors and history: * PyGreSQL written 1997 by D'Arcy J.M. Cain * based on code written 1995 by Pascal Andre -* setup script created 2000/04 Mark Alexander -* tweaked 2000/05 Jeremy Hylton -* win32 support 2001/01 by Gerhard Haering -* tweaked 2006/02-2010/02 by Christoph Zwerschke +* setup script created 2000 by Mark Alexander +* improved 2000 by Jeremy Hylton +* improved 2001 by Gerhard Haering +* improved 2006 and 2016 by Christoph Zwerschke Prerequisites to be installed: * Python including devel package (header files and distutils) @@ -27,9 +27,6 @@ python setup.py build # to build the module python setup.py install # to install it -You can use MinGW or MinGW-w64 for building on Windows: -python setup.py build -c mingw32 install - See docs.python.org/doc/install/ for more information on using distutils to install Python programs. @@ -58,6 +55,15 @@ from distutils.sysconfig import get_python_inc, get_python_lib +# For historical reasons, PyGreSQL does not install itself as a single +# "pygresql" package, but as two top-level modules "pg", providing the +# classic interface, and "pgdb" for the modern DB-API 2.0 interface. +# These two top-level Python modules share the same C extension "_pg". + +py_modules = ['pg', 'pgdb'] +c_sources = ['pgmodule.c'] + + def pg_config(s): """Retrieve information about installed version of PostgreSQL.""" f = os.popen('pg_config --%s' % s) @@ -80,7 +86,6 @@ def pg_version(): pg_version = pg_version() -py_modules = ['pg', 'pgdb'] libraries = ['pq'] # Make sure that the Python header files are searched before # those of PostgreSQL, because PostgreSQL can have its own Python.h @@ -176,7 +181,7 @@ def finalize_options(self): platforms=["any"], license="Python", py_modules=py_modules, - ext_modules=[Extension('_pg', ['pgmodule.c'], + ext_modules=[Extension('_pg', c_sources, include_dirs=include_dirs, library_dirs=library_dirs, define_macros=define_macros, undef_macros=undef_macros, libraries=libraries, extra_compile_args=extra_compile_args)], diff --git a/module/tests/__init__.py b/tests/__init__.py similarity index 100% rename from module/tests/__init__.py rename to tests/__init__.py diff --git a/module/tests/dbapi20.py b/tests/dbapi20.py similarity index 100% rename from module/tests/dbapi20.py rename to tests/dbapi20.py diff --git a/module/tests/test_classic.py b/tests/test_classic.py similarity index 100% rename from module/tests/test_classic.py rename to tests/test_classic.py diff --git a/module/tests/test_classic_connection.py b/tests/test_classic_connection.py similarity index 100% rename from module/tests/test_classic_connection.py rename to tests/test_classic_connection.py diff --git a/module/tests/test_classic_dbwrapper.py b/tests/test_classic_dbwrapper.py similarity index 100% rename from module/tests/test_classic_dbwrapper.py rename to tests/test_classic_dbwrapper.py diff --git a/module/tests/test_classic_functions.py b/tests/test_classic_functions.py similarity index 100% rename from module/tests/test_classic_functions.py rename to tests/test_classic_functions.py diff --git a/module/tests/test_classic_largeobj.py b/tests/test_classic_largeobj.py similarity index 100% rename from module/tests/test_classic_largeobj.py rename to tests/test_classic_largeobj.py diff --git a/module/tests/test_dbapi20.py b/tests/test_dbapi20.py similarity index 100% rename from module/tests/test_dbapi20.py rename to tests/test_dbapi20.py diff --git a/module/tox.ini b/tox.ini similarity index 100% rename from module/tox.ini rename to tox.ini From 71b706ea17b099cc5556feaedf421b9769378ef6 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 11 Jan 2016 10:24:33 +0000 Subject: [PATCH 069/144] Make python setup.py sdist include the docs --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index a4029f24..b5e70975 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,6 +3,7 @@ include *.h include *.py include *.cfg recursive-include tests *.py +recursive-include docs *.html *.css *.txt *.js *.gif *.png *.ico *.py Makefile *.bat *.rst *.css_t global-exclude *.pyc global-exclude *.pyo From 3a793d05879a46ea42510b1c627bb5023eae6b93 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 11 Jan 2016 11:26:06 +0000 Subject: [PATCH 070/144] Mention pip in the installation instructions Also remove the notes about MinGW because they are outdated, and using one of the free Microsoft compilers is today less problematic. --- docs/contents/install.rst | 41 +++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/docs/contents/install.rst b/docs/contents/install.rst index 4acfd034..48bcbabc 100644 --- a/docs/contents/install.rst +++ b/docs/contents/install.rst @@ -20,10 +20,31 @@ All three files will be installed directly into the Python site-packages directory. To uninstall PyGreSQL, simply remove these three files again. +Installing with Pip +------------------- + +This is the most easy way to install PyGreSQL if you have "pip" installed +on your computer. Just run the following command in your terminal:: + + pip install PyGreSQL + +This will automatically try to find and download a distribution on the +`Python Package Index `_ that matches your operating +system and Python version and install it on your computer. + + Installing from a Binary Distribution ------------------------------------- -This is the easiest way to install PyGreSQL. +If you don't want to use "pip", or "pip" doesn't find an appropriate +distribution for your computer, you can also try to manually download +and install a distribution. + +When you download the source distribution, you will need to compile the +C extensions, for which you need a C compiler installed on your computer. +If you don't want to install a C compiler or avoid possible problems +with the compilation, you can search for a pre-compiled binary distribution +of PyGreSQL on the Python Package Index or the PyGreSQL homepage. You can currently download PyGreSQL as Linux RPM, NetBSD package and Windows installer. Make sure the required Python version of the binary package matches @@ -49,7 +70,7 @@ tool. This is usually also part of the "devel" package on Unix, and will be installed as part of the database server feature on Windows systems. Building and installing with Distutils --------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can build and install PyGreSQL using `Distutils `_. @@ -61,27 +82,17 @@ Type the following commands to build and install PyGreSQL:: python setup.py build python setup.py install -If you are using `MinGW `_ to build PyGreSQL under -Microsoft Windows, please note that Python newer version 2.3 is using msvcr71 -instead of msvcrt as its common runtime library. You can allow for that by -editing the file ``%MinGWpath%/lib/gcc/%MinGWversion%/specs`` and changing -the entry that reads ``-lmsvcrt`` to ``-lmsvcr71``. You may also need to copy -``libpq.lib`` to ``libpq.a`` in the PostgreSQL ``lib`` directory. Then use -the following command to build and install PyGreSQL:: - - python setup.py build -c mingw32 install - Now you should be ready to use PyGreSQL. Compiling Manually ------------------- +~~~~~~~~~~~~~~~~~~ The source file for compiling the dynamic module is called pgmodule.c. You have two options. You can compile PyGreSQL as a stand-alone module or you can build it into the Python interpreter. Stand-Alone ------------ +^^^^^^^^^^^ * In the directory containing ``pgmodule.c``, run the following command:: @@ -139,7 +150,7 @@ Stand-Alone if your Python modules are in ``/usr/lib/python``. Built-in to Python interpreter ------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Find the directory where your ``Setup`` file lives (usually in the ``Modules`` subdirectory) in the Python source hierarchy and copy or symlink the From 69694679b4512cb6d4136c5541d8fe45ab337537 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 11 Jan 2016 11:44:49 +0000 Subject: [PATCH 071/144] Add simple README and LICENSE files in the root directory A simple README.rst file has been re-added because it is a standard part of a source distribution and displayed on PyPI and GitHub. Also, added a LICENSE file as a copy of the copyright notice in the docs. --- LICENSE.txt | 28 ++++++++++++++++++++++++++++ README.rst | 27 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 LICENSE.txt create mode 100644 README.rst diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..268e101c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,28 @@ +Written by D'Arcy J.M. Cain (darcy@druid.net) + +Based heavily on code written by Pascal Andre (andre@chimay.via.ecp.fr) + +Copyright (c) 1995, Pascal Andre + +Further modifications copyright (c) 1997-2008 by D'Arcy J.M. Cain +(darcy@PyGreSQL.org) + +Further modifications copyright (c) 2009-2016 by the PyGreSQL team. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose, without fee, and without a written agreement +is hereby granted, provided that the above copyright notice and this +paragraph and the following two paragraphs appear in all copies. In +this license the term "AUTHORS" refers to anyone who has contributed code +to PyGreSQL. + +IN NO EVENT SHALL THE AUTHORS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, +ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF +AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +THE AUTHORS SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE +AUTHORS HAVE NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, +ENHANCEMENTS, OR MODIFICATIONS. diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..319c5d9b --- /dev/null +++ b/README.rst @@ -0,0 +1,27 @@ +PyGreSQL - Python interface for PostgreSQL +========================================== + +PyGreSQL is a Python module that interfaces to a PostgreSQL database. +It embeds the PostgreSQL query library to allow easy use of the powerful +PostgreSQL features from a Python script. + +PyGreSQL is developed and tested on a NetBSD system, but it should also +run on most other platforms where PostgreSQL and Python is running. +It is based on the PyGres95 code written by Pascal Andre. +D'Arcy (darcy@druid.net) renamed it to PyGreSQL starting with version 2.0 +and serves as the "BDFL" of PyGreSQL. + +Installation +------------ + +The simplest way to install PyGreSQL is to type:: + + $ pip install PyGreSQL + +For other ways of installing PyGreSQL and requirements, +see the documentation. + +Documentation +------------- + +The documentation is available at http://www.pygresql.org/. From 4c3abb424dd60ce56730d4391db906c519e4f292 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 11 Jan 2016 12:09:18 +0000 Subject: [PATCH 072/144] Use the original header file from PG instead of pgfs.h --- pgtypes.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pgtypes.h b/pgtypes.h index dc2d909b..354a76a1 100644 --- a/pgtypes.h +++ b/pgtypes.h @@ -2,8 +2,8 @@ pgtypes - PostgreSQL type definitions These are the standard PostgreSQL built-in types, - extracted from catalog/pg_type.h Revision 1.212, - because that header file is sometimes not availale + extracted from server/catalog/pg_type.h Revision 1.212, + because that header file is sometimes not available or needs other header files to get properly included. You can also query pg_type to get this information. */ From 009dcb5fb787dcc67d90305a8628dc5491f89cde Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 11 Jan 2016 16:54:02 +0000 Subject: [PATCH 073/144] Add a "first steps" tutorial for PyGreSQL In addition to the already existing tutorial for the PostgreSQL database. Also added tests for all the steps in this tutorial. --- docs/contents/index.rst | 1 + docs/contents/postgres/basic.rst | 2 +- docs/contents/tutorial.rst | 257 +++++++++++++++++++++++++++++++ tests/test_tutorial.py | 183 ++++++++++++++++++++++ 4 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 docs/contents/tutorial.rst create mode 100644 tests/test_tutorial.py diff --git a/docs/contents/index.rst b/docs/contents/index.rst index 0857882a..f3e9caa3 100644 --- a/docs/contents/index.rst +++ b/docs/contents/index.rst @@ -10,6 +10,7 @@ Contents Installing PyGreSQL What's New and History of Changes General PyGreSQL Programming Information + First Steps with PyGreSQL The Classic PyGreSQL Interface The DB-API Compliant Interface A PostgreSQL Primer diff --git a/docs/contents/postgres/basic.rst b/docs/contents/postgres/basic.rst index c7a50fc3..b01d7f48 100644 --- a/docs/contents/postgres/basic.rst +++ b/docs/contents/postgres/basic.rst @@ -20,7 +20,7 @@ You can also pass the database name, host, port and login information as parameters when creating the :class:`DB` instance:: >>> db = DB(dbname='testdb', host='pgserver', port=5432, - ... user='fred', passwd='tiger') + ... user='scott', passwd='tiger') The :class:`DB` class of which ``db`` is an object is a wrapper around the lower level :class:`pgobject` class of the :mod:`pg` module. diff --git a/docs/contents/tutorial.rst b/docs/contents/tutorial.rst new file mode 100644 index 00000000..9a265dcd --- /dev/null +++ b/docs/contents/tutorial.rst @@ -0,0 +1,257 @@ +First Steps with PyGreSQL +========================= + +In this small tutorial we show you the basic operations you can perform +with both flavors of the PyGreSQL interface. Please choose your flavor: + +.. contents:: + :local: + + +First Steps with the classic PyGreSQL Interface +----------------------------------------------- + +.. py:currentmodule:: pg + +The first thing you need to do anything with your PostgreSQL database is +to create a database connection. + +To do this, simply import the :class:`DB` wrapper class and create an +instance of it, passing the necessary connection parameters, like this:: + + >>> from pg import DB + >>> db = DB(dbname='testdb', host='pgserver', port=5432, + ... user='scott', passwd='tiger') + +You can omit one or even all parameters if you want to use their default +values. PostgreSQL will use the name of the current operating system user +as the login and the database name, and will try to connect to the local +host on port 5432 if nothing else is specified. + +The `db` object has all methods of the lower-level :class:`pgobject` class +plus some more convenience methods provided by the :class:`DB` wrapper. + +You can now execute database queries using the :meth:`DB.query` method:: + + >>> db.query("create table fruits(id serial primary key, name varchar)") + +You can list all database tables with the :meth:`DB.get_tables` method:: + + >>> db.get_tables() + ['public.fruits'] + +To get the attributes of the *fruits* table, use :meth:`DB.get_attnames`:: + + >>> db.get_attnames('fruits') + {'id': 'int', 'name': 'text'} + +Verify that you can insert into the newly created *fruits* table: + + >>> db.has_table_privilege('fruits', 'insert') + True + +You can insert a new row into the table using the :meth:`DB.insert` method, +for example:: + + >>> db.insert('fruits', name='apple') + {'name': 'apple', 'id': 1} + +Note how this method returns the full row as a dictionary including its *id* +column that has been generated automatically by a database sequence. You can +also pass a dictionary to the :meth:`DB.insert` method instead of or in +addition to using keyword arguments. + +Let's add another row to the table: + + >>> banana = db.insert('fruits', name='banana') + +Or, you can add a whole bunch of fruits at the same time using the +:meth:`DB.inserttable` method. Note that this method uses the COPY command +of PostgreSQL to insert all data in one operation, which is faster than +sending many INSERT commands:: + + >>> more_fruits = 'cherimaya durian eggfruit fig grapefruit'.split() + >>> data = list(enumerate(more_fruits, start=3)) + >>> db.inserttable('fruits', data) + +We can now query the database for all rows that have been inserted into +the *fruits* table:: + + >>> print db.query('select * from fruits') + id| name + --+---------- + 1|apple + 2|banana + 3|cherimaya + 4|durian + 5|eggfruit + 6|fig + 7|grapefruit + (7 rows) + +Instead of simply printing the :class:`pgqueryobject` instance that has been +returned by this query, we can also request the data as list of tuples:: + + >>> q = db.query('select * from fruits') + >>> q.getresult() + ... [(1, 'apple'), ..., (7, 'grapefruit')] + +Instead of a list of tuples, we can also request a list of dicts:: + + >>> q.dictresult() + [{'id': 1, 'name': 'apple'}, ..., {'id': 7, 'name': 'grapefruit'}] + +And with Python 2.5 or higher, you can also return the rows as named tuples:: + + >>> rows = q.namedresult() + >>> rows[3].name + 'durian' + +To change a single row in the database, you can use the :meth:`DB.update` +method. For instance, if you want to capitalize the name 'banana':: + + >>> db.update('fruits', banana, name=banana['name'].capitalize()) + {'id': 2, 'name': 'Banana'} + >>> print db.query('select * from fruits where id between 1 and 3') + id| name + --+--------- + 1|apple + 2|Banana + 3|cherimaya + (3 rows) + +Let's also capitalize the other names in the database:: + + >>> db.query('update fruits set name=initcap(name)') + '7' + +The returned string `'7'` tells us the number of updated rows. It is returned +as a string to discern it from an OID which will be returned as an integer, +if a new row has been inserted into a table with an OID column. + +To delete a single row from the database, use the :meth:`DB.delete` method:: + + >>> db.delete('fruits', banana) + 1 + +The returned integer value `1` tells us that one row has been deleted. If we +try it again, the method returns the integer value `0`. Naturally, this method +can only return 0 or 1:: + + >>> db.delete('fruits', banana) + 0 + +Of course, we can insert the row back again:: + + >>> db.insert('fruits', banana) + {'id': 2, 'name': 'Banana'} + +If we want to change a different row, we can get its current state with:: + + >>> apple = db.get('fruits', 1) + >>> apple + {'name': 'Apple', 'id': 1} + +We can duplicate the row like this:: + + >>> db.insert('fruits', apple, id=8) + {'id': 8, 'name': 'Apple'} + + To remove the duplicated row, we can do:: + + >>> db.delete('fruits', id=8) + 1 + +Finally, to remove the table from the database and close the connection:: + + >>> db.query("drop table fruits") + >>> db.close() + +For more advanced features and details, see the reference: :doc:`pg/index` + +First Steps with the DB-API 2.0 Interface +----------------------------------------- + +.. py:currentmodule:: pgdb + +As with the classic interface, the first thing you need to do is to create +a database connection. To do this, use the function :func:`pgdb.connect` +in the :mod:`pgdb` module, passing the connection parameters:: + + >>> from pgdb import connect + >>> con = connect(database='testdb', host='pgserver:5432', + ... user='scott', password='tiger') + +Note that like in the classic interface, you can omit parameters if they +are the default values used by PostgreSQL. + +To do anything with the connection, you need to request a cursor object +from it, which is thought of as the Python representation of a database +cursor. The connection has a method that lets you get a cursor:: + + >>> cursor = con.cursor() + +The cursor now has a method that lets you execute database queries:: + + >>> cursor.execute("create table fruits(" + ... "id serial primary key, name varchar)") + + +To insert data into the table, also can also use this method:: + + >>> cursor.execute("insert into fruits (name) values ('apple')") + +You can pass parameters in a safe way:: + + >>> cursor.execute("insert into fruits (name) values (%s)", ('banana',)) + +For inserting multiple rows at once, you can use the following method:: + + >>> more_fruits = 'cherimaya durian eggfruit fig grapefruit'.split() + >>> parameters = [(name,) for name in more_fruits] + >>> cursor.executemany("insert into fruits (name) values (%s)", parameters) + +Note that the DB API 2.0 interface does not have an autocommit as you may +be used from PostgreSQL. So in order to make these inserts permanent, you +need to commit them to the database first:: + + >>> con.commit() + +If you end the program without calling the commit method of the connection, +or if you call the rollback method of the connection, then all the changes +will be discarded. + +In a similar way, you can also update or delete rows in the database, +executing UPDATE or DELETE statements instead of INSERT statements. + +To fetch rows from the database, execute a SELECT statement first. Then +you can use one of several fetch methods to retrieve the results. For +instance, to request a single row:: + + >>> cursor.execute('select * from fruits where id=1') + >>> cursor.fetchone() + [1, 'apple'] + +The output is a single list that represents the row. + +To fetch all rows of the query, use this method instead:: + + >>> cursor.execute('select * from fruits') + >>> cursor.fetchall() + [[1, 'apple'], ..., [7, 'grapefruit']] + +The output is a list with 7 items which are lists of 2 items. + +If you want to fetch only a limited number of rows from the query:: + + >>> cursor.execute('select * from fruits') + >>> cursor.fetchmany(2) + [[1, 'apple'], [2, 'banana']] + +Finally, to remove the table from the database and close the connection:: + + >>> db.execute("drop table fruits") + >>> cur.close() + >>> db.close() + +For more advanced features and details, see the reference: :doc:`pgdb/index` \ No newline at end of file diff --git a/tests/test_tutorial.py b/tests/test_tutorial.py new file mode 100644 index 00000000..c7909741 --- /dev/null +++ b/tests/test_tutorial.py @@ -0,0 +1,183 @@ +#! /usr/bin/python +# -*- coding: utf-8 -*- + +try: + import unittest2 as unittest # for Python < 2.7 +except ImportError: + import unittest + +try: + from collections import namedtuple +except ImportError: # Python < 2.6 + namedtuple = None + +from pg import DB +from pgdb import connect + +# We need a database to test against. If LOCAL_PyGreSQL.py exists we will +# get our information from that. Otherwise we use the defaults. +dbname = 'unittest' +dbhost = None +dbport = 5432 + +try: + from LOCAL_PyGreSQL import * +except ImportError: + pass + + +class TestClassicTutorial(unittest.TestCase): + """Test the First Steps Tutorial for the classic interface.""" + + def setUp(self): + """Setup test tables or empty them if they already exist.""" + db = DB(dbname=dbname, host=dbhost, port=dbport) + db.query("set datestyle to 'iso'") + db.query("set default_with_oids=false") + db.query("set standard_conforming_strings=false") + db.query("set client_min_messages=warning") + db.query("drop table if exists fruits cascade") + db.query("create table fruits(id serial primary key, name varchar)") + self.db = db + + def tearDown(self): + db = self.db + db.query("drop table fruits") + db.close() + + def test_all_steps(self): + db = self.db + r = db.get_tables() + self.assertIsInstance(r, list) + self.assertIn('public.fruits', r) + r = db.get_attnames('fruits') + self.assertIsInstance(r, dict) + self.assertEqual(r, {'id': 'int', 'name': 'text'}) + r = db.has_table_privilege('fruits', 'insert') + self.assertTrue(r) + r = db.insert('fruits', name='apple') + self.assertIsInstance(r, dict) + self.assertEqual(r, {'name': 'apple', 'id': 1}) + banana = r = db.insert('fruits', name='banana') + self.assertIsInstance(r, dict) + self.assertEqual(r, {'name': 'banana', 'id': 2}) + more_fruits = 'cherimaya durian eggfruit fig grapefruit'.split() + if namedtuple: + data = list(enumerate(more_fruits, start=3)) + else: # Pyton < 2.6 + data = [(n + 3, name) for n, name in enumerate(more_fruits)] + db.inserttable('fruits', data) + q = db.query('select * from fruits') + r = str(q).splitlines() + self.assertEqual(r[0], 'id| name ') + self.assertEqual(r[1], '--+----------') + self.assertEqual(r[2], ' 1|apple ') + self.assertEqual(r[8], ' 7|grapefruit') + self.assertEqual(r[9], '(7 rows)') + q = db.query('select * from fruits') + r = q.getresult() + self.assertIsInstance(r, list) + self.assertIsInstance(r[0], tuple) + self.assertEqual(r[0], (1, 'apple')) + self.assertEqual(r[6], (7, 'grapefruit')) + r = q.dictresult() + self.assertIsInstance(r, list) + self.assertIsInstance(r[0], dict) + self.assertEqual(r[0], {'id': 1, 'name': 'apple'}) + self.assertEqual(r[6], {'id': 7, 'name': 'grapefruit'}) + try: + rows = r = q.namedresult() + except TypeError: # Python < 2.6 + self.assertIsNone(namedtuple) + else: + self.assertIsInstance(r, list) + self.assertIsInstance(r[0], tuple) + self.assertEqual(rows[3].name, 'durian') + r = db.update('fruits', banana, name=banana['name'].capitalize()) + self.assertIsInstance(r, dict) + self.assertEqual(r, {'id': 2, 'name': 'Banana'}) + q = db.query('select * from fruits where id between 1 and 3') + r = str(q).splitlines() + self.assertEqual(r[0], 'id| name ') + self.assertEqual(r[1], '--+---------') + self.assertEqual(r[2], ' 1|apple ') + self.assertEqual(r[3], ' 2|Banana ') + self.assertEqual(r[4], ' 3|cherimaya') + self.assertEqual(r[5], '(3 rows)') + r = db.query('update fruits set name=initcap(name)') + self.assertIsInstance(r, str) + self.assertEqual(r, '7') + r = db.delete('fruits', banana) + self.assertIsInstance(r, int) + self.assertEqual(r, 1) + r = db.delete('fruits', banana) + self.assertIsInstance(r, int) + self.assertEqual(r, 0) + r = db.insert('fruits', banana) + self.assertIsInstance(r, dict) + self.assertEqual(r, {'id': 2, 'name': 'Banana'}) + apple = r = db.get('fruits', 1) + self.assertIsInstance(r, dict) + self.assertEqual(r, {'name': 'Apple', 'id': 1}) + r = db.insert('fruits', apple, id=8) + self.assertIsInstance(r, dict) + self.assertEqual(r, {'id': 8, 'name': 'Apple'}) + r = db.delete('fruits', id=8) + self.assertIsInstance(r, int) + self.assertEqual(r, 1) + + +class TestDbApi20Tutorial(unittest.TestCase): + """Test the First Steps Tutorial for the DB-API 2.0 interface.""" + + def setUp(self): + """Setup test tables or empty them if they already exist.""" + database = dbname + host = '%s:%d' % (dbhost or '', dbport or -1) + con = connect(database=database, host=host) + cur = con.cursor() + cur.execute("set datestyle to 'iso'") + cur.execute("set default_with_oids=false") + cur.execute("set standard_conforming_strings=false") + cur.execute("set client_min_messages=warning") + cur.execute("drop table if exists fruits cascade") + cur.execute("create table fruits(id serial primary key, name varchar)") + cur.close() + self.con = con + + def tearDown(self): + con = self.con + cur = con.cursor() + cur.execute("drop table fruits") + cur.close() + con.close() + + def test_all_steps(self): + con = self.con + cursor = con.cursor() + cursor.execute("insert into fruits (name) values ('apple')") + cursor.execute("insert into fruits (name) values (%s)", ('banana',)) + more_fruits = 'cherimaya durian eggfruit fig grapefruit'.split() + parameters = [(name,) for name in more_fruits] + cursor.executemany("insert into fruits (name) values (%s)", parameters) + con.commit() + cursor.execute('select * from fruits where id=1') + r = cursor.fetchone() + self.assertIsInstance(r, list) + self.assertEqual(r, [1, 'apple']) + cursor.execute('select * from fruits') + r = cursor.fetchall() + self.assertIsInstance(r, list) + self.assertEqual(len(r), 7) + self.assertEqual(r[0], [1, 'apple']) + self.assertEqual(r[6], [7, 'grapefruit']) + cursor.execute('select * from fruits') + r = cursor.fetchmany(2) + self.assertIsInstance(r, list) + self.assertEqual(len(r), 2) + self.assertEqual(r[0], [1, 'apple']) + self.assertEqual(r[1], [2, 'banana']) + + +if __name__ == '__main__': + unittest.main() From c01434c6e359c50ad92927553f0419baf2eb14e5 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 11 Jan 2016 17:16:46 +0000 Subject: [PATCH 074/144] Add coverage results to ignore list From 30590d933dbcafe07d9ad9b7e9e10e5080f32eda Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Tue, 12 Jan 2016 08:08:40 +0000 Subject: [PATCH 075/144] Fixed misleading docstrings The methods work with dicts, not with tuples, so speaking of a tuple in this context can be confusing. --- pg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pg.py b/pg.py index edd9a27d..b3c82f2a 100644 --- a/pg.py +++ b/pg.py @@ -726,7 +726,7 @@ def has_table_privilege(self, cl, privilege='select'): return ret def get(self, cl, arg, keyname=None): - """Get a tuple from a database table or view. + """Get a row from a database table or view. This method is the basic mechanism to get a single row. The keyname that the key specifies a unique row. If keyname is not specified @@ -782,7 +782,7 @@ def get(self, cl, arg, keyname=None): return arg def insert(self, cl, d=None, **kw): - """Insert a tuple into a database table. + """Insert a row into a database table. This method inserts a row into a table. If a dictionary is supplied it starts with that. Otherwise it uses a blank dictionary. From cd0e4641fa4a2510239b081af6af28875f2e59c5 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Tue, 12 Jan 2016 12:27:13 +0000 Subject: [PATCH 076/144] Better checks for system catalogs --- pg.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pg.py b/pg.py index b3c82f2a..d32a0d04 100644 --- a/pg.py +++ b/pg.py @@ -590,7 +590,8 @@ def pkey(self, cl, newpkey=None): " pg_attribute.attname FROM pg_class" " JOIN pg_namespace" " ON pg_namespace.oid = pg_class.relnamespace" - " AND pg_namespace.nspname NOT LIKE 'pg_%'" + " AND pg_namespace.nspname" + " NOT SIMILAR TO 'pg/_%|information/_schema' ESCAPE '/'" " JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid" " AND pg_attribute.attisdropped = 'f'" " JOIN pg_index ON pg_index.indrelid = pg_class.oid" @@ -625,8 +626,8 @@ def get_relations(self, kinds=None): "SELECT pg_namespace.nspname, pg_class.relname" " FROM pg_class " " JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace " - " WHERE %s pg_namespace.nspname != 'information_schema'" - " AND pg_namespace.nspname !~ '^pg_' " + " WHERE %s pg_namespace.nspname" + " NOT SIMILAR TO 'pg/_%%|information/_schema' ESCAPE '/'" " ORDER BY 1, 2" % where).getresult()] def get_tables(self): From ac1904d3332dedb4c3e6d008727ed79d8375df89 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Tue, 12 Jan 2016 13:28:43 +0000 Subject: [PATCH 077/144] Back port some code changes from trunk to 4.x --- pg.py | 78 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/pg.py b/pg.py index d32a0d04..53e6c28b 100644 --- a/pg.py +++ b/pg.py @@ -47,7 +47,7 @@ namedtuple = None -# Auxiliary functions that are independent of a DB connection: +# Auxiliary functions that are independent from a DB connection: def _is_quoted(s): """Check whether this string is a quoted identifier.""" @@ -304,7 +304,7 @@ def __init__(self, *args, **kw): # * to any other true value to just print debug statements def __getattr__(self, name): - # All undefined members are same as in underlying pg connection: + # All undefined members are same as in underlying connection: if self.db: return getattr(self.db, name) else: @@ -411,7 +411,7 @@ def _split_schema(self, cl): """ s = _split_parts(cl) - if len(s) > 1: # name already qualfied? + if len(s) > 1: # name already qualified? # should be database.schema.table or schema.table if len(s) > 3: raise _prg_error('Too many dots in class name %s' % cl) @@ -528,7 +528,7 @@ def query(self, qstr, *args): has OIDs, the return value is the OID of the newly inserted row. If the query is an update or delete statement, or an insert statement that did not insert exactly one row in a table with OIDs, then the - numer of rows affected is returned as a string. If it is a statement + number of rows affected is returned as a string. If it is a statement that returns rows as a result (usually a select statement, but maybe also an "insert/update ... returning" statement), this method returns a pgqueryobject that can be accessed via getresult() or dictresult() @@ -581,22 +581,20 @@ def pkey(self, cl, newpkey=None): self._pkeys = {} if self.server_version >= 80200: # the ANY syntax works correctly only with PostgreSQL >= 8.2 - any_indkey = "= ANY (pg_index.indkey)" + any_indkey = "= ANY (i.indkey)" else: any_indkey = "IN (%s)" % ', '.join( - ['pg_index.indkey[%d]' % i for i in range(16)]) - for r in self.db.query( - "SELECT pg_namespace.nspname, pg_class.relname," - " pg_attribute.attname FROM pg_class" - " JOIN pg_namespace" - " ON pg_namespace.oid = pg_class.relnamespace" - " AND pg_namespace.nspname" - " NOT SIMILAR TO 'pg/_%|information/_schema' ESCAPE '/'" - " JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid" - " AND pg_attribute.attisdropped = 'f'" - " JOIN pg_index ON pg_index.indrelid = pg_class.oid" - " AND pg_index.indisprimary = 't'" - " AND pg_attribute.attnum " + any_indkey).getresult(): + ['i.indkey[%d]' % i for i in range(16)]) + q = ("SELECT s.nspname, r.relname, a.attname" + " FROM pg_class r" + " JOIN pg_namespace s ON s.oid = r.relnamespace" + " AND s.nspname NOT SIMILAR" + " TO 'pg/_%|information/_schema' ESCAPE '/'" + " JOIN pg_attribute a ON a.attrelid = r.oid" + " AND NOT a.attisdropped" + " JOIN pg_index i ON i.indrelid = r.oid" + " AND i.indisprimary AND a.attnum " + any_indkey) + for r in self.db.query(q).getresult(): cl, pkey = _join_parts(r[:2]), r[2] self._pkeys.setdefault(cl, []).append(pkey) # (only) for composite primary keys, the values will be frozensets @@ -620,15 +618,15 @@ def get_relations(self, kinds=None): specifying which kind of relations you want to list. """ - where = kinds and "pg_class.relkind IN (%s) AND" % ','.join( - ["'%s'" % x for x in kinds]) or '' - return [_join_parts(x) for x in self.db.query( - "SELECT pg_namespace.nspname, pg_class.relname" - " FROM pg_class " - " JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace " - " WHERE %s pg_namespace.nspname" - " NOT SIMILAR TO 'pg/_%%|information/_schema' ESCAPE '/'" - " ORDER BY 1, 2" % where).getresult()] + where = kinds and " AND r.relkind IN (%s)" % ','.join( + ["'%s'" % k for k in kinds]) or '' + q = ("SELECT s.nspname, r.relname" + " FROM pg_class r" + " JOIN pg_namespace s ON s.oid = r.relnamespace" + " WHERE s.nspname NOT SIMILAR" + " TO 'pg/_%%|information/_schema' ESCAPE '/' %s" + " ORDER BY 1, 2") % where + return [_join_parts(r) for r in self.db.query(q).getresult()] def get_tables(self): """Return list of tables in connected database.""" @@ -659,17 +657,16 @@ def get_attnames(self, cl, newattnames=None): if qcl not in self.get_relations('rv'): raise _prg_error('Class %s does not exist' % qcl) - q = "SELECT pg_attribute.attname, pg_type.typname" - if self._regtypes: - q += "::regtype" - q += (" FROM pg_class" - " JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid" - " JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid" - " JOIN pg_type ON pg_type.oid = pg_attribute.atttypid" - " WHERE pg_namespace.nspname = '%s' AND pg_class.relname = '%s'" - " AND (pg_attribute.attnum > 0 OR pg_attribute.attname = 'oid')" - " AND pg_attribute.attisdropped = 'f'") % cl - q = self.db.query(q).getresult() + q = ("SELECT a.attname, t.typname%s" + " FROM pg_class r" + " JOIN pg_namespace s ON r.relnamespace = s.oid" + " JOIN pg_attribute a ON a.attrelid = r.oid" + " JOIN pg_type t ON t.oid = a.atttypid" + " WHERE s.nspname = $1 AND r.relname = $2" + " AND (a.attnum > 0 OR a.attname = 'oid')" + " AND NOT a.attisdropped") % ( + self._regtypes and '::regtype' or '',) + q = self.db.query(q, cl).getresult() if self._regtypes: t = dict(q) @@ -721,8 +718,9 @@ def has_table_privilege(self, cl, privilege='select'): try: return self._privileges[(qcl, privilege)] except KeyError: - q = "SELECT has_table_privilege('%s', '%s')" % (qcl, privilege) - ret = self.db.query(q).getresult()[0][0] == self._make_bool(True) + q = "SELECT has_table_privilege($1, $2)" + q = self.db.query(q, (qcl, privilege)) + ret = q.getresult()[0][0] == self._make_bool(True) self._privileges[(qcl, privilege)] = ret return ret From 80c56345ecea89b44ca03dc28f639e61a82b02dc Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Tue, 12 Jan 2016 13:44:48 +0000 Subject: [PATCH 078/144] Minor fixes and additions in the docs --- docs/contents/pg/db_wrapper.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index fed4eb32..85357e87 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -138,6 +138,8 @@ has_table_privilege -- check whether current user has specified table privilege Returns True if the current user has the specified privilege for the table. +.. versionadded:: 4.1 + get -- get a row from a database table or view ---------------------------------------------- From 73f7492ae6124af7d90dc18f27c2e4c29cbfa0b0 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Tue, 12 Jan 2016 13:46:00 +0000 Subject: [PATCH 079/144] Fix accidental mistake in last change --- docs/contents/pg/db_wrapper.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index 85357e87..4bcb2163 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -138,7 +138,7 @@ has_table_privilege -- check whether current user has specified table privilege Returns True if the current user has the specified privilege for the table. -.. versionadded:: 4.1 +.. versionadded:: 4.0 get -- get a row from a database table or view ---------------------------------------------- From f7ce2387c822b8d283d3fde1a2eec2c11423c8fd Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Tue, 12 Jan 2016 15:32:32 +0000 Subject: [PATCH 080/144] Improve implementation and test for pkey() --- pg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pg.py b/pg.py index 53e6c28b..21e051ed 100644 --- a/pg.py +++ b/pg.py @@ -558,7 +558,7 @@ def pkey(self, cl, newpkey=None): If newpkey is set and is not a dictionary then set that value as the primary key of the class. If it is a dictionary - then replace the _pkeys dictionary with a copy of it. + then replace the internal cache of primary keys with a copy of it. """ # First see if the caller is supplying a dictionary From 59a393fcf56262e0d4eda57f10060f39ab876c87 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Tue, 12 Jan 2016 16:33:00 +0000 Subject: [PATCH 081/144] Use query parameter again --- pg.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pg.py b/pg.py index 21e051ed..f0037cbb 100644 --- a/pg.py +++ b/pg.py @@ -423,16 +423,16 @@ def _split_schema(self, cl): schemas = self.db.query(q).getresult()[0][0][1:-1].split(',') if schemas: # non-empty path # search schema for this object in the current search path + # (we could also use unnest with ordinality here to spare + # one query, but this is only possible since PostgreSQL 9.4) q = ' UNION '.join( ["SELECT %d::integer AS n, '%s'::name AS nspname" % s for s in enumerate(schemas)]) - q = ("SELECT nspname FROM pg_class" - " JOIN pg_namespace" - " ON pg_class.relnamespace = pg_namespace.oid" + q = ("SELECT nspname FROM pg_class r" + " JOIN pg_namespace s ON r.relnamespace = s.oid" " JOIN (%s) AS p USING (nspname)" - " WHERE pg_class.relname = '%s'" - " ORDER BY n LIMIT 1" % (q, cl)) - schema = self.db.query(q).getresult() + " WHERE r.relname = $1 ORDER BY n LIMIT 1" % q) + schema = self.db.query(q, (cl,)).getresult() if schema: # schema found schema = schema[0][0] else: # object not found in current search path From fa87c98f7aee386cc5b7e62bad63eea5b54f6530 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Tue, 12 Jan 2016 16:57:21 +0000 Subject: [PATCH 082/144] Improve docstring --- pg.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pg.py b/pg.py index f0037cbb..1ed5e448 100644 --- a/pg.py +++ b/pg.py @@ -613,9 +613,9 @@ def get_databases(self): def get_relations(self, kinds=None): """Get list of relations in connected database of specified kinds. - If kinds is None or empty, all kinds of relations are returned. - Otherwise kinds can be a string or sequence of type letters - specifying which kind of relations you want to list. + If kinds is None or empty, all kinds of relations are returned. + Otherwise kinds can be a string or sequence of type letters + specifying which kind of relations you want to list. """ where = kinds and " AND r.relkind IN (%s)" % ','.join( @@ -783,9 +783,12 @@ def get(self, cl, arg, keyname=None): def insert(self, cl, d=None, **kw): """Insert a row into a database table. - This method inserts a row into a table. If a dictionary is - supplied it starts with that. Otherwise it uses a blank dictionary. - Either way the dictionary is updated from the keywords. + This method inserts a row into a table. The name of the table must + be passed as the first parameter. The other parameters are used for + providing the data of the row that shall be inserted into the table. + If a dictionary is supplied as the second parameter, it starts with + that. Otherwise it uses a blank dictionary. Either way the dictionary + is updated from the keywords. The dictionary is then, if possible, reloaded with the values actually inserted in order to pick up values modified by rules, triggers, etc. From 47492e14e31e674481e55e2a835169901973c416 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Tue, 12 Jan 2016 21:21:46 +0000 Subject: [PATCH 083/144] Minor doc fixes --- docs/contents/pg/db_wrapper.rst | 6 ++++-- pg.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index 4bcb2163..fa52a80e 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -63,10 +63,12 @@ pkey -- return the primary key of a table :param str table: name of table :returns: Name of the field which is the primary key of the table :rtype: str + :rtype: str + :raises KeyError: the table does not have a primary key This method returns the primary key of a table. For composite primary -keys, the return value will be a frozenset. Note that this raises an -exception if the table does not have a primary key. +keys, the return value will be a frozenset. Note that this raises a +KeyError if the table does not have a primary key. get_databases -- get list of databases in the system ---------------------------------------------------- diff --git a/pg.py b/pg.py index 1ed5e448..605dd631 100644 --- a/pg.py +++ b/pg.py @@ -554,7 +554,7 @@ def pkey(self, cl, newpkey=None): """This method gets or sets the primary key of a class. Composite primary keys are represented as frozensets. Note that - this raises an exception if the table does not have a primary key. + this raises a KeyError if the table does not have a primary key. If newpkey is set and is not a dictionary then set that value as the primary key of the class. If it is a dictionary @@ -743,7 +743,7 @@ def get(self, cl, arg, keyname=None): # build qualified class name qcl = self._add_schema(cl) # To allow users to work with multiple tables, - # we munge the name of the "oid" the key + # we munge the name of the "oid" key qoid = _oid_key(qcl) if not keyname: # use the primary key by default From 7cb29d18e9245896cf7c64bdf4a4df0fd598cd07 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 13 Jan 2016 02:00:04 +0000 Subject: [PATCH 084/144] Back ported some minor fixes from trunk to 4.x --- tests/test_classic.py | 1 + tests/test_classic_connection.py | 2 +- tests/test_classic_dbwrapper.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_classic.py b/tests/test_classic.py index 0dd27631..7977c20c 100755 --- a/tests/test_classic.py +++ b/tests/test_classic.py @@ -181,6 +181,7 @@ def test_update(self): self.assertEqual(r['dvar'], 123) r = db.get('_test_schema', 1234) + self.assertIn('dvar', r) db.update('_test_schema', _test=1234, dvar=456) r = db.get('_test_schema', 1234) self.assertEqual(r['dvar'], 456) diff --git a/tests/test_classic_connection.py b/tests/test_classic_connection.py index 4ae295c7..030cce51 100755 --- a/tests/test_classic_connection.py +++ b/tests/test_classic_connection.py @@ -998,7 +998,7 @@ def testSetDecimalPoint(self): en_locales = 'en', 'en_US', 'en_US.utf8', 'en_US.UTF-8' en_money = '$34.25', '$ 34.25', '34.25$', '34.25 $', '34.25 Dollar' de_locales = 'de', 'de_DE', 'de_DE.utf8', 'de_DE.UTF-8' - de_money = ('34,25€', '34,25 €', '€34,25' '€ 34,25', + de_money = ('34,25€', '34,25 €', '€34,25', '€ 34,25', 'EUR34,25', 'EUR 34,25', '34,25 EUR', '34,25 Euro', '34,25 DM') # first try with English localization (using the point) for lc in en_locales: diff --git a/tests/test_classic_dbwrapper.py b/tests/test_classic_dbwrapper.py index f853d509..c81e07ce 100755 --- a/tests/test_classic_dbwrapper.py +++ b/tests/test_classic_dbwrapper.py @@ -790,7 +790,7 @@ def testInsert(self): data, change = test expect = data.copy() expect.update(change) - if data.get('m') and server_version < 910000: + if data.get('m') and server_version < 90100: # PostgreSQL < 9.1 cannot directly convert numbers to money data['m'] = "'%s'::money" % data['m'] self.assertEqual(insert(table, data), data) From 069041e93f61211c58fb18f68bf66a9342deaee5 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 13 Jan 2016 15:50:25 +0000 Subject: [PATCH 085/144] Add documentation for transaction handling methods Also, the savepoint name is not optional. --- docs/contents/pg/db_wrapper.rst | 68 ++++++++++++++++++++++++++++++++- pg.py | 9 ++--- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index fa52a80e..27094fbc 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -126,8 +126,8 @@ get_attnames -- get the attribute names of a table Given the name of a table, digs out the set of attribute names. -has_table_privilege -- check whether current user has specified table privilege -------------------------------------------------------------------------------- +has_table_privilege -- check table privilege +-------------------------------------------- .. method:: DB.has_table_privilege(table, privilege) @@ -142,6 +142,70 @@ Returns True if the current user has the specified privilege for the table. .. versionadded:: 4.0 +begin/commit/rollback/savepoint/release -- transaction handling +--------------------------------------------------------------- + +.. method:: DB.begin([mode]) + + Begin a transaction + + :param str mode: an optional transaction mode such as 'READ ONLY' + + This initiates a transaction block, that is, all following queries + will be executed in a single transaction until :meth:`DB.commit` + or :meth:`DB.rollback` is called. + +.. versionadded:: 4.1 + +.. method:: DB.start() + + This is the same as the :meth:`DB.begin` method. + +.. method:: DB.commit() + + Commit a transaction + + This commits the current transaction. All changes made by the + transaction become visible to others and are guaranteed to be + durable if a crash occurs. + +.. method:: DB.end() + + This is the same as the :meth:`DB.commit` method. + +.. versionadded:: 4.1 + +.. method:: DB.rollback([name]) + + Roll back a transaction + + :param str name: optionally, roll back to the specified savepoint + + This rolls back the current transaction and causes all the updates + made by the transaction to be discarded. + +.. versionadded:: 4.1 + +.. method:: DB.savepoint(name) + + Define a new savepoint + + :param str name: the name to give to the new savepoint + + This establishes a new savepoint within the current transaction. + +.. versionadded:: 4.1 + +.. method:: DB.release(name) + + Destroy a savepoint + + :param str name: the name of the savepoint to destroy + + This destroys a savepoint previously defined in the current transaction. + +.. versionadded:: 4.1 + get -- get a row from a database table or view ---------------------------------------------- diff --git a/pg.py b/pg.py index 605dd631..16138e3f 100644 --- a/pg.py +++ b/pg.py @@ -503,18 +503,15 @@ def commit(self): end = commit def rollback(self, name=None): - """Rollback the current transaction.""" + """Roll back the current transaction.""" qstr = 'ROLLBACK' if name: qstr += ' TO ' + name return self.query(qstr) - def savepoint(self, name=None): + def savepoint(self, name): """Define a new savepoint within the current transaction.""" - qstr = 'SAVEPOINT' - if name: - qstr += ' ' + name - return self.query(qstr) + return self.query('SAVEPOINT ' + name) def release(self, name): """Destroy a previously defined savepoint.""" From 3f53e18fbbb9661d6106ce2c765f54a4b93a5367 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 13 Jan 2016 22:07:36 +0000 Subject: [PATCH 086/144] Back port minor changes in the docs from trunk to 4.x --- docs/contents/pg/connection.rst | 3 +-- docs/contents/pg/db_wrapper.rst | 11 +++++++---- pg.py | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/contents/pg/connection.rst b/docs/contents/pg/connection.rst index 46c6e8c4..a009d693 100644 --- a/docs/contents/pg/connection.rst +++ b/docs/contents/pg/connection.rst @@ -168,9 +168,8 @@ uses the COPY command of the PostgreSQL database. The list is a list of tuples/lists that define the values for each inserted row. The rows values may contain string, integer, long or double (real) values. -.. note:: +.. warning:: - **Be very careful**: This method doesn't type check the fields according to the table definition; it just look whether or not it knows how to handle such types. diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index 27094fbc..c73bac14 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -218,6 +218,7 @@ get -- get a row from a database table or view :param str keyname: name of field to use as key (optional) :returns: A dictionary - the keys are the attribute names, the values are the row values. + :raises ProgrammingError: no primary key or missing privilege This method is the basic mechanism to get a single row. It assumes that the key specifies a unique row. If *keyname* is not specified, @@ -232,14 +233,15 @@ as ``oid(schema.table)``. insert -- insert a row into a database table -------------------------------------------- -.. method:: DB.insert(table, [d,] [key = val, ...]) +.. method:: DB.insert(table, [d], [col=val, ...]) Insert a row into a database table :param str table: name of table :param dict d: optional dictionary of values - :returns: the inserted values + :returns: the inserted values in the database :rtype: dict + :raises ProgrammingError: missing privilege or conflict This method inserts a row into a table. If the optional dictionary is not supplied then the required values must be included as keyword/value @@ -255,14 +257,15 @@ although PostgreSQL does. update -- update a row in a database table ------------------------------------------ -.. method:: DB.update(table, [d,] [key = val, ...]) +.. method:: DB.update(table, [d], [col=val, ...]) Update a row in a database table :param str table: name of table :param dict d: optional dictionary of values - :returns: the new row + :returns: the new row in the database :rtype: dict + :raises ProgrammingError: no primary key or missing privilege Similar to insert but updates an existing row. The update is based on the OID value as munged by get or passed as keyword, or on the primary key of diff --git a/pg.py b/pg.py index 16138e3f..392e6ecb 100644 --- a/pg.py +++ b/pg.py @@ -842,9 +842,9 @@ def update(self, cl, d=None, **kw): default values, etc. """ - # Update always works on the oid which get returns if available, + # Update always works on the oid which get() returns if available, # otherwise use the primary key. Fail if neither. - # Note that we only accept oid key from named args for safety + # Note that we only accept oid key from named args for safety. qcl = self._add_schema(cl) qoid = _oid_key(qcl) if 'oid' in kw: From 96311fe1c7753fa6d341c04177f14b0a26f2eb5f Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 13 Jan 2016 22:16:53 +0000 Subject: [PATCH 087/144] Update year in the copyright in two more files --- pg.py | 2 +- pgmodule.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pg.py b/pg.py index 392e6ecb..41a0e6c8 100644 --- a/pg.py +++ b/pg.py @@ -15,7 +15,7 @@ """ -# Copyright (c) 1997-2013 by D'Arcy J.M. Cain. +# Copyright (c) 1997-2016 by D'Arcy J.M. Cain. # # Contributions made by Ch. Zwerschke and others. # diff --git a/pgmodule.c b/pgmodule.c index 05b5ffb2..580df30a 100644 --- a/pgmodule.c +++ b/pgmodule.c @@ -22,7 +22,7 @@ * AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, * ENHANCEMENTS, OR MODIFICATIONS. * - * Further modifications copyright 1997, 1998, 1999 by D'Arcy J.M. Cain + * Further modifications copyright 1997 to 2016 by D'Arcy J.M. Cain * (darcy@druid.net) subject to the same terms and conditions as above. * */ From 924d06924f8c6d6e75475a2b2ed49e4690ded055 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 13 Jan 2016 22:57:29 +0000 Subject: [PATCH 088/144] Renamed some parameters to clarify their meaning This is uncritical, because these are not keyword parameters. Also, we don't change much in the 4.x branch in order to stay compatible. Also, avoided the old terminology "class" for a table or table-like object in PostgreSQL, because it may confuse people. The word "table" is so much clearer. --- docs/contents/pg/db_wrapper.rst | 18 +++++++++--------- pg.py | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index c73bac14..fd6ea1fc 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -233,7 +233,7 @@ as ``oid(schema.table)``. insert -- insert a row into a database table -------------------------------------------- -.. method:: DB.insert(table, [d], [col=val, ...]) +.. method:: DB.insert(table, [d], [key=val, ...]) Insert a row into a database table @@ -257,7 +257,7 @@ although PostgreSQL does. update -- update a row in a database table ------------------------------------------ -.. method:: DB.update(table, [d], [col=val, ...]) +.. method:: DB.update(table, [d], [key=val, ...]) Update a row in a database table @@ -311,27 +311,27 @@ Example:: clear -- clear row values in memory ----------------------------------- -.. method:: DB.clear(table, [a]) +.. method:: DB.clear(table, [d]) Clear row values in memory :param str table: name of table - :param dict a: optional dictionary of values + :param dict d: optional dictionary of values :returns: an empty row :rtype: dict This method clears all the attributes to values determined by the types. -Numeric types are set to 0, Booleans are set to ``'f'``, dates are set -to ``'now()'`` and everything else is set to the empty string. -If the array argument is present, it is used as the array and any entries -matching attribute names are cleared with everything else left unchanged. +Numeric types are set to 0, Booleans are set to ``'f'``, and everything +else is set to the empty string. If the optional dictionary is present, +it is used as the row and any entries matching attribute names are cleared +with everything else left unchanged. If the dictionary is not supplied a new one is created. delete -- delete a row from a database table -------------------------------------------- -.. method:: DB.delete(table, [d,] [key = val, ...]) +.. method:: DB.delete(table, [d], [key=val, ...]) Delete a row from a database table diff --git a/pg.py b/pg.py index 41a0e6c8..d4a892e5 100644 --- a/pg.py +++ b/pg.py @@ -896,19 +896,19 @@ def update(self, cl, d=None, **kw): self.get(qcl, d) return d - def clear(self, cl, a=None): + def clear(self, cl, d=None): """Clear all the attributes to values determined by the types. Numeric types are set to 0, Booleans are set to false, and everything - else is set to the empty string. If the array argument is present, - it is used as the array and any entries matching attribute names are - cleared with everything else left unchanged. + else is set to the empty string. If the second argument is present, + it is used as the row dictionary and any entries matching attribute + names are cleared with everything else left unchanged. """ # At some point we will need a way to get defaults from a table. qcl = self._add_schema(cl) - if a is None: - a = {} # empty if argument is not present + if d is None: + d = {} # empty if argument is not present attnames = self.get_attnames(qcl) for n, t in attnames.items(): if n == 'oid': @@ -916,12 +916,12 @@ def clear(self, cl, a=None): if t in ('int', 'integer', 'smallint', 'bigint', 'float', 'real', 'double precision', 'num', 'numeric', 'money'): - a[n] = 0 + d[n] = 0 elif t in ('bool', 'boolean'): - a[n] = self._make_bool(False) + d[n] = self._make_bool(False) else: - a[n] = '' - return a + d[n] = '' + return d def delete(self, cl, d=None, **kw): """Delete an existing row in a database table. From cf4ff6c1db3bf02ce32858d2f8fc677154e0f92c Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Thu, 14 Jan 2016 04:15:11 +0000 Subject: [PATCH 089/144] Fix typo. --- docs/contents/postgres/basic.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contents/postgres/basic.rst b/docs/contents/postgres/basic.rst index b01d7f48..09f08051 100644 --- a/docs/contents/postgres/basic.rst +++ b/docs/contents/postgres/basic.rst @@ -60,7 +60,7 @@ Now we want to fill our tables with data. An **INSERT** statement is used to insert a new row into a table. There are several ways you can specify what columns the data should go to. -Let us insert a row into each of these tables. Tthe simplest case is when +Let us insert a row into each of these tables. The simplest case is when the list of values corresponds to the order of the columns specified in the CREATE TABLE command:: From 11550423fca070bf03040f877351e5ea2e42fcd6 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 14 Jan 2016 16:01:34 +0000 Subject: [PATCH 090/144] Remove antiquated make and spec files Use python setup.py build or the instructions in install.txt to build. Linux distributions like openSUSE use their own spec files anyway. These stalled and unmaintained files only create confusion. --- GNUmakefile | 58 --------------------------------------------------- PyGreSQL.spec | 58 --------------------------------------------------- 2 files changed, 116 deletions(-) delete mode 100644 GNUmakefile delete mode 100644 PyGreSQL.spec diff --git a/GNUmakefile b/GNUmakefile deleted file mode 100644 index f3a93fea..00000000 --- a/GNUmakefile +++ /dev/null @@ -1,58 +0,0 @@ -# $Header: /usr/cvs/Public/pygresql/module/GNUmakefile,v 1.19 2005-01-11 12:13:38 darcy Exp $ -# $Id$ - -subdir = src/interfaces/python -top_builddir = ../../.. -include $(top_builddir)/src/Makefile.global - -NAME = _pgmodule -OBJS = pgmodule.o -SHLIB_LINK = $(libpq) -ifeq ($(PORTNAME), cygwin) -override CPPFLAGS += -DUSE_DL_IMPORT -SHLIB_LINK += $(python_libspec) -endif - - -include $(top_srcdir)/src/Makefile.shlib - -override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS) $(python_includespec) - -all: all-lib - -all-lib: libpq-all - -.PHONY: libpq-all -libpq-all: - $(MAKE) -C $(libpq_builddir) all - -install-warning-msg := { \ -echo "*** Skipping the installation of the Python interface module for lack"; \ -echo "*** of permissions. To install it, change to the directory"; \ -echo "*** `pwd`,"; \ -echo "*** become the appropriate user, and do '$(MAKE) install'."; } - -install: all installdirs - @if test -w $(DESTDIR)$(python_moduleexecdir) && test -w $(DESTDIR)$(python_moduledir); then \ - echo "$(INSTALL_SHLIB) $(shlib) $(DESTDIR)$(python_moduleexecdir)/_pgmodule$(DLSUFFIX)"; \ - $(INSTALL_SHLIB) $(shlib) $(DESTDIR)$(python_moduleexecdir)/_pgmodule$(DLSUFFIX); \ - \ - echo "$(INSTALL_DATA) $(srcdir)/pg.py $(DESTDIR)$(python_moduledir)/pg.py"; \ - $(INSTALL_DATA) $(srcdir)/pg.py $(DESTDIR)$(python_moduledir)/pg.py; \ - \ - echo "$(INSTALL_DATA) $(srcdir)/pgdb.py $(DESTDIR)$(python_moduledir)/pgdb.py"; \ - $(INSTALL_DATA) $(srcdir)/pgdb.py $(DESTDIR)$(python_moduledir)/pgdb.py; \ - else \ - $(install-warning-msg); \ - fi - -installdirs: - $(mkinstalldirs) $(DESTDIR)$(python_moduleexecdir) $(DESTDIR)$(python_moduledir) - -uninstall: - rm -f $(DESTDIR)$(python_moduleexecdir)/_pgmodule$(DLSUFFIX) \ - $(DESTDIR)$(python_moduledir)/pg.py \ - $(DESTDIR)$(python_moduledir)/pgdb.py - -clean distclean maintainer-clean: clean-lib - rm -f $(OBJS) diff --git a/PyGreSQL.spec b/PyGreSQL.spec deleted file mode 100644 index 380461a7..00000000 --- a/PyGreSQL.spec +++ /dev/null @@ -1,58 +0,0 @@ -# $Id$ -%define version 3.0 -%define release pre20000310 -%define name PyGreSQL -%define pythonversion 1.5 -Source: %{name}-%{version}-%{release}.tgz -Summary: A Python interface for PostgreSQL database. -Name: %{name} -Version: %{version} -Release: %{release} -#Patch: -Group: Applications/Databases -BuildRoot: /tmp/rpmbuild_%{name} -Copyright: GPL-like -Requires: python >= %{pythonversion}, postgresql -Packager: Hartmut Goebel -Vendor: D'Arcy J.M. Cain -URL: http://www.druid.net/pygresql/ - -%changelog -#* Tue Oct 06 1998 Fabio Coatti -#- fixed installation directory files list - -%description -PyGreSQL is a python module that interfaces to a PostgreSQL database. It -embeds the PostgreSQL query library to allow easy use of the powerful -PostgreSQL features from a Python script. - -Version 3.0 includes DB-API 2.0 support. - -%prep -rm -rf $RPM_BUILD_ROOT - -%setup -n %{name}-%{version}-%{release} -#%patch - -%build -mkdir -p $RPM_BUILD_ROOT/usr/lib/python%{pythonversion}/lib-dynload -cc -fpic -shared -o $RPM_BUILD_ROOT/usr/lib/python%{pythonversion}/lib-dynload/_pg.so -I/usr/include/pgsql/ -I/usr/include/python1.5 pgmodule.c -lpq -## import fails, since _pg is not yet installed -python -c 'import pg' || true -python -c 'import pgdb' || true - -%install -cp *.py *.pyc $RPM_BUILD_ROOT/usr/lib/python%{pythonversion}/ - -cd $RPM_BUILD_ROOT -find . -type f | sed 's,^\.,\%attr(-\,root\,root) ,' > $RPM_BUILD_DIR/file.list.%{name} -find . -type l | sed 's,^\.,\%attr(-\,root\,root) ,' >> $RPM_BUILD_DIR/file.list.%{name} - -%files -f ../file.list.%{name} -%doc %attr(-,root,root) Announce ChangeLog README tutorial - - -%clean -rm -rf $RPM_BUILD_ROOT -cd $RPM_BUILD_DIR -rm -rf %{name}-%{version}-%{release} file.list.%{name} From 27ba9c64e37b88454ebc86e31156005065ecbe97 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 14 Jan 2016 17:32:01 +0000 Subject: [PATCH 091/144] Test error messages and security of the get() method The get() method should be immune against SQL hacking with apostrophes in values, and give a proper and helpful error message if a row is not found. --- tests/test_classic_dbwrapper.py | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_classic_dbwrapper.py b/tests/test_classic_dbwrapper.py index c81e07ce..945f78dc 100755 --- a/tests/test_classic_dbwrapper.py +++ b/tests/test_classic_dbwrapper.py @@ -724,6 +724,45 @@ def testGetFromView(self): self.assertIn('v4', r) self.assertEqual(r['v4'], 'abc4') + def testGetLittleBobbyTables(self): + get = self.db.get + query = self.db.query + query("drop table if exists test_students") + query("create table test_students (firstname varchar primary key," + " nickname varchar, grade char(2))") + query("insert into test_students values (" + "'D''Arcy', 'Darcey', 'A+')") + query("insert into test_students values (" + "'Sheldon', 'Moonpie', 'A+')") + query("insert into test_students values (" + "'Robert', 'Little Bobby Tables', 'D-')") + r = get('test_students', 'Sheldon') + self.assertEqual(r, dict( + firstname="Sheldon", nickname='Moonpie', grade='A+')) + r = get('test_students', 'Robert') + self.assertEqual(r, dict( + firstname="Robert", nickname='Little Bobby Tables', grade='D-')) + r = get('test_students', "D'Arcy") + self.assertEqual(r, dict( + firstname="D'Arcy", nickname='Darcey', grade='A+')) + try: + get('test_students', "D' Arcy") + except pg.DatabaseError as error: + self.assertEqual(str(error), + 'No such record in public.test_students where firstname = ' + "'D'' Arcy'") + try: + get('test_students', "Robert'); TRUNCATE TABLE test_students;--") + except pg.DatabaseError as error: + self.assertEqual(str(error), + 'No such record in public.test_students where firstname = ' + "'Robert''); TRUNCATE TABLE test_students;--'") + q = "select * from test_students order by 1 limit 4" + r = query(q).getresult() + self.assertEqual(len(r), 3) + self.assertEqual(r[1][2], 'D-') + query('drop table test_students') + def testInsert(self): insert = self.db.insert query = self.db.query From dec66602425df2a66a70f3b30b58b3df7437ec0c Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 15 Jan 2016 01:16:23 +0000 Subject: [PATCH 092/144] Add methods get/set_parameter to DB wrapper class These methods can be used to get/set/reset run-time parameters, even several at once. Since this is pretty useful and will not break anything, I have also back ported these additions to the 4.x branch. Everything is well documented and tested, of course. --- docs/contents/changelog.rst | 4 +- docs/contents/pg/db_wrapper.rst | 71 +++++++++- pg.py | 119 ++++++++++++++++ tests/test_classic_dbwrapper.py | 244 +++++++++++++++++++++++--------- tests/test_tutorial.py | 7 +- 5 files changed, 373 insertions(+), 72 deletions(-) diff --git a/docs/contents/changelog.rst b/docs/contents/changelog.rst index 885b1319..d51afac4 100644 --- a/docs/contents/changelog.rst +++ b/docs/contents/changelog.rst @@ -3,10 +3,12 @@ ChangeLog Version 4.2 ----------- -- Set a better default for the user option "escaping-funcs". - The supported Python versions are 2.4 to 2.7. - PostgreSQL is supported in all versions from 8.3 to 9.5. +- Set a better default for the user option "escaping-funcs". - Force build to compile with no errors. +- New methods get_parameters() and set_parameters() in the classic interface + which can be used to get or set run-time parameters. - Fix decimal point handling. - Add option to return boolean values as bool objects. - Add option to return money values as string. diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index fd6ea1fc..bf7d8074 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -121,11 +121,78 @@ get_attnames -- get the attribute names of a table Get the attribute names of a table :param str table: name of table - :returns: A dictionary -- the keys are the attribute names, - the values are the type names of the attributes. + :returns: a dictionary mapping attribute names to type names Given the name of a table, digs out the set of attribute names. +Returns a dictionary of attribute names (the names are the keys, +the values are the names of the attributes' types). + +By default, only a limited number of simple types will be returned. +You can get the regular types after enabling this by calling the +:meth:`DB.use_regtypes` method. + +get/set_parameter -- get or set run-time parameters +---------------------------------------------------- + +.. method:: DB.get_parameter(parameter) + + Get the value of run-time parameters + + :param parameter: the run-time parameter(s) to get + :type param: str, tuple, list or dict + :returns: the current value(s) of the run-time parameter(s) + :rtype: str, list or dict + :raises TypeError: Invalid parameter type(s) + :raises ProgrammingError: Invalid parameter name(s) + +If the parameter is a string, the return value will also be a string +that is the current setting of the run-time parameter with that name. + +You can get several parameters at once by passing a list or tuple of +parameter names. The return value will then be a corresponding list +of parameter settings. If you pass a dict as parameter instead, its +values will be set to the parameter settings corresponding to its keys. + +By passing the special name `'all'` as the parameter, you can get a dict +of all existing configuration parameters. + +.. versionadded:: 4.2 + +.. method:: DB.set_parameter(self, parameter, [value], [local]) + + Set the value of run-time parameters + + :param parameter: the run-time parameter(s) to set + :type param: string, tuple, list or dict + :param value: the value to set + :type param: str or None + :raises TypeError: Invalid parameter type(s) + :raises ValueError: Invalid value argument(s) + :raises ProgrammingError: Invalid parameter name(s) or values + +If the parameter and the value are strings, the run-time parameter +will be set to that value. If no value or *None* is passed as a value, +then the run-time parameter will be restored to its default value. + +You can set several parameters at once by passing a list or tuple +of parameter names, with a single value that all parameters should +be set to or with a corresponding list or tuple of values. + +You can also pass a dict as parameters. In this case, you should +not pass a value, since the values will be taken from the dict. + +By passing the special name `'all'` as the parameter, you can reset +all existing settable run-time parameters to their default values. + +If you set *local* to `True`, then the command takes effect for only the +current transaction. After :meth:`DB.commit` or :meth:`DB.rollback`, +the session-level setting takes effect again. Setting *local* to `True` +will appear to have no effect if it is executed outside a transaction, +since the transaction will end immediately. + +.. versionadded:: 4.2 + has_table_privilege -- check table privilege -------------------------------------------- diff --git a/pg.py b/pg.py index d4a892e5..83d31f5d 100644 --- a/pg.py +++ b/pg.py @@ -517,6 +517,125 @@ def release(self, name): """Destroy a previously defined savepoint.""" return self.query('RELEASE ' + name) + def get_parameter(self, parameter): + """Get the value of a run-time parameter. + + If the parameter is a string, the return value will also be a string + that is the current setting of the run-time parameter with that name. + + You can get several parameters at once by passing a list or tuple of + parameter names. The return value will then be a corresponding list + of parameter settings. If you pass a dict as parameter instead, its + values will be set to the parameter settings corresponding to its keys. + + By passing the special name 'all' as the parameter, you can get a dict + of all existing configuration parameters. + """ + if isinstance(parameter, basestring): + parameter = [parameter] + values = None + elif isinstance(parameter, (list, tuple)): + values = [] + elif isinstance(parameter, dict): + values = parameter + else: + raise TypeError('The parameter must be a dict, list or string') + if not parameter: + raise TypeError('No parameter has been specified') + if isinstance(values, dict): + params = {} + else: + params = [] + for key in parameter: + if isinstance(key, basestring): + param = key.strip().lower() + else: + param = None + if not param: + raise TypeError('Invalid parameter') + if param == 'all': + q = 'SHOW ALL' + values = self.db.query(q).getresult() + values = dict(value[:2] for value in values) + break + if isinstance(values, dict): + params[param] = key + else: + params.append(param) + else: + for param in params: + q = 'SHOW %s' % (param,) + value = self.db.query(q).getresult()[0][0] + if values is None: + values = value + elif isinstance(values, list): + values.append(value) + else: + values[params[param]] = value + return values + + def set_parameter(self, parameter, value=None, local=False): + """Set the value of a run-time parameter. + + If the parameter and the value are strings, the run-time parameter + will be set to that value. If no value or None is passed as a value, + then the run-time parameter will be restored to its default value. + + You can set several parameters at once by passing a list or tuple + of parameter names, with a single value that all parameters should + be set to or with a corresponding list or tuple of values. + + You can also pass a dict as parameters. In this case, you should + not pass a value, since the values will be taken from the dict. + + By passing the special name 'all' as the parameter, you can reset + all existing settable run-time parameters to their default values. + + If you set local to True, then the command takes effect for only the + current transaction. After commit() or rollback(), the session-level + setting takes effect again. Setting local to True will appear to + have no effect if it is executed outside a transaction, since the + transaction will end immediately. + """ + if isinstance(parameter, basestring): + parameter = {parameter: value} + elif isinstance(parameter, (list, tuple)): + if isinstance(value, (list, tuple)): + parameter = dict(zip(parameter, value)) + else: + parameter = dict.fromkeys(parameter, value) + elif isinstance(parameter, dict): + if value is not None: + raise ValueError( + 'A value must not be set when parameter is a dictionary') + else: + raise TypeError('The parameter must be a dict, list or string') + if not parameter: + raise TypeError('No parameter has been specified') + params = {} + for key, value in parameter.items(): + if isinstance(key, basestring): + param = key.strip().lower() + else: + param = None + if not param: + raise TypeError('Invalid parameter') + if param == 'all': + if value is not None: + raise ValueError( + "A value must ot be set when parameter is 'all'") + params = {'all': None} + break + params[param] = value + local = local and ' LOCAL' or '' + for param, value in params.items(): + if value is None: + q = 'RESET%s %s' % (local, param) + else: + q = 'SET%s %s TO %s' % (local, param, value) + self._do_debug(q) + self.db.query(q) + def query(self, qstr, *args): """Executes a SQL command string. diff --git a/tests/test_classic_dbwrapper.py b/tests/test_classic_dbwrapper.py index 945f78dc..565fe0b7 100755 --- a/tests/test_classic_dbwrapper.py +++ b/tests/test_classic_dbwrapper.py @@ -72,61 +72,31 @@ def tearDown(self): def testAllDBAttributes(self): attributes = [ 'begin', - 'cancel', - 'clear', - 'close', - 'commit', - 'db', - 'dbname', - 'debug', - 'delete', - 'end', - 'endcopy', - 'error', - 'escape_bytea', - 'escape_identifier', - 'escape_literal', - 'escape_string', + 'cancel', 'clear', 'close', 'commit', + 'db', 'dbname', 'debug', 'delete', + 'end', 'endcopy', 'error', + 'escape_bytea', 'escape_identifier', + 'escape_literal', 'escape_string', 'fileno', - 'get', - 'get_attnames', - 'get_databases', - 'get_notice_receiver', - 'get_relations', - 'get_tables', - 'getline', - 'getlo', - 'getnotify', - 'has_table_privilege', - 'host', - 'insert', - 'inserttable', - 'locreate', - 'loimport', + 'get', 'get_attnames', 'get_databases', + 'get_notice_receiver', 'get_parameter', + 'get_relations', 'get_tables', + 'getline', 'getlo', 'getnotify', + 'has_table_privilege', 'host', + 'insert', 'inserttable', + 'locreate', 'loimport', 'notification_handler', 'options', - 'parameter', - 'pkey', - 'port', - 'protocol_version', - 'putline', + 'parameter', 'pkey', 'port', + 'protocol_version', 'putline', 'query', - 'release', - 'reopen', - 'reset', - 'rollback', - 'savepoint', - 'server_version', - 'set_notice_receiver', - 'source', - 'start', - 'status', - 'transaction', - 'tty', - 'unescape_bytea', - 'update', - 'use_regtypes', - 'user', + 'release', 'reopen', 'reset', 'rollback', + 'savepoint', 'server_version', + 'set_notice_receiver', 'set_parameter', + 'source', 'start', 'status', + 'transaction', 'tty', + 'unescape_bytea', 'update', + 'use_regtypes', 'user', ] if self.db.server_version < 90000: # PostgreSQL < 9.0 attributes.remove('escape_identifier') @@ -288,8 +258,8 @@ def setUpClass(cls): db.query("drop table if exists test cascade") db.query("create table test (" "i2 smallint, i4 integer, i8 bigint," - "d numeric, f4 real, f8 double precision, m money, " - "v4 varchar(4), c4 char(4), t text)") + " d numeric, f4 real, f8 double precision, m money," + " v4 varchar(4), c4 char(4), t text)") db.query("create or replace view test_view as" " select i4, v4 from test") db.close() @@ -441,6 +411,150 @@ def testQuote(self): self.assertEqual(f('ab\\c', 'text'), "'ab\\\\c'") self.assertEqual(f("a\\b'c", 'text'), "'a\\\\b''c'") + def testGetParameter(self): + f = self.db.get_parameter + self.assertRaises(TypeError, f) + self.assertRaises(TypeError, f, None) + self.assertRaises(TypeError, f, 42) + self.assertRaises(pg.ProgrammingError, f, 'this_does_not_exist') + r = f('standard_conforming_strings') + self.assertEqual(r, 'on') + r = f('lc_monetary') + self.assertEqual(r, 'C') + r = f('datestyle') + self.assertEqual(r, 'ISO, YMD') + r = f('bytea_output') + self.assertEqual(r, 'hex') + r = f(('bytea_output', 'lc_monetary')) + self.assertIsInstance(r, list) + self.assertEqual(r, ['hex', 'C']) + r = f(['standard_conforming_strings', 'datestyle', 'bytea_output']) + self.assertEqual(r, ['on', 'ISO, YMD', 'hex']) + s = dict.fromkeys(('bytea_output', 'lc_monetary')) + r = f(s) + self.assertIs(r, s) + self.assertEqual(r, {'bytea_output': 'hex', 'lc_monetary': 'C'}) + s = dict.fromkeys(('Bytea_Output', 'LC_Monetary')) + r = f(s) + self.assertIs(r, s) + self.assertEqual(r, {'Bytea_Output': 'hex', 'LC_Monetary': 'C'}) + + def testGetParameterServerVersion(self): + r = self.db.get_parameter('server_version_num') + self.assertIsInstance(r, str) + s = self.db.server_version + self.assertIsInstance(s, int) + self.assertEqual(r, str(s)) + + def testGetParameterAll(self): + f = self.db.get_parameter + r = f('all') + self.assertIsInstance(r, dict) + self.assertEqual(r['standard_conforming_strings'], 'on') + self.assertEqual(r['lc_monetary'], 'C') + self.assertEqual(r['DateStyle'], 'ISO, YMD') + self.assertEqual(r['bytea_output'], 'hex') + + def testSetParameter(self): + f = self.db.set_parameter + g = self.db.get_parameter + self.assertRaises(TypeError, f) + self.assertRaises(TypeError, f, None) + self.assertRaises(TypeError, f, 42) + self.assertRaises(pg.ProgrammingError, f, 'this_does_not_exist') + f('standard_conforming_strings', 'off') + self.assertEqual(g('standard_conforming_strings'), 'off') + f('datestyle', 'ISO, DMY') + self.assertEqual(g('datestyle'), 'ISO, DMY') + f(('standard_conforming_strings', 'datestyle'), ('on', 'ISO, YMD')) + self.assertEqual(g('standard_conforming_strings'), 'on') + self.assertEqual(g('datestyle'), 'ISO, YMD') + f(['standard_conforming_strings', 'datestyle'], ['off', 'ISO, DMY']) + self.assertEqual(g('standard_conforming_strings'), 'off') + self.assertEqual(g('datestyle'), 'ISO, DMY') + f({'standard_conforming_strings': 'on', 'datestyle': 'ISO, YMD'}) + self.assertEqual(g('standard_conforming_strings'), 'on') + self.assertEqual(g('datestyle'), 'ISO, YMD') + f(('default_with_oids', 'standard_conforming_strings'), 'off') + self.assertEqual(g('default_with_oids'), 'off') + self.assertEqual(g('standard_conforming_strings'), 'off') + f(['default_with_oids', 'standard_conforming_strings'], 'on') + self.assertEqual(g('default_with_oids'), 'on') + self.assertEqual(g('standard_conforming_strings'), 'on') + + def testResetParameter(self): + db = DB() + f = db.set_parameter + g = db.get_parameter + r = g('default_with_oids') + self.assertIn(r, ('on', 'off')) + dwi, not_dwi = r, r == 'on' and 'off' or 'on' + r = g('standard_conforming_strings') + self.assertIn(r, ('on', 'off')) + scs, not_scs = r, r == 'on' and 'off' or 'on' + f('default_with_oids', not_dwi) + f('standard_conforming_strings', not_scs) + self.assertEqual(g('default_with_oids'), not_dwi) + self.assertEqual(g('standard_conforming_strings'), not_scs) + f('default_with_oids') + f('standard_conforming_strings', None) + self.assertEqual(g('default_with_oids'), dwi) + self.assertEqual(g('standard_conforming_strings'), scs) + f('default_with_oids', not_dwi) + f('standard_conforming_strings', not_scs) + self.assertEqual(g('default_with_oids'), not_dwi) + self.assertEqual(g('standard_conforming_strings'), not_scs) + f(('default_with_oids', 'standard_conforming_strings')) + self.assertEqual(g('default_with_oids'), dwi) + self.assertEqual(g('standard_conforming_strings'), scs) + f('default_with_oids', not_dwi) + f('standard_conforming_strings', not_scs) + self.assertEqual(g('default_with_oids'), not_dwi) + self.assertEqual(g('standard_conforming_strings'), not_scs) + f(['default_with_oids', 'standard_conforming_strings'], None) + self.assertEqual(g('default_with_oids'), dwi) + self.assertEqual(g('standard_conforming_strings'), scs) + + def testResetParameterAll(self): + db = DB() + f = db.set_parameter + self.assertRaises(ValueError, f, 'all', 0) + self.assertRaises(ValueError, f, 'all', 'off') + g = db.get_parameter + r = g('default_with_oids') + self.assertIn(r, ('on', 'off')) + dwi, not_dwi = r, r == 'on' and 'off' or 'on' + r = g('standard_conforming_strings') + self.assertIn(r, ('on', 'off')) + scs, not_scs = r, r == 'on' and 'off' or 'on' + f('default_with_oids', not_dwi) + f('standard_conforming_strings', not_scs) + self.assertEqual(g('default_with_oids'), not_dwi) + self.assertEqual(g('standard_conforming_strings'), not_scs) + f('all') + self.assertEqual(g('default_with_oids'), dwi) + self.assertEqual(g('standard_conforming_strings'), scs) + + def testSetParameterLocal(self): + f = self.db.set_parameter + g = self.db.get_parameter + self.assertEqual(g('standard_conforming_strings'), 'on') + self.db.begin() + f('standard_conforming_strings', 'off', local=True) + self.assertEqual(g('standard_conforming_strings'), 'off') + self.db.end() + self.assertEqual(g('standard_conforming_strings'), 'on') + + def testSetParameterSession(self): + f = self.db.set_parameter + g = self.db.get_parameter + self.assertEqual(g('standard_conforming_strings'), 'on') + self.db.begin() + f('standard_conforming_strings', 'off', local=False) + self.assertEqual(g('standard_conforming_strings'), 'off') + self.db.end() + self.assertEqual(g('standard_conforming_strings'), 'off') + def testQuery(self): query = self.db.query query("drop table if exists test_table") @@ -529,9 +643,9 @@ def testPkey(self): query("create table pkeytest2 (" "c smallint, d smallint primary key)") query("create table pkeytest3 (" - "e smallint, f smallint, g smallint, " - "h smallint, i smallint, " - "primary key (f,h))") + "e smallint, f smallint, g smallint," + " h smallint, i smallint," + " primary key (f,h))") pkey = self.db.pkey self.assertRaises(KeyError, pkey, 'pkeytest0') self.assertEqual(pkey('pkeytest1'), 'b') @@ -609,7 +723,7 @@ def testGetRelations(self): self.assertNotIn('public.test', result) self.assertNotIn('public.test_view', result) - def testAttnames(self): + def testGetAttnames(self): self.assertRaises(pg.ProgrammingError, self.db.get_attnames, 'does_not_exist') self.assertRaises(pg.ProgrammingError, @@ -617,12 +731,12 @@ def testAttnames(self): for table in ('attnames_test_table', 'test table for attnames'): self.db.query('drop table if exists "%s"' % table) self.db.query('create table "%s" (' - 'a smallint, b integer, c bigint, ' - 'e numeric, f float, f2 double precision, m money, ' - 'x smallint, y smallint, z smallint, ' - 'Normal_NaMe smallint, "Special Name" smallint, ' - 't text, u char(2), v varchar(2), ' - 'primary key (y, u)) with oids' % table) + ' a smallint, b integer, c bigint,' + ' e numeric, f float, f2 double precision, m money,' + ' x smallint, y smallint, z smallint,' + ' Normal_NaMe smallint, "Special Name" smallint,' + ' t text, u char(2), v varchar(2),' + ' primary key (y, u)) with oids' % table) attributes = self.db.get_attnames(table) result = {'a': 'int', 'c': 'int', 'b': 'int', 'e': 'num', 'f': 'float', 'f2': 'float', 'm': 'money', @@ -747,13 +861,13 @@ def testGetLittleBobbyTables(self): firstname="D'Arcy", nickname='Darcey', grade='A+')) try: get('test_students', "D' Arcy") - except pg.DatabaseError as error: + except pg.DatabaseError, error: self.assertEqual(str(error), 'No such record in public.test_students where firstname = ' "'D'' Arcy'") try: get('test_students', "Robert'); TRUNCATE TABLE test_students;--") - except pg.DatabaseError as error: + except pg.DatabaseError, error: self.assertEqual(str(error), 'No such record in public.test_students where firstname = ' "'Robert''); TRUNCATE TABLE test_students;--'") diff --git a/tests/test_tutorial.py b/tests/test_tutorial.py index c7909741..055a173d 100644 --- a/tests/test_tutorial.py +++ b/tests/test_tutorial.py @@ -64,7 +64,7 @@ def test_all_steps(self): more_fruits = 'cherimaya durian eggfruit fig grapefruit'.split() if namedtuple: data = list(enumerate(more_fruits, start=3)) - else: # Pyton < 2.6 + else: # Python < 2.6 data = [(n + 3, name) for n, name in enumerate(more_fruits)] db.inserttable('fruits', data) q = db.query('select * from fruits') @@ -87,12 +87,11 @@ def test_all_steps(self): self.assertEqual(r[6], {'id': 7, 'name': 'grapefruit'}) try: rows = r = q.namedresult() - except TypeError: # Python < 2.6 - self.assertIsNone(namedtuple) - else: self.assertIsInstance(r, list) self.assertIsInstance(r[0], tuple) self.assertEqual(rows[3].name, 'durian') + except (AttributeError, TypeError): # Python < 2.6 + self.assertIsNone(namedtuple) r = db.update('fruits', banana, name=banana['name'].capitalize()) self.assertIsInstance(r, dict) self.assertEqual(r, {'id': 2, 'name': 'Banana'}) From 530570a91ecccdd816e9ab9a203427e1cca1b074 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 15 Jan 2016 01:27:05 +0000 Subject: [PATCH 093/144] Make references in the Postgres tutorial functional --- docs/contents/postgres/advanced.rst | 2 ++ docs/contents/postgres/basic.rst | 2 ++ docs/contents/postgres/func.rst | 2 ++ docs/contents/postgres/syscat.rst | 2 ++ 4 files changed, 8 insertions(+) diff --git a/docs/contents/postgres/advanced.rst b/docs/contents/postgres/advanced.rst index 7fb25ac4..1f55f5ad 100644 --- a/docs/contents/postgres/advanced.rst +++ b/docs/contents/postgres/advanced.rst @@ -1,6 +1,8 @@ Examples for advanced features ============================== +.. py:currentmodule:: pg + In this section, we show how to use some advanced features of PostgreSQL using the classic PyGreSQL interface. diff --git a/docs/contents/postgres/basic.rst b/docs/contents/postgres/basic.rst index 09f08051..d2d458d4 100644 --- a/docs/contents/postgres/basic.rst +++ b/docs/contents/postgres/basic.rst @@ -1,6 +1,8 @@ Basic examples ============== +.. py:currentmodule:: pg + In this section, we demonstrate how to use some of the very basic features of PostgreSQL using the classic PyGreSQL interface. diff --git a/docs/contents/postgres/func.rst b/docs/contents/postgres/func.rst index 7ad3dcc5..4331d193 100644 --- a/docs/contents/postgres/func.rst +++ b/docs/contents/postgres/func.rst @@ -1,6 +1,8 @@ Examples for using SQL functions ================================ +.. py:currentmodule:: pg + We assume that you have already created a connection to the PostgreSQL database, as explained in the :doc:`basic`:: diff --git a/docs/contents/postgres/syscat.rst b/docs/contents/postgres/syscat.rst index 95ba95ae..338f1ebc 100644 --- a/docs/contents/postgres/syscat.rst +++ b/docs/contents/postgres/syscat.rst @@ -1,6 +1,8 @@ Examples for using the system catalogs ====================================== +.. py:currentmodule:: pg + The system catalogs are regular tables where PostgreSQL stores schema metadata, such as information about tables and columns, and internal bookkeeping information. You can drop and recreate the tables, add columns, insert and From 01a20cc1c70b1c2f8d70e3e1ef3d86b511a32d1c Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 15 Jan 2016 11:06:51 +0000 Subject: [PATCH 094/144] Improve the get/set_parameter methods In addition to a list, also allow a set as parameter. --- docs/contents/pg/db_wrapper.rst | 24 ++++++++++-------- pg.py | 41 +++++++++++++++++++++--------- tests/test_classic_dbwrapper.py | 44 ++++++++++++++++++++++++--------- 3 files changed, 76 insertions(+), 33 deletions(-) diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index bf7d8074..0e3acfb5 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -149,10 +149,13 @@ get/set_parameter -- get or set run-time parameters If the parameter is a string, the return value will also be a string that is the current setting of the run-time parameter with that name. -You can get several parameters at once by passing a list or tuple of -parameter names. The return value will then be a corresponding list -of parameter settings. If you pass a dict as parameter instead, its -values will be set to the parameter settings corresponding to its keys. +You can get several parameters at once by passing a list, set or dict. +When passing a list of parameter names, the return value will be a +corresponding list of parameter settings. When passing a set of +parameter names, a new dict will be returned, mapping these parameter +names to their settings. Finally, if you pass a dict as parameter, +its values will be set to the current parameter settings corresponding +to its keys. By passing the special name `'all'` as the parameter, you can get a dict of all existing configuration parameters. @@ -175,12 +178,13 @@ If the parameter and the value are strings, the run-time parameter will be set to that value. If no value or *None* is passed as a value, then the run-time parameter will be restored to its default value. -You can set several parameters at once by passing a list or tuple -of parameter names, with a single value that all parameters should -be set to or with a corresponding list or tuple of values. - -You can also pass a dict as parameters. In this case, you should -not pass a value, since the values will be taken from the dict. +You can set several parameters at once by passing a list of parameter +names, together with a single value that all parameters should be +set to or with a corresponding list of values. You can also pass +the parameters as a set if you only provide a single value. +Finally, you can pass a dict with parameter names as keys. In this +case, you should not pass a value, since the values for the parameters +will be taken from the dict. By passing the special name `'all'` as the parameter, you can reset all existing settable run-time parameters to their default values. diff --git a/pg.py b/pg.py index 83d31f5d..2be206d1 100644 --- a/pg.py +++ b/pg.py @@ -523,10 +523,13 @@ def get_parameter(self, parameter): If the parameter is a string, the return value will also be a string that is the current setting of the run-time parameter with that name. - You can get several parameters at once by passing a list or tuple of - parameter names. The return value will then be a corresponding list - of parameter settings. If you pass a dict as parameter instead, its - values will be set to the parameter settings corresponding to its keys. + You can get several parameters at once by passing a list, set or dict. + When passing a list of parameter names, the return value will be a + corresponding list of parameter settings. When passing a set of + parameter names, a new dict will be returned, mapping these parameter + names to their settings. Finally, if you pass a dict as parameter, + its values will be set to the current parameter settings corresponding + to its keys. By passing the special name 'all' as the parameter, you can get a dict of all existing configuration parameters. @@ -536,10 +539,13 @@ def get_parameter(self, parameter): values = None elif isinstance(parameter, (list, tuple)): values = [] + elif isinstance(parameter, (set, frozenset)): + values = {} elif isinstance(parameter, dict): values = parameter else: - raise TypeError('The parameter must be a dict, list or string') + raise TypeError( + 'The parameter must be a string, list, set or dict') if not parameter: raise TypeError('No parameter has been specified') if isinstance(values, dict): @@ -581,12 +587,13 @@ def set_parameter(self, parameter, value=None, local=False): will be set to that value. If no value or None is passed as a value, then the run-time parameter will be restored to its default value. - You can set several parameters at once by passing a list or tuple - of parameter names, with a single value that all parameters should - be set to or with a corresponding list or tuple of values. - - You can also pass a dict as parameters. In this case, you should - not pass a value, since the values will be taken from the dict. + You can set several parameters at once by passing a list of parameter + names, together with a single value that all parameters should be + set to or with a corresponding list of values. You can also pass + the parameters as a set if you only provide a single value. + Finally, you can pass a dict with parameter names as keys. In this + case, you should not pass a value, since the values for the parameters + will be taken from the dict. By passing the special name 'all' as the parameter, you can reset all existing settable run-time parameters to their default values. @@ -604,12 +611,22 @@ def set_parameter(self, parameter, value=None, local=False): parameter = dict(zip(parameter, value)) else: parameter = dict.fromkeys(parameter, value) + elif isinstance(parameter, (set, frozenset)): + if isinstance(value, (list, tuple, set, frozenset)): + value = set(value) + if len(value) == 1: + value = value.pop() + if not(value is None or isinstance(value, basestring)): + raise ValueError('A single value must be specified' + ' when parameter is a set') + parameter = dict.fromkeys(parameter, value) elif isinstance(parameter, dict): if value is not None: raise ValueError( 'A value must not be set when parameter is a dictionary') else: - raise TypeError('The parameter must be a dict, list or string') + raise TypeError( + 'The parameter must be a string, list, set or dict') if not parameter: raise TypeError('No parameter has been specified') params = {} diff --git a/tests/test_classic_dbwrapper.py b/tests/test_classic_dbwrapper.py index 565fe0b7..f7b107cb 100755 --- a/tests/test_classic_dbwrapper.py +++ b/tests/test_classic_dbwrapper.py @@ -425,19 +425,25 @@ def testGetParameter(self): self.assertEqual(r, 'ISO, YMD') r = f('bytea_output') self.assertEqual(r, 'hex') - r = f(('bytea_output', 'lc_monetary')) + r = f(['bytea_output', 'lc_monetary']) self.assertIsInstance(r, list) self.assertEqual(r, ['hex', 'C']) - r = f(['standard_conforming_strings', 'datestyle', 'bytea_output']) + r = f(('standard_conforming_strings', 'datestyle', 'bytea_output')) self.assertEqual(r, ['on', 'ISO, YMD', 'hex']) + r = f(set(['bytea_output', 'lc_monetary'])) + self.assertIsInstance(r, dict) + self.assertEqual(r, {'bytea_output': 'hex', 'lc_monetary': 'C'}) + r = f(set(['Bytea_Output', ' LC_Monetary '])) + self.assertIsInstance(r, dict) + self.assertEqual(r, {'Bytea_Output': 'hex', ' LC_Monetary ': 'C'}) s = dict.fromkeys(('bytea_output', 'lc_monetary')) r = f(s) self.assertIs(r, s) self.assertEqual(r, {'bytea_output': 'hex', 'lc_monetary': 'C'}) - s = dict.fromkeys(('Bytea_Output', 'LC_Monetary')) + s = dict.fromkeys(('Bytea_Output', ' LC_Monetary ')) r = f(s) self.assertIs(r, s) - self.assertEqual(r, {'Bytea_Output': 'hex', 'LC_Monetary': 'C'}) + self.assertEqual(r, {'Bytea_Output': 'hex', ' LC_Monetary ': 'C'}) def testGetParameterServerVersion(self): r = self.db.get_parameter('server_version_num') @@ -466,21 +472,30 @@ def testSetParameter(self): self.assertEqual(g('standard_conforming_strings'), 'off') f('datestyle', 'ISO, DMY') self.assertEqual(g('datestyle'), 'ISO, DMY') - f(('standard_conforming_strings', 'datestyle'), ('on', 'ISO, YMD')) + f(['standard_conforming_strings', 'datestyle'], ['on', 'ISO, DMY']) self.assertEqual(g('standard_conforming_strings'), 'on') - self.assertEqual(g('datestyle'), 'ISO, YMD') - f(['standard_conforming_strings', 'datestyle'], ['off', 'ISO, DMY']) - self.assertEqual(g('standard_conforming_strings'), 'off') self.assertEqual(g('datestyle'), 'ISO, DMY') - f({'standard_conforming_strings': 'on', 'datestyle': 'ISO, YMD'}) + f(['default_with_oids', 'standard_conforming_strings'], 'off') + self.assertEqual(g('default_with_oids'), 'off') + self.assertEqual(g('standard_conforming_strings'), 'off') + f(('standard_conforming_strings', 'datestyle'), ('on', 'ISO, YMD')) self.assertEqual(g('standard_conforming_strings'), 'on') self.assertEqual(g('datestyle'), 'ISO, YMD') f(('default_with_oids', 'standard_conforming_strings'), 'off') self.assertEqual(g('default_with_oids'), 'off') self.assertEqual(g('standard_conforming_strings'), 'off') - f(['default_with_oids', 'standard_conforming_strings'], 'on') + f(set(['default_with_oids', 'standard_conforming_strings']), 'on') self.assertEqual(g('default_with_oids'), 'on') self.assertEqual(g('standard_conforming_strings'), 'on') + self.assertRaises(ValueError, f, set([ 'default_with_oids', + 'standard_conforming_strings']), ['off', 'on']) + f(set(['default_with_oids', 'standard_conforming_strings']), + ['off', 'off']) + self.assertEqual(g('default_with_oids'), 'off') + self.assertEqual(g('standard_conforming_strings'), 'off') + f({'standard_conforming_strings': 'on', 'datestyle': 'ISO, YMD'}) + self.assertEqual(g('standard_conforming_strings'), 'on') + self.assertEqual(g('datestyle'), 'ISO, YMD') def testResetParameter(self): db = DB() @@ -504,6 +519,13 @@ def testResetParameter(self): f('standard_conforming_strings', not_scs) self.assertEqual(g('default_with_oids'), not_dwi) self.assertEqual(g('standard_conforming_strings'), not_scs) + f(['default_with_oids', 'standard_conforming_strings'], None) + self.assertEqual(g('default_with_oids'), dwi) + self.assertEqual(g('standard_conforming_strings'), scs) + f('default_with_oids', not_dwi) + f('standard_conforming_strings', not_scs) + self.assertEqual(g('default_with_oids'), not_dwi) + self.assertEqual(g('standard_conforming_strings'), not_scs) f(('default_with_oids', 'standard_conforming_strings')) self.assertEqual(g('default_with_oids'), dwi) self.assertEqual(g('standard_conforming_strings'), scs) @@ -511,7 +533,7 @@ def testResetParameter(self): f('standard_conforming_strings', not_scs) self.assertEqual(g('default_with_oids'), not_dwi) self.assertEqual(g('standard_conforming_strings'), not_scs) - f(['default_with_oids', 'standard_conforming_strings'], None) + f(set(['default_with_oids', 'standard_conforming_strings']), None) self.assertEqual(g('default_with_oids'), dwi) self.assertEqual(g('standard_conforming_strings'), scs) From 094d42008945afff4ff942e47787541a82b688ee Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 15 Jan 2016 14:25:31 +0000 Subject: [PATCH 095/144] Add method truncate() to DB wrapper class This methods can be used to quickly truncate tables. Since this is pretty useful and will not break anything, I have also back ported this addition to the 4.x branch. Everything is well documented and tested, of course. --- docs/contents/changelog.rst | 2 + docs/contents/pg/db_wrapper.rst | 29 +++++ pg.py | 64 +++++++++- tests/test_classic_dbwrapper.py | 204 +++++++++++++++++++++++++++++++- 4 files changed, 294 insertions(+), 5 deletions(-) diff --git a/docs/contents/changelog.rst b/docs/contents/changelog.rst index d51afac4..5092c8ce 100644 --- a/docs/contents/changelog.rst +++ b/docs/contents/changelog.rst @@ -9,6 +9,8 @@ Version 4.2 - Force build to compile with no errors. - New methods get_parameters() and set_parameters() in the classic interface which can be used to get or set run-time parameters. +- New method truncate() in the classic interface that can be used to quickly + empty a table or a set of tables. - Fix decimal point handling. - Add option to return boolean values as bool objects. - Add option to return money values as string. diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index 0e3acfb5..d5ee670a 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -415,6 +415,35 @@ as munged by get or passed as keyword, or on the primary key of the table. The return value is the number of deleted rows (i.e. 0 if the row did not exist and 1 if the row was deleted). +truncate -- Quickly empty database tables +----------------------------------------- + +.. method:: DB.truncate(self, table, [restart], [cascade], [only]): + + Empty a table or set of tables + + :param table: the name of the table(s) + :type table: str, list or set + :param bool restart: whether table sequences should be restarted + :param bool cascade: whether referenced tables should also be truncated + :param only: whether only parent tables should be truncated + :type only: bool or list + +This method quickly removes all rows from the given table or set +of tables. It has the same effect as an unqualified DELETE on each +table, but since it does not actually scan the tables it is faster. +Furthermore, it reclaims disk space immediately, rather than requiring +a subsequent VACUUM operation. This is most useful on large tables. + +If *restart* is set to `True`, sequences owned by columns of the truncated +table(s) are automatically restarted. If *cascade* is set to `True`, it +also truncates all tables that have foreign-key references to any of +the named tables. If the parameter *only* is not set to `True`, all the +descendant tables (if any) will also be truncated. Optionally, a ``*`` +can be specified after the table name to explicitly indicate that +descendant tables are included. If the parameter *table* is a list, +the parameter *only* can also be a list of corresponding boolean values. + escape_literal -- escape a literal string for use within SQL ------------------------------------------------------------ diff --git a/pg.py b/pg.py index 2be206d1..586b2f8f 100644 --- a/pg.py +++ b/pg.py @@ -622,8 +622,8 @@ def set_parameter(self, parameter, value=None, local=False): parameter = dict.fromkeys(parameter, value) elif isinstance(parameter, dict): if value is not None: - raise ValueError( - 'A value must not be set when parameter is a dictionary') + raise ValueError('A value must not be specified' + ' when parameter is a dictionary') else: raise TypeError( 'The parameter must be a string, list, set or dict') @@ -639,8 +639,8 @@ def set_parameter(self, parameter, value=None, local=False): raise TypeError('Invalid parameter') if param == 'all': if value is not None: - raise ValueError( - "A value must ot be set when parameter is 'all'") + raise ValueError('A value must ot be specified' + " when parameter is 'all'") params = {'all': None} break params[param] = value @@ -1099,6 +1099,62 @@ def delete(self, cl, d=None, **kw): self._do_debug(q) return int(self.db.query(q)) + def truncate(self, table, restart=False, cascade=False, only=False): + """Empty a table or set of tables. + + This method quickly removes all rows from the given table or set + of tables. It has the same effect as an unqualified DELETE on each + table, but since it does not actually scan the tables it is faster. + Furthermore, it reclaims disk space immediately, rather than requiring + a subsequent VACUUM operation. This is most useful on large tables. + + If restart is set to True, sequences owned by columns of the truncated + table(s) are automatically restarted. If cascade is set to True, it + also truncates all tables that have foreign-key references to any of + the named tables. If the parameter only is not set to True, all the + descendant tables (if any) will also be truncated. Optionally, a '*' + can be specified after the table name to explicitly indicate that + descendant tables are included. + """ + if isinstance(table, basestring): + only = {table: only} + table = [table] + elif isinstance(table, (list, tuple)): + if isinstance(only, (list, tuple)): + only = dict(zip(table, only)) + else: + only = dict.fromkeys(table, only) + elif isinstance(table, (set, frozenset)): + only = dict.fromkeys(table, only) + else: + raise TypeError('The table must be a string, list or set') + if not (restart is None or isinstance(restart, (bool, int))): + raise TypeError('Invalid type for the restart option') + if not (cascade is None or isinstance(cascade, (bool, int))): + raise TypeError('Invalid type for the cascade option') + tables = [] + for t in table: + u = only.get(t) + if not (u is None or isinstance(u, (bool, int))): + raise TypeError('Invalid type for the only option') + if t.endswith('*'): + if u: + raise ValueError( + 'Contradictory table name and only options') + t = t[:-1].rstrip() + t = self._add_schema(t) + if u: + t = 'ONLY %s' % t + tables.append(t) + q = ['TRUNCATE', ', '.join(tables)] + if restart: + q.append('RESTART IDENTITY') + if cascade: + q.append('CASCADE') + q = ' '.join(q) + self._do_debug(q) + return self.query(q) + def notification_handler(self, event, callback, arg_dict={}, timeout=None): """Get notification handler that will run the given callback.""" return NotificationHandler(self.db, event, callback, arg_dict, timeout) diff --git a/tests/test_classic_dbwrapper.py b/tests/test_classic_dbwrapper.py index f7b107cb..1f3bea36 100755 --- a/tests/test_classic_dbwrapper.py +++ b/tests/test_classic_dbwrapper.py @@ -94,7 +94,7 @@ def testAllDBAttributes(self): 'savepoint', 'server_version', 'set_notice_receiver', 'set_parameter', 'source', 'start', 'status', - 'transaction', 'tty', + 'transaction', 'truncate', 'tty', 'unescape_bytea', 'update', 'use_regtypes', 'user', ] @@ -1148,6 +1148,208 @@ def testDeleteWithCompositeKey(self): self.assertEqual(r, ['f']) query("drop table %s" % table) + def testTruncate(self): + truncate = self.db.truncate + self.assertRaises(TypeError, truncate, None) + self.assertRaises(TypeError, truncate, 42) + self.assertRaises(TypeError, truncate, dict(test_table=None)) + query = self.db.query + query("drop table if exists test_table") + query("create table test_table (n smallint)") + for i in range(3): + query("insert into test_table values (1)") + q = "select count(*) from test_table" + r = query(q).getresult()[0][0] + self.assertEqual(r, 3) + truncate('test_table') + r = query(q).getresult()[0][0] + self.assertEqual(r, 0) + for i in range(3): + query("insert into test_table values (1)") + r = query(q).getresult()[0][0] + self.assertEqual(r, 3) + truncate('public.test_table') + r = query(q).getresult()[0][0] + self.assertEqual(r, 0) + query("drop table if exists test_table_2") + query('create table test_table_2 (n smallint)') + for t in (list, tuple, set): + for i in range(3): + query("insert into test_table values (1)") + query("insert into test_table_2 values (2)") + q = ("select (select count(*) from test_table)," + " (select count(*) from test_table_2)") + r = query(q).getresult()[0] + self.assertEqual(r, (3, 3)) + truncate(t(['test_table', 'test_table_2'])) + r = query(q).getresult()[0] + self.assertEqual(r, (0, 0)) + query("drop table test_table_2") + query("drop table test_table") + + def testTruncateRestart(self): + truncate = self.db.truncate + self.assertRaises(TypeError, truncate, 'test_table', restart='invalid') + query = self.db.query + query("drop table if exists test_table") + query("create table test_table (n serial, t text)") + for n in range(3): + query("insert into test_table (t) values ('test')") + q = "select count(n), min(n), max(n) from test_table" + r = query(q).getresult()[0] + self.assertEqual(r, (3, 1, 3)) + truncate('test_table') + r = query(q).getresult()[0] + self.assertEqual(r, (0, None, None)) + for n in range(3): + query("insert into test_table (t) values ('test')") + r = query(q).getresult()[0] + self.assertEqual(r, (3, 4, 6)) + truncate('test_table', restart=True) + r = query(q).getresult()[0] + self.assertEqual(r, (0, None, None)) + for n in range(3): + query("insert into test_table (t) values ('test')") + r = query(q).getresult()[0] + self.assertEqual(r, (3, 1, 3)) + query("drop table test_table") + + def testTruncateCascade(self): + truncate = self.db.truncate + self.assertRaises(TypeError, truncate, 'test_table', cascade='invalid') + query = self.db.query + query("drop table if exists test_child") + query("drop table if exists test_parent") + query("create table test_parent (n smallint primary key)") + query("create table test_child (" + " n smallint primary key references test_parent (n))") + for n in range(3): + query("insert into test_parent (n) values (%d)" % n) + query("insert into test_child (n) values (%d)" % n) + q = ("select (select count(*) from test_parent)," + " (select count(*) from test_child)") + r = query(q).getresult()[0] + self.assertEqual(r, (3, 3)) + self.assertRaises(pg.ProgrammingError, truncate, 'test_parent') + truncate(['test_parent', 'test_child']) + r = query(q).getresult()[0] + self.assertEqual(r, (0, 0)) + for n in range(3): + query("insert into test_parent (n) values (%d)" % n) + query("insert into test_child (n) values (%d)" % n) + r = query(q).getresult()[0] + self.assertEqual(r, (3, 3)) + truncate('test_parent', cascade=True) + r = query(q).getresult()[0] + self.assertEqual(r, (0, 0)) + for n in range(3): + query("insert into test_parent (n) values (%d)" % n) + query("insert into test_child (n) values (%d)" % n) + r = query(q).getresult()[0] + self.assertEqual(r, (3, 3)) + truncate('test_child') + r = query(q).getresult()[0] + self.assertEqual(r, (3, 0)) + self.assertRaises(pg.ProgrammingError, truncate, 'test_parent') + truncate('test_parent', cascade=True) + r = query(q).getresult()[0] + self.assertEqual(r, (0, 0)) + query("drop table test_child") + query("drop table test_parent") + + def testTruncateOnly(self): + truncate = self.db.truncate + self.assertRaises(TypeError, truncate, 'test_table', only='invalid') + query = self.db.query + query("drop table if exists test_child") + query("drop table if exists test_parent") + query("create table test_parent (n smallint)") + query("create table test_child (" + " m smallint) inherits (test_parent)") + for n in range(3): + query("insert into test_parent (n) values (1)") + query("insert into test_child (n, m) values (2, 3)") + q = ("select (select count(*) from test_parent)," + " (select count(*) from test_child)") + r = query(q).getresult()[0] + self.assertEqual(r, (6, 3)) + truncate('test_parent') + r = query(q).getresult()[0] + self.assertEqual(r, (0, 0)) + for n in range(3): + query("insert into test_parent (n) values (1)") + query("insert into test_child (n, m) values (2, 3)") + r = query(q).getresult()[0] + self.assertEqual(r, (6, 3)) + truncate('test_parent*') + r = query(q).getresult()[0] + self.assertEqual(r, (0, 0)) + for n in range(3): + query("insert into test_parent (n) values (1)") + query("insert into test_child (n, m) values (2, 3)") + r = query(q).getresult()[0] + self.assertEqual(r, (6, 3)) + truncate('test_parent', only=True) + r = query(q).getresult()[0] + self.assertEqual(r, (3, 3)) + truncate('test_parent', only=False) + r = query(q).getresult()[0] + self.assertEqual(r, (0, 0)) + self.assertRaises(ValueError, truncate, 'test_parent*', only=True) + truncate('test_parent*', only=False) + query("drop table if exists test_parent_2") + query("create table test_parent_2 (n smallint)") + query("drop table if exists test_child_2") + query("create table test_child_2 (" + " m smallint) inherits (test_parent_2)") + for n in range(3): + query("insert into test_parent (n) values (1)") + query("insert into test_child (n, m) values (2, 3)") + query("insert into test_parent_2 (n) values (1)") + query("insert into test_child_2 (n, m) values (2, 3)") + q = ("select (select count(*) from test_parent)," + " (select count(*) from test_child)," + " (select count(*) from test_parent_2)," + " (select count(*) from test_child_2)") + r = query(q).getresult()[0] + self.assertEqual(r, (6, 3, 6, 3)) + truncate(['test_parent', 'test_parent_2'], only=[False, True]) + r = query(q).getresult()[0] + self.assertEqual(r, (0, 0, 3, 3)) + truncate(['test_parent', 'test_parent_2'], only=False) + r = query(q).getresult()[0] + self.assertEqual(r, (0, 0, 0, 0)) + self.assertRaises(ValueError, truncate, + ['test_parent*', 'test_child'], only=[True, False]) + truncate(['test_parent*', 'test_child'], only=[False, True]) + query("drop table test_child_2") + query("drop table test_parent_2") + query("drop table test_child") + query("drop table test_parent") + + def testTruncateQuoted(self): + truncate = self.db.truncate + query = self.db.query + table = "test table for truncate()" + query('drop table if exists "%s"' % table) + query('create table "%s" (n smallint)' % table) + for i in range(3): + query('insert into "%s" values (1)' % table) + q = 'select count(*) from "%s"' % table + r = query(q).getresult()[0][0] + self.assertEqual(r, 3) + truncate(table) + r = query(q).getresult()[0][0] + self.assertEqual(r, 0) + for i in range(3): + query('insert into "%s" values (1)' % table) + r = query(q).getresult()[0][0] + self.assertEqual(r, 3) + truncate('public."%s"' % table) + r = query(q).getresult()[0][0] + self.assertEqual(r, 0) + query('drop table "%s"' % table) + def testTransaction(self): query = self.db.query query("drop table if exists test_table") From 041299b366c42bf40a88c96dee2d8363a7af02bc Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 15 Jan 2016 14:27:15 +0000 Subject: [PATCH 096/144] Forgot "versionadded" hint in the docs --- docs/contents/pg/db_wrapper.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index d5ee670a..971ead7e 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -444,6 +444,8 @@ can be specified after the table name to explicitly indicate that descendant tables are included. If the parameter *table* is a list, the parameter *only* can also be a list of corresponding boolean values. +.. versionadded:: 4.2 + escape_literal -- escape a literal string for use within SQL ------------------------------------------------------------ From 7a6bb4f40c3e7c7d2be4187ce214d899cbdeb45f Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 15 Jan 2016 15:02:58 +0000 Subject: [PATCH 097/144] Update comment in mktar script The setup script also includes the docs now, so it now does the same as the mktar script does. --- mktar | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mktar b/mktar index 99074a9b..e5413cc5 100755 --- a/mktar +++ b/mktar @@ -26,8 +26,14 @@ else fi # Package up as a source tarball in the distribution directory. -# Note that this does essentially the same as "python setup.py sdist", -# except this also makes the docs and bundles them as source and html. + +echo "Making source tarball..." + +# Make sure that the documentation has been built. + +./mkdocs + +# The following does essentially the same as "python setup.py sdist". TD=PyGreSQL-$VERSION TF=$DISTDIR/$TD.tgz @@ -41,10 +47,6 @@ DOCFILES="docs/Makefile docs/make.bat docs/*.rst HTMLFILES="docs/_build/html" TESTFILES="tests/*.py" -echo "Making source tarball..." - -./mkdocs - rm -rf $TD mkdir $TD mkdir -p $TD/docs/_build/html From 83e7155468f82f91684e9b1e2fa5468219d909a4 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 15 Jan 2016 15:23:38 +0000 Subject: [PATCH 098/144] Mention the SQLSTATE error code in the pgdb docs --- docs/contents/pg/connection.rst | 4 ++-- docs/contents/pgdb/cursor.rst | 7 +++++++ docs/contents/pgdb/module.rst | 3 +++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/contents/pg/connection.rst b/docs/contents/pg/connection.rst index a009d693..612db3d9 100644 --- a/docs/contents/pg/connection.rst +++ b/docs/contents/pg/connection.rst @@ -61,8 +61,8 @@ need to be escaped, making this an effective way to pass arbitrary or unknown data without worrying about SQL injection or syntax errors. When the database could not process the query, a :exc:`pg.ProgrammingError` or -a :exc:`pg.InternalError` is raised. You can check the ``SQLSTATE`` code of -this error by reading its :attr:`sqlstate` attribute. +a :exc:`pg.InternalError` is raised. You can check the ``SQLSTATE`` error code +of this error by reading its :attr:`sqlstate` attribute. Example:: diff --git a/docs/contents/pgdb/cursor.rst b/docs/contents/pgdb/cursor.rst index 835528b2..c56d917c 100644 --- a/docs/contents/pgdb/cursor.rst +++ b/docs/contents/pgdb/cursor.rst @@ -91,6 +91,13 @@ The parameters may also be specified as list of tuples to e.g. insert multiple rows in a single operation, but this kind of usage is deprecated: :meth:`pgdbCursor.executemany` should be used instead. +Note that in case this method raises a :exception:`DatabaseError`, you can +get information about the error condition that has occurred by introspecting +its :attr:`DatabaseError.sqlstate` attribute, which will be the ``SQLSTATE`` +error code associated with the error. Applications that need to know which +error condition has occurred should usually test the error code, rather than +looking at the textual error message. + executemany -- execute many similar database operations ------------------------------------------------------- diff --git a/docs/contents/pgdb/module.rst b/docs/contents/pgdb/module.rst index b0891ac6..cff00e2c 100644 --- a/docs/contents/pgdb/module.rst +++ b/docs/contents/pgdb/module.rst @@ -82,6 +82,9 @@ The errors that can be raised by the :mod:`pgdb` module are the following: Exception raised for errors that are related to the database. + In PyGreSQL, this also has a :attr:`DatabaseError.sqlstate` attribute + that contains the ``SQLSTATE`` error code of this error. + .. exception:: DataError Exception raised for errors that are due to problems with the processed From 18ce12b3f9fd16c5ef46d05f775ad928e585fb96 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 15 Jan 2016 16:44:24 +0000 Subject: [PATCH 099/144] Add new logo to the sidebar of the documentation --- docs/_static/favicon.ico | Bin 894 -> 9326 bytes docs/_static/pygresql.png | Bin 0 -> 9719 bytes docs/conf.py | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/_static/pygresql.png diff --git a/docs/_static/favicon.ico b/docs/_static/favicon.ico index 89522b71b48dada1addde2cb64d3159cd0ea5657..40ea652f6718f6908f53a58d664d01fd1f22ca44 100644 GIT binary patch literal 9326 zcmeHL33yG{7G8;j5F{ajhDZ#lDaO#4;+lsDG8iJGA)<=Vr+tkM*O>WU!M!}RUZ{rl zRYwo4p`}HPB~G=fO_i#aYD0;H+L`@Z*meXslb*4=xbwg0vD-e>Q% z{&R_(s0Mj@5^6V^;Y!q#i2VJP{#G}l4R~fSDE+oRL__NnwZ#}f2*U&p)qP8k!!q@R z^g1e2?_)CcLEZO+O#M#E6#9uwhSM?)I1b@;kxT<2gC&`Ud@9q>voa0;T&59R&dD_L z3z@TQzT^Ew=#`C1p$6M+7r>9bVa84Z)BSPO(`uJO|*El z6D^H$qGb_8%f6N=9qo)LqKx=D_2W3YYGXU|KaRl&mwm z(uU{8(U$C4^v(}56&>6~S1(_n8~0?gRa6kO*@!VlvMk?sFaLb_=MMbQb|74op|zVU z4du6)i>q5bFYQT-5slsIc^VA;>`xwhv{P3vAA`Xl`s7@*PJJ(*ruM#*b4~a94SM$S zwfAe(tm|PI@Q9y3?~fW4UaPK0JA+{)dIm!n4Ak{#XfOmrX9x{#+aWx>k#`e=p+EYe zeZzbA!GM64ygw|wZ}0FP-2(9dO2eS=(B3`TgtzLB{$TVwhxQHW5;Q!h3;N;c10B+( zWBZ`iyswslL)2Df{hz)s9%ua_1_+POzgB_|$`pJ^rXHx(nA@{ZrpLG(ktqbiV{k9T zU>=8iBj)m0+y}LY$B5A)Cfnn3f8hE<*B>#uKjNr?%Y9$QhHkATPuLACEl4^FspWkbpTPak;{13g$NzzLPPR z6!@I=olJ9z%W2L<$oEPh>xGHC!$b(koRGterFPThRGE%gh_}<1W z+P&FKd#=m0|BdyuzeJ`3Z>*z(@4ie&cfU?2_HLt7`?rhSdFJpwI(PCQeN}Xnu9VAk z_1mvSKK=RXcU1Dzk0P&@{&Is#Z8F_qGL@Cxq4GPWbnk9CRouHv6&3eH-nH4(+-uLj zf503p{f_c>|3?}PdHvS_o=&cA?j8-BH1B-h5xD8~pl;gSzvZL%8L-w#l0H<_fjtY} zs5WA_>p`i9KnHdxK-pMj#K_upJ$!uq0$K%jNaXs0D*BaEXUux`xy&|?1_i%XRX=@1 zSor9%kb22i-6WZhxA=4^zW#8Z$;e<>b6{Mm@oA6 zRlQ3sjuz@pE1vwDVbJqJZ?@~V*Fu(R+{{0qprBiDNa&=3sAWRGS=#x25ylL2_wZ_h zO*0h~Z0Q|3VCcM2tAxHl`b&|70lhpLaCJNWaLdHl_-TJt)CCsBK|j0^|CViL^QRx2 z#J$C8)2}PP&^t7oMb4H(sQcl`O*N-%Sm69mL@G)lygiOuuZ|ASj_c+Lp}GvzO+T@dlmgD zMgIa{yIr3k&4r%7L0(?6PhhL6e@2=M{bWUp-#KEy2I*b+U!C{F8EHXUTH1S>THoL= zZr7_BZynb^{CC^G#tZ&i&pA&qKo&wC#8bN4c?#zyA>b?GyAV91ALk$7DE;tl$oYo) zO*jNOn|})qN3I@@94)>BFlCj?3%zKOi6x_05VN(L#8S1 z$fRC|R=Skj3;AS@IT;Wlbho-h7=bZ*3*(+`+{8$(qzY zw05I~))uU#wcu;(<`1VAw{4=A7DRx%jHmqP$I+(636RNx#}uSb0hgIBxXi2IFR$f3 z0hvv&ndi~Aym|DtHBEf?Z(qNdc5Fzeof~tA^PTrL<t(R=|cLl=m-@{$Ef(z6LjJ1N&4p88M<^{qRYjf(Ran4)0GS79JthvS1!`^ z?=R8yA1>34>sRT9ombtgkm=TqpXm0@5-Pm~j)i_%=`AV;$GTfyMt8xtRF0+pPXq1$ z$?{>BCXMRUs?#W--D{=4dx2xTT%4VqU0hsU-Q3*V>o)Cs_xG3>QWgt|F#*F)*vrFvJYQ7pSuG0 z@brHFHyOw!rxASM57;>e_&Td)$9E4m(bLJvpN}f6?Erry*2>G$eGjwX@8n$P1_m{9 zfP2msaQv)%h=Eqv`8olu>nd>UTs;pDPtP_c6>q)sU<0lAi&NJw(114_s==+h9*l3T z96GUec6h^v-dig1u@A;C8Zlz**a;I7P^M2G#ODsU_l8P*;)C%H+M5j&xX+qOyvgP; z#NH_#a-Fsn1Y;4wiKfjhO_5Po&I{#Xg!A9|({aiF&oVT~PPv}~cH6plW z%S7G&uz_Ck=uIo=-nHCqQ0fyeQsgtX$5J= z^FYZ1t+?)g5ia;o1|rSwM4T4=;)T}n1U1#eJtH*y1y;e4*V#_(kbN zf8@2T!YQ-2-(@9sqa1Cq9A_mkFP76HHX3RRmY>r^%Xn(I7(8Bb~I{Yp6w7qwX9piWoH*aJm_)TO#<5xR| zrOkzRAD_zQTP?rvT@&R3ds~H@udz3cM&n|3-DoyHsokT)EqJu>^?lZkKxMhbZ@DPA z?{@DT5(az(yNzFSk-7j@+N%TZdVlTh17FVgRe1jMLwFOh%H++)$&5uCrx?SS?MY!q zGawqpc89#e{y$~0TFRJh53`y0(#>Y0aVoa&+w#2=xf!EznzjJN&MrOv9%E(yNZfmM z|4;05#XS(lpV*0#va-ku7M6P1JlYK3^?GyQ}}IA@p||W#s+#lu?Az-2`1qKZR~Z`>qu?E1pe;{t01Lnb zs`v*UvyX~V@NrcRkb2`3GZ)$A```yNb}+_{N04$T6%L6C6Ifr(KOUY3T=>UKs+21Z zq5kiTEq0v$cw9M2fyXCn0Z3=$PG$Z*Z$;{6%Ny@T~`;s*dF@{A6qYnHql zo0yiC81dH+u8G~T9gBKA!}MfQ*s9AI^~(@(fMSMxq=P)PX|vfk`jdaYn40?1dtc*n R{#nEh;P1gd%m48X{0kTI5}N=3 literal 894 zcmb7@F$%&^5JXojY*UG)cGlWh3LeDPA~)~?-oRUU2oEC&7UBis3j@QF|B)CspWWHF zlO@533BRTZ&PAN25%Y*xb0fu)yVv_`Pj7gw4rj;lerZ29%4a(xq~|=7f;mU>I14ua z*aT7fgJttEJm&WAx=xWdJZXFEBMPFf>iR=5YW300(qQO+^RV0}&NA07Hc@EdT%j32;bRa{vGe z@Bjb`@Bu=sG?)MY00d`2O+f$vv5yP9vYb1A3FDd3oA&+Q`}%u}@jMSs(fAQQ-Y@WfolXHT&U6ZZ zai&uMj5D1AV4Ud`0OL&Lc)!3&O2{_nIL=Qq11|5N%iBBP;vv*&HFm4XV$x?=jT(}~ zaV-4@{VxYVbaDh&2NQUOVM%bzoob< zHiyk*HfZe@p_h>)`reeg7{|f>L3cxI&z>VK$GV)yyIoGN-|Y_=G*q@#pKmo5IZTs^ zb0!vLv4?2FvI|e7{~J-$fJJ% z3o!!2HRPGu=6Pi~3#ukeD=)}#ShWfq?Lo84lxRvnO7#uzFHSu7-*f1_MK$2VeML{WnJ{Y+3?K#3;z~P6l>* z8B9w(cHU{%pMPei&2&-#fbM;)wR_!bo8H*d!fS|WdP+woB{`UvPpw-# z{hF2Y3Ue}*_eKrB-*XaYIIglsk9Rcz~SQ^ zGwyqRnq5b!VW=VUv5?YIKm74lSyFfR4VK*fYOw~GV@ZZl0{}#@dug`Q%f5Zvveipw zD-D5-pVzJaX;*W=e#PbnUA09X#rzWW8gk2aF3`Pg+X`ma|nxP19s=@1#Rv(`M%(Ts^A z;=O$G3Lueg;Q8(V*GCY?AHT6U&mrIKi|^|fKlIL2yDl*R%0uLb931fPdHlM{$wd*D zs2qye_(k3BKDS9d?xXHA$)dmboYUnq554+vv&(Oe&waHB-91Y~lmfow=4bXEZj#S0 znmhe(YgQg~`lCl*yjMa4b*f!E^ZwV6!4~-^72nuL2d;c=%V}nb4rPH$a37OC*P~g&`SWHFdrWAuEM69bKmPb4;+_I z-gD*B%VrcIHzN7_Ah(nR6}bP{=o2U)q1~uM_c*y+}#N3q}hiTPuFUKhX281ZTouNO2^_vjsS_!({m~8qJP=I zK`5P>T2}DzsyPilHrYF1Sf|U^*3~bcQ3n9j88`24DATDK-0tu&s}@!T?+?I}Z+|`6 ztfqWRiDa5*xIY2?a%Ri zsa;>3AGE&R9^dQx+DvJWFquKoI|&*q39)&aS##^FU&_irr=dQ-cQq13cpN7n?4%ev z<=Ej7qYlfOJNr5Y{giq#p)bJQICmmR1UX)Jpb=vHm`a=xAqmE{MGPibwHN*Fud*Pj zt)8@?)Zt~}hN3 z60d7_eztADbP`!a&)#{#@u5J13nfB!gHcQ6X4&O4>HyHz?^cofyci*wJRwgyM4rp~ zeJ!TZ=KMhXfaQog@vcQ@r&zTsp83#EGt!A^6ARBObND$J?{X}ZrLgEVHp_6FRUH6Y zdR?mAMlG_^`jr}$`j4;m{@kRaB`tT`6qP%Flf~D0BH1 z!%laYUs!wbL{C=HK2eanP` ztR!Ea<2WT3A~ZSDAGkpqQ7WMl00j=AzDKR#7=g>VUo^HhKYBxKrn7h zBkg7PGZS!7>SRf2B&@%E$d)VB$M4>A6af1|+#PV!JN3gRUu8)^2zxsxOQsty1) zi=ohNYUp#P4*+6=LnwQVypPWx`270=k8J#IYx^Ki;<+09vg(rOe}1mjr0*Z}e7Lnf z_hhj$84z{Hqk3(I^M3Kr@rvvOe zo3`KZ=2iy6N~7kq4850yGm9NhuDM7W_()11C4BqO7F_hme|`S&wYaD%=Fgmjb071m z*qZV_GCHSZ8M8C2()ocQ&rOeQdiS9V%*J5{3U&65em4!U+LCNpIW=|kNiItanTcVz zhA7U>j5w`Y{IY6depf2_xwsq=ycNiY^vRIhYrFpGtN;2>08NmD3zyJ6h7f3n{ec|!8?)Bd_%$rs` zu{cLMv$Sf$qo3~17YlUxsB*Rhy1@fMQHEJQJl@))(GV2iE7!iUYEo{o!}8ZXO&G(L z8>ziRbX9r2+;U%2cfJOXc#yR7qmO<1tg7gNksJWLA2{C!o^Jv4q7DjR0ANs5mzeYr zw6PP#q&7>iw9?c9V#pkg0m*_9>LFsKIWx7yyL+0xx%-8OuUwFuVfmu&$h{x$E-`5~ z?`uTTS!y$TdU59mb&^|@Dh6RCx}v{@P_MG+_O_w9~>oCuD%7 zebm8m0+06c&|Bo_NQPmTKl2G1d3gqErb){|_}@o5Sq_pS7s`+YnQ%}>V<6s{H{w`_ z(<`6P%d#_}(7%Di4dJ)Q3aJW)<9=}ZrfTqL1;9XXMu82vkkRijk`5U940Qeu&`kgi z0*LxPCkP@QO5GyJ3Gp8sx_??p^ahg6jwT|=vmpS`2*8k9i=#v-97BqsCPSYuhF_2} z>~IHsp_I49WFR!8%;CgAY2_Kj|QcvSb#fwPDRWY zjt>B0di(DH(+{9uWHgMycQ8Bx(ErM_2C!6$WD?Nucksd^QV?n+*3~~0{5Bk$$*$zOvw0d$&_Q%+D}6t;~$ZB9=%@o9J5H5l+9D^*! zg#Z@9rLkTX3DAY7PmTA&aRBIf9dNFY%H<{gjyM7UWL8x1Kl)YB!%FT50hXj?5Her} zg_)N@LK4Coe@G@63R(iM>l*4A2uiaItNDuRB6qk=C76K>lxrij8Y<#F(R~IO_^Y2x zHX5`b4#ffhb$tI3#LXbzgNz<|1fXqIQ9oePcE@l`E{A4V7IG;JDP$M96+OUY)F-|; zK~mGMC;#^O&QLRO&qZhc=n6zP^M;Hrt(GEld~2i?mHAP0d6L=cXc*9AS(^D_-!bVB^@%@S zwQ!${4!ev%MwhJf@rhYxMj`R)-Si_@pM@vPBSrlUv7Q+a# z;A2J&0O~b0Iab+c=^k)_R9x#J$lFcBlZQOMfQ3jQ;1~hJbhGyES2xL0eo5_gN*C70 z%@p3)F%Y^IR87e7hIBpxg49rF&YGO~iBT^%xLJ@tL3cN{pby_F#)Q~%MnNyeja~$K zIles0EOViyqYvOIaBzr&CHac*WUtFg;t2$OVi+_8R@dd)d$?ITuhEe2-mqxz(C{EI z;s}5~`LHn4)D=?HhkVR)t7i+EIW+*h2SmS@i{$nzv8CVudA|h2(cf3c6j>Qx7Bf>Dvs=epMPh@*^=@~P!Ogec|hLOO!2Zy6Z z$lD5plmyH2moA+%;!~mls8`6JnW2Ghhq!?)jGV;mS&Xxc(x4WYCmqA7)$ zjY%7|EX|%Xcbc@1#KNne?=9EG)qyEtP;J$&SpSJ!3td)}z2?kGy)=^Zz+u$NW7g}V z(f$z2-=s+BrvgBr18^b$BL;u?1X73x9DUK80O|;%0f5B6Z|IVxsxZAjxezy!371UE z7o@OAeNOk6tpka(LlI%i1SBkh!*YN5Xq$Xy?X}BVJgl3A6LanI_3b@Q;9*|6{0wFK zJ{17m2ZVH#lAB;uQV8%Bj$=d;3+zM_^1(iC?3a@&#d(>Oo=Qdt9U7wVzjT4j&3C`t zWz!~`@lFPY8Ai=Ny}7M_&@CMzq@VuT?6y8%?er44&F*7eq@KET+3e)MR{?+^d0<%h zGcg8X9vKL;M|oZkntxGaaHd`-A&B+o7Y`O~t^RiV`|=m2x) z2RkR|Q(Qf4qydqqCm(rzlYDsn`SZ1YG)9BVKOimr%+`kYZeOO?rZAaM88>l@=6?)s zhWH*k{Gh-<_KJ}z4qSbP_n147{Hw?`*+{+c!4Gn$zh>n;cVGlHu+K$5cGW^Tfbq@F zBLRP4)a3yY(H@zyb>x$q54VOUnHu%lH`lJThcdmrP8UfM%jZ|7^sER!fQt$NDSJH# z!ypWp=s)pOG8-uUX!0{=ig;ybQ&(>=tZy;t-??>hUB53LKhe`Diku&E`cY53=Z&wb z%-W_N(3!}VQDHmCTsEh-eNA!tDrc=5rEV^5teh-EZ?Z1Du`(LhIGK#x} zkC)i7eh{V-QZbMUjYk(0>gm*DbLfIuHvRlZH_BEA8rtiBdGU)E*Y4`^4k&axQopr% zmb}o!&%q(jhTY9+t`V4+j3oYP%fS1acgbgv)i9+b$Cktzi3tHv%8+Et%_Q7391g5o z$?SL2Bfm)Wn_-|_M=XBeHIFZNIdVa7UcI2@`CC^^FWlAVs~hz9(CofJ{~gO_%HiuT zcO2$Z3f`qo=<{kU+Kbk2YHSOwoPuFhQA=SZNs7|2-j6{0OMvYcTmePRp_u)Tvz6P# z+XBpwmsr-7RT_1pn9zu>K!$7whV?yry*5~L$xF|5y6AmJJHFlD{PEtFcOF=kogwr- z2!Zvs4Q>xT-9YJ5REDV%8hJ-K(EI0G<gLa%Z@c;I!x$fk^+X)tl#o3Y-52*DU!SNUz9DxM`6IHi~->7_<`RqGA9!s zMW5B_XBQO`&t9>QfCDL6Uro;Hi`LJ2>-+f}W|P_%SnAg;GIx(QJr_-=XFPS$T-oV! zy1X|${HE=?KiPi%>}TKABZDo$Ql>1+^$xh$@G__HX$yO=0CY>F4hRZXrJR-i6i**0 z+RNPp!#rTJ{h`7cCVgVPIK*%UE&tuGj_W{6N?NDYJ+o{rKf$6uI_Uqya@((ex=ozM z1CI5bR{mSICL`JSaAKBOSo zg~j#Z6I#*V_w{@B-b>`^c7>d+IB*VQAnYLS-|*6;ra`(;Px2x2>9@=6ChDl9Fd$Fx z48$mv)=|KQ%OT>oEdJx808D_iLP?T-Wb=Wej$V$?ThIGPzR_8E=?l7ccc9QfNv9F_ubZNK@WOfw`lRRbJSo&IsKozlVNv+u)lViAWF^0n zL`(|z*9imA6@_e}6hlBs8>v~u?zRW|^g4b=pRe7|)(z47oxbh&;B)h~DD19?Yvm9j z4l$F9E&r~0!8I?}mOq4fX(0SYgz(!l{ElAlZ{`_ZzUcq~Luq|s1Zbg3o&W=3wZoKZ z#6K}b$zt0);5iJC-!Js2QH+3CTM*ek6W_bzu8y6Zm75P!lXI~}lbcQ0kD+cJ$K8PfEBW0$`neOfiL+>dJo0!sa-KGM zIeuE6O|50R;sD@S4m#czr4WoQg+MIs2Mr`FdCUl|XLk0Nv$Jton#aWt@etD&4{uu>y=ai!*p`P}rpqa^4B5Q{LDFmW)c!ZQyCOY<^RL4|=E_YO#;oLRj2%n}00ABHyupiP)FAXEl=G`O3<+~RBo z5cv*3?iuQgfG!r67i6Y?pM(G)$~q6Y4-2z`)fxdKV36D$gty^veo%`IY4MI>7mNo9 zWS8J9sThi5VQz*w{o5!hmOjZ?C}?9KTDvKR9MTNijpo{s>;+!MSR4-=p|#Y;5&(<@ znM(mi1^Zp#SKE1GA%IVLMFvBr* zAV4?6HKc}0IjuA*0B{}1{;9B)gFKP8If%jlL}38JG2o-P8C?JnjTt|BxhJ;4KqwxS z9K~8OES!M#uaUh~j1xijlVpof!pYG6q80QiV|v zfc&&96OO5zYL0(9i~v&}0U8Iuv>|4Nb2Acys52l=XCI-YE-{=*V-`At1CJ0rOB(^? zepHv`ss5`S3GviqgWOAm7&Wd9tqYXAxaSiJjZ`sg`|D96A@$kK$iya|_^ zrUL-Xb*StFBnN=d$^yzC0Gto>Z33=+g5U2ssK`@-NDfA3C?gj#O33};J4Rw4Ox>XY z91_EA$vWE@;4En?PF~1`5k|cVWe}qRKq5j9vdD~SNBBe4~%DX^q6vefl&|x`I;Pyx|slp{^0=N zLo=B{(wZNdwL}_=CkQmiz&Yi)>Sh8YQuBuwVpG$T!qiuZHA?v&CjCK_{!q|x{UH<- z0Otx#<4G#{gYitBjo;Cp76Pp-oHik6EZ8&l0J!!5x-F{KD4s0@MzV-PhCfI;WewQU zEa-*M3et!o1h`2>Y2|+7(bxlkY*u0RJsYwPWy4VlZ(s_;Q4Z^ew!zwy@U4tT%&H*} zoos;lR-&B^NuW5k%T*8#(!Q)R6jCIt8h6Cm|s>Wvp%T*17d1q&xE zOPpkdq+FdBT^|NaMEjzGqK+Xrx_L_^z+*0a{tp5GQ1;D`YQ^|)fL^D$YGv)%vFC*7 z03~09P=X2MT`2_kqABMppf4B4C6*}P3sS~F16#M;fbEID-a!PRX6-)a7!4aAzkbAp z_tK%U2Y_+}`4Z#>F36wE<#E270Ga2Bb9F#WKBj}goV;z1EHUgD2?KO>x1X)+^pyPn_>RXH+l*z zT8EY(hJPcKry<`?b50*i>c|HdR9t`F+~Pd-(=X!z0BIKbwX_nWtUnVFA^JOEdRsLN zFwTAJyf0Q(SHH2<^wkl*&nq}tsFmHIE6Fr1sG3k)Svn!#p-W}KpEPOY-+WD%dOril z9|NpYjG$qF&;V^dsD2B?D=!%M0yIAjIOp(${d6WO$GM8fjJ?d{BR!=_>q84A5)>tQf)+-~GPO0K(xJ8DTyINNpU%9z!^W z`LlG*Su?6Y##nh+Nz&LWVI)$rmWwrBQXwq_tOsF z03cTCU)ZArd)K;Re7O%!kQrKVji_36!MrZ?0lEk5zYcVNFbo6T29B)*-ERZdEiz78 zITnOt5Hm87Fl+~+F_Tu9FaYavq5N{ZY3y}?L<3vF@uvXYDf}j!P-Te=p$Md(kx7b+ zbK9X;{LPL)D0QQpaty)?0Avv#K504fs+jG?EolOV(?H(6f^{(7bfN%2mccV1Mp zfKT3Z84U))MijF+A-ET4%vlzI>>E!K?t>Ez03yyS$Rg)9z>4KPK>AHe5R022Meav$ zrH+rpokpna)8>JU)xh$T@#S~~op1moLMox-E#N&SzyKI=nHeyQM7k!w6z&!aSTIWH z^#n3_76HelK<(P*^l6*`kSNdtT3!T$J3|$ZY-ru^P!J+z`GbpmM_W>`bC3m0Ie>jR zuq_%6yPjk;t^i2HGoWt^82TOz?G~40@{7Nr5}K+H=-Ts5nNd|zD(8xe;+EATfG9l;L-w;w2K Date: Fri, 15 Jan 2016 17:42:23 +0000 Subject: [PATCH 100/144] Forgot some needed changes in the mkdocs and mktar script --- mkdocs | 2 +- mktar | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mkdocs b/mkdocs index 2992db80..d7cf5146 100755 --- a/mkdocs +++ b/mkdocs @@ -4,7 +4,7 @@ MAKE=make which gmake && MAKE=gmake # small safety test -if [ ! -f module/pgmodule.c ] +if [ ! -f pgmodule.c ] then echo "Hmmm. Are you sure you are in the right directory?" exit 1 diff --git a/mktar b/mktar index e5413cc5..05f2da41 100755 --- a/mktar +++ b/mktar @@ -4,7 +4,7 @@ VERSION=4.2 DISTDIR=/u/pyg/files # small safety tests -if [ ! -f module/pgmodule.c ] +if [ ! -f pgmodule.c ] then echo "Hmmm. Are you sure you are in the right directory?" exit 1 From a46bfcb4553a676b455c535b10d86d69adc222c8 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 15 Jan 2016 17:46:53 +0000 Subject: [PATCH 101/144] Fix bad reference in pgdb docs --- docs/contents/pgdb/cursor.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contents/pgdb/cursor.rst b/docs/contents/pgdb/cursor.rst index c56d917c..e775e493 100644 --- a/docs/contents/pgdb/cursor.rst +++ b/docs/contents/pgdb/cursor.rst @@ -91,8 +91,8 @@ The parameters may also be specified as list of tuples to e.g. insert multiple rows in a single operation, but this kind of usage is deprecated: :meth:`pgdbCursor.executemany` should be used instead. -Note that in case this method raises a :exception:`DatabaseError`, you can -get information about the error condition that has occurred by introspecting +Note that in case this method raises a :exc:`DatabaseError`, you can get +information about the error condition that has occurred by introspecting its :attr:`DatabaseError.sqlstate` attribute, which will be the ``SQLSTATE`` error code associated with the error. Applications that need to know which error condition has occurred should usually test the error code, rather than From 02c124f7015626e86f0aae472cadd7e87c19cc3b Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 15 Jan 2016 18:03:24 +0000 Subject: [PATCH 102/144] Some more improvements in mkdocs and mktar --- MANIFEST.in | 2 ++ mkdocs | 1 + mktar | 5 +++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index b5e70975..df1b78ad 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,8 @@ include *.c include *.h include *.py include *.cfg +include *.rst +include *.txt recursive-include tests *.py recursive-include docs *.html *.css *.txt *.js *.gif *.png *.ico *.py Makefile *.bat *.rst *.css_t diff --git a/mkdocs b/mkdocs index d7cf5146..e4968b72 100755 --- a/mkdocs +++ b/mkdocs @@ -15,3 +15,4 @@ echo "Making Sphinx docs..." cd docs ${MAKE} clean ${MAKE} html +rm -f build/html/.buildinfo diff --git a/mktar b/mktar index 05f2da41..55e85196 100755 --- a/mktar +++ b/mktar @@ -40,8 +40,9 @@ TF=$DISTDIR/$TD.tgz MODFILES="pg.py pgdb.py pgmodule.c pgfs.h pgtypes.h - setup.py setup.cfg" -DOCFILES="docs/Makefile docs/make.bat docs/*.rst + setup.py setup.cfg + LICENSE.txt README.rst MANIFEST.in" +DOCFILES="docs/Makefile docs/make.bat docs/*.rst docs/*.py docs/contents docs/download docs/community docs/_static docs/_templates" HTMLFILES="docs/_build/html" From 29360f7f033221e42eb143974fe33a92ec0adad0 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 15 Jan 2016 18:30:25 +0000 Subject: [PATCH 103/144] Fix some issues in the MANIFEST.in file The exclude commands are unnecessary since these files are nowhere included. They only cause warnings when building the source distribution. --- MANIFEST.in | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index df1b78ad..feb1c0c7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,10 +5,13 @@ include *.cfg include *.rst include *.txt recursive-include tests *.py -recursive-include docs *.html *.css *.txt *.js *.gif *.png *.ico *.py Makefile *.bat *.rst *.css_t - -global-exclude *.pyc -global-exclude *.pyo -global-exclude *.o -global-exclude *.so -global-exclude _pg.* \ No newline at end of file +include docs/Makefile +include docs/make.bat +include docs/*.py +include docs/*.rst +recursive-include docs/community *.rst +recursive-include docs/contents *.rst +recursive-include docs/download *.rst +recursive-include docs/_static *.css_t *.ico *.png +recursive-include docs/_templates *.html +recursive-include docs/_build/html *.css *.gif *.html *.ico *.js *.png *.txt From 7bd682157fe5124c319b70e7a9219723c3c34c85 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 15 Jan 2016 18:42:07 +0000 Subject: [PATCH 104/144] Build source distribution with setup script Instead of building the source distribution (the tarball) manually in mktar, we now let setup.py do the work for us. This has the advantage that we can be sure that the created tarball conforms to the standards (e.g. contains the egg-info bits), and we don't need to maintain the list of source files in two places any more - we only need to maintain the MANIFEST.in file now. Note that setup.py creates a tar.gz file instead of .tgz as before. We should also distribute it like that because it's the standard on PyPI etc. I have also changed the download docs to reflect this. --- docs/download/download.rst | 4 ++-- mkdocs | 1 - mktar | 34 ++++++++++------------------------ 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/docs/download/download.rst b/docs/download/download.rst index fffb6433..d8c644c9 100644 --- a/docs/download/download.rst +++ b/docs/download/download.rst @@ -5,9 +5,9 @@ You can find PyGreSQL on the **Python Package Index** at * http://pypi.python.org/pypi/PyGreSQL/ The **released version of the source code** is available at - * http://pygresql.org/files/PyGreSQL.tgz + * http://pygresql.org/files/PyGreSQL.tar.gz You can also check the latest **pre-release version** at - * http://pygresql.org/files/PyGreSQL-beta.tgz + * http://pygresql.org/files/PyGreSQL-beta.tar.gz A **Linux RPM** can be picked up from * http://pygresql.org/files/pygresql.i386.rpm A **NetBSD package** is available in their pkgsrc collection diff --git a/mkdocs b/mkdocs index e4968b72..d7cf5146 100755 --- a/mkdocs +++ b/mkdocs @@ -15,4 +15,3 @@ echo "Making Sphinx docs..." cd docs ${MAKE} clean ${MAKE} html -rm -f build/html/.buildinfo diff --git a/mktar b/mktar index 55e85196..108d4bc9 100755 --- a/mktar +++ b/mktar @@ -33,33 +33,19 @@ echo "Making source tarball..." ./mkdocs -# The following does essentially the same as "python setup.py sdist". +# Package as source distribution. -TD=PyGreSQL-$VERSION -TF=$DISTDIR/$TD.tgz +rm -rf build dist -MODFILES="pg.py pgdb.py pgmodule.c - pgfs.h pgtypes.h - setup.py setup.cfg - LICENSE.txt README.rst MANIFEST.in" -DOCFILES="docs/Makefile docs/make.bat docs/*.rst docs/*.py - docs/contents docs/download docs/community - docs/_static docs/_templates" -HTMLFILES="docs/_build/html" -TESTFILES="tests/*.py" +python setup.py sdist -rm -rf $TD -mkdir $TD -mkdir -p $TD/docs/_build/html -mkdir $TD/tests -cp $MODFILES $TD -cp -r $DOCFILES $TD/docs -cp -r $HTMLFILES $TD/docs/_build -cp $TESTFILES $TD/tests -tar -cvzf $TF $TD +DF=`ls dist` +TF=$DISTDIR/$DF + +cp dist/$DF $TF chmod 644 $TF -rm -rf $TD + rm -f $DISTDIR/$SYMLINK -ln -s $TD.tgz $DISTDIR/$SYMLINK +ln -s $DF $DISTDIR/$SYMLINK -echo "$TF has been built" +echo "$TF has been built." From 3277a40c77b98147516d37444f3ebb788269a80a Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 17 Jan 2016 16:19:32 +0000 Subject: [PATCH 105/144] Avoid warning in tests with old PostgreSQL version --- tests/test_dbapi20.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_dbapi20.py b/tests/test_dbapi20.py index 8c0be84a..e48da81e 100755 --- a/tests/test_dbapi20.py +++ b/tests/test_dbapi20.py @@ -51,7 +51,6 @@ def setUp(self): db = pg.DB('postgres', dbhost or None, dbport or -1) db.query('create database ' + dbname) - def tearDown(self): dbapi20.DatabaseAPI20Test.tearDown(self) @@ -100,6 +99,7 @@ def test_fetch_2_rows(self): "datetimetest timestamp," "intervaltest interval," "rowidtest oid)" % table) + cur.execute("set standard_conforming_strings to on") for s in ('numeric', 'monetary', 'time'): cur.execute("set lc_%s to 'C'" % s) for _i in range(2): From 33e4671b3077d66c522249582be9534a0cbe2e40 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 17 Jan 2016 16:32:18 +0000 Subject: [PATCH 106/144] Docs and 100% test coverage for NotificationHandler --- docs/contents/changelog.rst | 6 +- docs/contents/pg/db_wrapper.rst | 19 ++++++ docs/contents/pg/index.rst | 1 + pg.py | 84 +++++++++++++++------------ tests/test_classic_dbwrapper.py | 100 +++++++++++++++++++++++++++++++- 5 files changed, 169 insertions(+), 41 deletions(-) diff --git a/docs/contents/changelog.rst b/docs/contents/changelog.rst index 5092c8ce..12aff541 100644 --- a/docs/contents/changelog.rst +++ b/docs/contents/changelog.rst @@ -17,12 +17,14 @@ Version 4.2 - get_tables() does not list information schema tables any more. - Fix notification handler (Thanks Patrick TJ McPhee). - Fix a small issue with large objects. +- Minor improvements in the NotificationHandler. +- Converted documentation to Sphinx and added many missing parts. - The tutorial files have become a chapter in the documentation. -- Greatly improve unit testing, tests run with Python 2.4 to 2.7 again. +- Greatly improved unit testing, tests run with Python 2.4 to 2.7 again. Version 4.1.1 (2013-01-08) -------------------------- -- Add WhenNotified class and method. Replaces need for third party pgnotify. +- Add NotificationHandler class and method. Replaces need for pgnotify. - Sharpen test for inserting current_timestamp. - Add more quote tests. False and 0 should evaluate to NULL. - More tests - Any number other than 0 is True. diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index 971ead7e..8adfb4b5 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -529,3 +529,22 @@ The method can also be used to check through its return value whether currently regular type names are used. .. versionadded:: 4.1 + +notification_handler -- create a notification handler +----------------------------------------------------- + +.. class:: DB.notification_handler(event, callback, [arg_dict], [timeout], [stop_event]) + + Create a notification handler instance + + :param str event: the name of an event to listen for + :param callback: a callback function + :param dict arg_dict: an optional dictionary for passing arguments + :param timeout: the time-out when waiting for notifications + :type timeout: int, float or None + :param str stop_event: an optional different name to be used as stop event + +This method creates a :class:`pg.NotificationHandler` object using the +:class:`DB` connection as explained under :doc:`notification`. + +.. versionadded:: 4.1.1 diff --git a/docs/contents/pg/index.rst b/docs/contents/pg/index.rst index be9fd32d..a394ecaa 100644 --- a/docs/contents/pg/index.rst +++ b/docs/contents/pg/index.rst @@ -14,3 +14,4 @@ Contents db_wrapper query large_objects + notification diff --git a/pg.py b/pg.py index 586b2f8f..23b3ac30 100644 --- a/pg.py +++ b/pg.py @@ -143,23 +143,28 @@ def _prg_error(msg): class NotificationHandler(object): """A PostgreSQL client-side asynchronous notification handler.""" - def __init__(self, db, event, callback, arg_dict=None, timeout=None): + def __init__(self, db, event, callback=None, + arg_dict=None, timeout=None, stop_event=None): """Initialize the notification handler. - db - PostgreSQL connection object. - event - Event (notification channel) to LISTEN for. - callback - Event callback function. - 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. + You must pass a PyGreSQL database connection, the name of an + event (notification channel) to listen for and a callback function. + You can also specify a dictionary arg_dict that will be passed as + the single argument to the callback function, and a timeout value + in seconds (a floating point number denotes fractions of seconds). + If it is absent or None, the callers will never time out. If the + timeout is reached, the callback function will be called with a + single argument that is None. If you set the timeout to zero, + the handler will poll notifications synchronously and return. + + You can specify the name of the event that will be used to signal + the handler to stop listening as stop_event. By default, it will + be the event name prefixed with 'stop_'. """ - if isinstance(db, DB): - db = db.db self.db = db self.event = event - self.stop_event = 'stop_%s' % event + self.stop_event = stop_event or 'stop_%s' % event self.listening = False self.callback = callback if arg_dict is None: @@ -168,7 +173,7 @@ def __init__(self, db, event, callback, arg_dict=None, timeout=None): self.timeout = timeout def __del__(self): - self.close() + self.unlisten() def close(self): """Stop listening and close the connection.""" @@ -194,42 +199,47 @@ def unlisten(self): def notify(self, db=None, stop=False, payload=None): """Generate a notification. - Note: If the main loop is running in another thread, you must pass - a different database connection to avoid a collision. + Optionally, you can pass a payload with the notification. - The payload parameter is only supported in PostgreSQL >= 9.0. + If you set the stop flag, a stop notification will be sent that + will cause the handler to stop listening. + Note: If the notification handler is running in another thread, you + must pass a different database connection since PyGreSQL database + connections are not thread-safe. """ - if not db: - db = self.db if self.listening: + if not db: + db = self.db q = 'notify "%s"' % (stop and self.stop_event or self.event) if payload: q += ", '%s'" % payload return db.query(q) - def __call__(self, close=False): + def __call__(self): """Invoke the notification handler. - The handler is a loop that actually LISTENs for two NOTIFY messages: - - and stop_. + The handler is a loop that listens for notifications on the event + and stop event channels. When either of these notifications are + received, its associated 'pid', 'event' and 'extra' (the payload + passed with the notification) are inserted into its arg_dict + dictionary and the callback is invoked with this dictionary as + a single argument. When the handler receives a stop event, it + stops listening to both events and return. - When either of these NOTIFY messages are received, its associated - 'pid' and 'event' are inserted into , and the callback is - invoked with . If the NOTIFY message is stop_, the - handler UNLISTENs both and stop_ and exits. + In the special case that the timeout of the handler has been set + to zero, the handler will poll all events synchronously and return. + If will keep listening until it receives a stop event. Note: If you run this loop in another thread, don't use the same database connection for database operations in the main thread. - """ self.listen() - _ilist = [self.db.fileno()] - + poll = self.timeout == 0 + if not poll: + rlist = [self.db.fileno()] while self.listening: - ilist, _olist, _elist = select.select(_ilist, [], [], self.timeout) - if ilist: + if poll or select.select(rlist, [], [], self.timeout)[0]: while self.listening: notice = self.db.getnotify() if not notice: # no more messages @@ -238,14 +248,14 @@ def __call__(self, close=False): if event not in (self.event, self.stop_event): self.unlisten() raise _db_error( - 'listening for "%s" and "%s", but notified of "%s"' + 'Listening for "%s" and "%s", but notified of "%s"' % (self.event, self.stop_event, event)) if event == self.stop_event: self.unlisten() - self.arg_dict['pid'] = pid - self.arg_dict['event'] = event - self.arg_dict['extra'] = extra + self.arg_dict.update(pid=pid, event=event, extra=extra) self.callback(self.arg_dict) + if poll: + break else: # we timed out self.unlisten() self.callback(None) @@ -1155,9 +1165,11 @@ def truncate(self, table, restart=False, cascade=False, only=False): self._do_debug(q) return self.query(q) - def notification_handler(self, event, callback, arg_dict={}, timeout=None): + def notification_handler(self, + event, callback, arg_dict=None, timeout=None, stop_event=None): """Get notification handler that will run the given callback.""" - return NotificationHandler(self.db, event, callback, arg_dict, timeout) + return NotificationHandler(self, + event, callback, arg_dict, timeout, stop_event) # if run as script, print some information diff --git a/tests/test_classic_dbwrapper.py b/tests/test_classic_dbwrapper.py index 1f3bea36..ab31792f 100755 --- a/tests/test_classic_dbwrapper.py +++ b/tests/test_classic_dbwrapper.py @@ -225,6 +225,26 @@ def testMethodClose(self): self.assertRaises(pg.InternalError, self.db.close) self.assertRaises(pg.InternalError, self.db.query, 'select 1') + def testMethodReset(self): + con = self.db.db + self.db.reset() + self.assertIs(self.db.db, con) + self.db.query("select 1+1") + self.db.close() + self.assertRaises(pg.InternalError, self.db.reset) + + def testMethodReopen(self): + con = self.db.db + self.db.reopen() + self.assertIsNot(self.db.db, con) + con = self.db.db + self.db.query("select 1+1") + self.db.close() + self.db.reopen() + self.assertIsNot(self.db.db, con) + self.db.query("select 1+1") + self.db.close() + def testExistingConnection(self): db = pg.DB(self.db.db) self.assertEqual(self.db.db, db.db) @@ -1433,6 +1453,83 @@ def testBytea(self): self.assertEqual(r, s) query('drop table bytea_test') + def testNotificationHandler(self): + # the notification handler itself is tested separately + f = self.db.notification_handler + callback = lambda arg_dict: None + handler = f('test', callback) + self.assertIsInstance(handler, pg.NotificationHandler) + self.assertIs(handler.db, self.db) + self.assertEqual(handler.event, 'test') + self.assertEqual(handler.stop_event, 'stop_test') + self.assertIs(handler.callback, callback) + self.assertIsInstance(handler.arg_dict, dict) + self.assertEqual(handler.arg_dict, {}) + self.assertIsNone(handler.timeout) + self.assertFalse(handler.listening) + handler.close() + self.assertIsNone(handler.db) + self.db.reopen() + self.assertIsNone(handler.db) + handler = f('test2', callback, timeout=2) + self.assertIsInstance(handler, pg.NotificationHandler) + self.assertIs(handler.db, self.db) + self.assertEqual(handler.event, 'test2') + self.assertEqual(handler.stop_event, 'stop_test2') + self.assertIs(handler.callback, callback) + self.assertIsInstance(handler.arg_dict, dict) + self.assertEqual(handler.arg_dict, {}) + self.assertEqual(handler.timeout, 2) + self.assertFalse(handler.listening) + handler.close() + self.assertIsNone(handler.db) + self.db.reopen() + self.assertIsNone(handler.db) + arg_dict = {'testing': 3} + handler = f('test3', callback, arg_dict=arg_dict) + self.assertIsInstance(handler, pg.NotificationHandler) + self.assertIs(handler.db, self.db) + self.assertEqual(handler.event, 'test3') + self.assertEqual(handler.stop_event, 'stop_test3') + self.assertIs(handler.callback, callback) + self.assertIs(handler.arg_dict, arg_dict) + self.assertEqual(arg_dict['testing'], 3) + self.assertIsNone(handler.timeout) + self.assertFalse(handler.listening) + handler.close() + self.assertIsNone(handler.db) + self.db.reopen() + self.assertIsNone(handler.db) + handler = f('test4', callback, stop_event='stop4') + self.assertIsInstance(handler, pg.NotificationHandler) + self.assertIs(handler.db, self.db) + self.assertEqual(handler.event, 'test4') + self.assertEqual(handler.stop_event, 'stop4') + self.assertIs(handler.callback, callback) + self.assertIsInstance(handler.arg_dict, dict) + self.assertEqual(handler.arg_dict, {}) + self.assertIsNone(handler.timeout) + self.assertFalse(handler.listening) + handler.close() + self.assertIsNone(handler.db) + self.db.reopen() + self.assertIsNone(handler.db) + arg_dict = {'testing': 5} + handler = f('test5', callback, arg_dict, 1.5, 'stop5') + self.assertIsInstance(handler, pg.NotificationHandler) + self.assertIs(handler.db, self.db) + self.assertEqual(handler.event, 'test5') + self.assertEqual(handler.stop_event, 'stop5') + self.assertIs(handler.callback, callback) + self.assertIs(handler.arg_dict, arg_dict) + self.assertEqual(arg_dict['testing'], 5) + self.assertEqual(handler.timeout, 1.5) + self.assertFalse(handler.listening) + handler.close() + self.assertIsNone(handler.db) + self.db.reopen() + self.assertIsNone(handler.db) + def testDebugWithCallable(self): if debug: self.assertEqual(self.db.debug, debug) @@ -1455,7 +1552,6 @@ class TestSchemas(unittest.TestCase): def setUpClass(cls): db = DB() query = db.query - query("set client_min_messages=warning") for num_schema in range(5): if num_schema: schema = "s%d" % num_schema @@ -1480,7 +1576,6 @@ def setUpClass(cls): def tearDownClass(cls): db = DB() query = db.query - query("set client_min_messages=warning") for num_schema in range(5): if num_schema: schema = "s%d" % num_schema @@ -1493,7 +1588,6 @@ def tearDownClass(cls): def setUp(self): self.db = DB() - self.db.query("set client_min_messages=warning") def tearDown(self): self.db.close() From a43dbbd23da7894352bf172536d49660519dd4d8 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 17 Jan 2016 21:01:04 +0000 Subject: [PATCH 107/144] Achieve 100% test coverage for pg module on the trunk Note that some lines are only covered in certain Pg or Py versions, so you need to run tests with different versions to be sure. Also added another synonym for transaction methods, you can now pick your favorite for all three of them. --- docs/contents/pg/db_wrapper.rst | 6 ++++++ pg.py | 4 +++- tests/test_classic_dbwrapper.py | 20 ++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index 8adfb4b5..b24f0c30 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -257,6 +257,12 @@ begin/commit/rollback/savepoint/release -- transaction handling .. versionadded:: 4.1 +.. method:: DB.abort() + + This is the same as the :meth:`DB.rollback` method. + +.. versionadded:: 4.2 + .. method:: DB.savepoint(name) Define a new savepoint diff --git a/pg.py b/pg.py index 23b3ac30..a60b4658 100644 --- a/pg.py +++ b/pg.py @@ -519,6 +519,8 @@ def rollback(self, name=None): qstr += ' TO ' + name return self.query(qstr) + abort = rollback + def savepoint(self, name): """Define a new savepoint within the current transaction.""" return self.query('SAVEPOINT ' + name) @@ -898,7 +900,7 @@ def get(self, cl, arg, keyname=None): if keyname == 'oid': if isinstance(arg, dict): if qoid not in arg: - raise _db_error('%s not in arg' % qoid) + raise _prg_error('%s not in arg' % qoid) else: arg = {qoid: arg} where = 'oid = %s' % arg[qoid] diff --git a/tests/test_classic_dbwrapper.py b/tests/test_classic_dbwrapper.py index ab31792f..9be18848 100755 --- a/tests/test_classic_dbwrapper.py +++ b/tests/test_classic_dbwrapper.py @@ -71,6 +71,7 @@ def tearDown(self): def testAllDBAttributes(self): attributes = [ + 'abort', 'begin', 'cancel', 'clear', 'close', 'commit', 'db', 'dbname', 'debug', 'delete', @@ -222,8 +223,12 @@ def testMethodClose(self): pass else: self.fail('Reset should give an error for a closed connection') + self.assertIsNone(self.db.db) self.assertRaises(pg.InternalError, self.db.close) self.assertRaises(pg.InternalError, self.db.query, 'select 1') + self.assertRaises(pg.InternalError, getattr, self.db, 'status') + self.assertRaises(pg.InternalError, getattr, self.db, 'error') + self.assertRaises(pg.InternalError, getattr, self.db, 'absent') def testMethodReset(self): con = self.db.db @@ -436,6 +441,9 @@ def testGetParameter(self): self.assertRaises(TypeError, f) self.assertRaises(TypeError, f, None) self.assertRaises(TypeError, f, 42) + self.assertRaises(TypeError, f, '') + self.assertRaises(TypeError, f, []) + self.assertRaises(TypeError, f, ['']) self.assertRaises(pg.ProgrammingError, f, 'this_does_not_exist') r = f('standard_conforming_strings') self.assertEqual(r, 'on') @@ -487,6 +495,12 @@ def testSetParameter(self): self.assertRaises(TypeError, f) self.assertRaises(TypeError, f, None) self.assertRaises(TypeError, f, 42) + self.assertRaises(TypeError, f, '') + self.assertRaises(TypeError, f, []) + self.assertRaises(TypeError, f, ['']) + self.assertRaises(ValueError, f, 'all', 'invalid') + self.assertRaises(ValueError, f, { + 'invalid1': 'value1', 'invalid2': 'value2'}, 'value') self.assertRaises(pg.ProgrammingError, f, 'this_does_not_exist') f('standard_conforming_strings', 'off') self.assertEqual(g('standard_conforming_strings'), 'off') @@ -770,6 +784,12 @@ def testGetAttnames(self): self.db.get_attnames, 'does_not_exist') self.assertRaises(pg.ProgrammingError, self.db.get_attnames, 'has.too.many.dots') + attributes = self.db.get_attnames('test') + self.assertIsInstance(attributes, dict) + self.assertEqual(attributes, dict( + i2='int', i4='int', i8='int', d='num', + f4='float', f8='float', m='money', + v4='text', c4='text', t='text')) for table in ('attnames_test_table', 'test table for attnames'): self.db.query('drop table if exists "%s"' % table) self.db.query('create table "%s" (' From 444753ca7593936ffe8f82e53f9df5dafd7b2e16 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 17 Jan 2016 21:05:19 +0000 Subject: [PATCH 108/144] Forgot to check in the notification handler docs --- docs/contents/pg/notification.rst | 119 ++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 docs/contents/pg/notification.rst diff --git a/docs/contents/pg/notification.rst b/docs/contents/pg/notification.rst new file mode 100644 index 00000000..a37df668 --- /dev/null +++ b/docs/contents/pg/notification.rst @@ -0,0 +1,119 @@ +The Notification Handler +======================== + +.. py:currentmodule:: pg + +PyGreSQL comes with a client-side asynchronous notification handler that +was based on the ``pgnotify`` module written by Ng Pheng Siong. + +.. versionadded:: 4.1.1 + +Instantiating the notification handler +-------------------------------------- + +.. class:: NotificationHandler(db, event, callback, [arg_dict], [timeout], [stop_event]) + + Create an instance of the notification handler + + :param int db: the database connection + :type db: :class:`Connection` + :param str event: the name of an event to listen for + :param callback: a callback function + :param dict arg_dict: an optional dictionary for passing arguments + :param timeout: the time-out when waiting for notifications + :type timeout: int, float or None + :param str stop_event: an optional different name to be used as stop event + +You can also create an instance of the NotificationHandler using the +:class:`DB.connection_handler` method. In this case you don't need to +pass a database connection because the :class:`DB` connection itself +will be used as the datebase connection for the notification handler. + +You must always pass the name of an *event* (notification channel) to listen +for and a *callback* function. + +You can also specify a dictionary *arg_dict* that will be passed as the +single argument to the callback function, and a *timeout* value in seconds +(a floating point number denotes fractions of seconds). If it is absent +or *None*, the callers will never time out. If the time-out is reached, +the callback function will be called with a single argument that is *None*. +If you set the *timeout* to ``0``, the handler will poll notifications +synchronously and return. + +You can specify the name of the event that will be used to signal the handler +to stop listening as *stop_event*. By default, it will be the event name +prefixed with ``'stop_'``. + +All of the parameters will be also available as attributes of the +created notification handler object. + +Invoking the notification handler +--------------------------------- + +To invoke the notification handler, just call the instance without passing +any parameters. + +The handler is a loop that listens for notifications on the event and stop +event channels. When either of these notifications are received, its +associated *pid*, *event* and *extra* (the payload passed with the +notification) are inserted into its *arg_dict* dictionary and the callback +is invoked with this dictionary as a single argument. When the handler +receives a stop event, it stops listening to both events and return. + +In the special case that the timeout of the handler has been set to ``0``, +the handler will poll all events synchronously and return. If will keep +listening until it receives a stop event. + +.. warning:: + + If you run this loop in another thread, don't use the same database + connection for database operations in the main thread. + +Sending notifications +--------------------- + +You can send notifications by either running ``NOTIFY`` commands on the +database directly, or using the following method: + +.. method:: NotificationHandler.notify([db], [stop], [payload]) + + Generate a notification + + :param int db: the database connection for sending the notification + :type db: :class:`Connection` + :param bool stop: whether to produce a normal event or a stop event + :param str payload: an optional payload to be sent with the notification + +This method sends a notification event together with an optional *payload*. +If you set the *stop* flag, a stop notification will be sent instead of +a normal notification. This will cause the handler to stop listening. + +.. warning:: + + If the notification handler is running in another thread, you must pass + a different database connection since PyGreSQL database connections are + not thread-safe. + +Auxiliary methods +----------------- + +.. method:: NotificationHandler.listen() + + Start listening for the event and the stop event + +This method is called implicitly when the handler is invoked. + +.. method:: NotificationHandler.unlisten() + + Stop listening for the event and the stop event + +This method is called implicitly when the handler receives a stop event +or when it is closed or deleted. + +.. method:: NotificationHandler.close() + + Stop listening and close the database connection + +You can call this method instead of :meth:`NotificationHandler.unlisten` +if you want to close not only the handler, but also the database connection +it was created with. \ No newline at end of file From 99038342e604b9b1c413df9d53b34b92786d9a72 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 18 Jan 2016 23:22:35 +0000 Subject: [PATCH 109/144] Minor doc fixes. --- docs/contents/pg/db_wrapper.rst | 26 ++++++++++++++------------ pg.py | 16 ++++++++-------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index b24f0c30..22fb92e6 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -66,8 +66,8 @@ pkey -- return the primary key of a table :rtype: str :raises KeyError: the table does not have a primary key -This method returns the primary key of a table. For composite primary -keys, the return value will be a frozenset. Note that this raises a +This method returns the primary key of a table. For composite primary +keys, the return value will be a frozenset. Note that this raises a KeyError if the table does not have a primary key. get_databases -- get list of databases in the system @@ -297,9 +297,9 @@ get -- get a row from a database table or view the values are the row values. :raises ProgrammingError: no primary key or missing privilege -This method is the basic mechanism to get a single row. It assumes -that the key specifies a unique row. If *keyname* is not specified, -then the primary key for the table is used. If *arg* is a dictionary +This method is the basic mechanism to get a single row. It assumes +that the key specifies a unique row. If *keyname* is not specified, +then the primary key for the table is used. If *arg* is a dictionary then the value for the key is taken from it and it is modified to include the new values, replacing existing values where necessary. For a composite key, *keyname* can also be a sequence of key names. @@ -345,14 +345,14 @@ update -- update a row in a database table :raises ProgrammingError: no primary key or missing privilege Similar to insert but updates an existing row. The update is based on the -OID value as munged by get or passed as keyword, or on the primary key of -the table. The dictionary is modified, if possible, to reflect any changes -caused by the update due to triggers, rules, default values, etc. +OID value as munged by :meth:`DB.get` or passed as keyword, or on the primary +key of the table. The dictionary is modified, if possible, to reflect any +changes caused by the update due to triggers, rules, default values, etc. Like insert, the dictionary is optional and updates will be performed on the fields in the keywords. There must be an OID or primary key either in the dictionary where the OID must be munged, or in the keywords -where it can be simply the string 'oid'. +where it can be simply the string ``'oid'``. query -- execute a SQL command string ------------------------------------- @@ -415,11 +415,13 @@ delete -- delete a row from a database table :param str table: name of table :param dict d: optional dictionary of values :rtype: None + :raises ProgrammingError: table has no primary key, + row is still referenced or missing privilege This method deletes the row from a table. It deletes based on the OID value -as munged by get or passed as keyword, or on the primary key of the table. -The return value is the number of deleted rows (i.e. 0 if the row did not -exist and 1 if the row was deleted). +as munged by :meth:`DB.get` or passed as keyword, or on the primary key of +the table. The return value is the number of deleted rows (i.e. 0 if the +row did not exist and 1 if the row was deleted). truncate -- Quickly empty database tables ----------------------------------------- diff --git a/pg.py b/pg.py index a60b4658..c5b34587 100644 --- a/pg.py +++ b/pg.py @@ -668,20 +668,20 @@ def set_parameter(self, parameter, value=None, local=False): def query(self, qstr, *args): """Executes a SQL command string. - This method simply sends a SQL query to the database. If the query is + This method simply sends a SQL query to the database. If the query is an insert statement that inserted exactly one row into a table that has OIDs, the return value is the OID of the newly inserted row. If the query is an update or delete statement, or an insert statement that did not insert exactly one row in a table with OIDs, then the - number of rows affected is returned as a string. If it is a statement + number of rows affected is returned as a string. If it is a statement that returns rows as a result (usually a select statement, but maybe also an "insert/update ... returning" statement), this method returns a pgqueryobject that can be accessed via getresult() or dictresult() - or simply printed. Otherwise, it returns `None`. + or simply printed. Otherwise, it returns `None`. The query can contain numbered parameters of the form $1 in place - of any data constant. Arguments given after the query string will - be substituted for the corresponding numbered parameter. Parameter + of any data constant. Arguments given after the query string will + be substituted for the corresponding numbered parameter. Parameter values can also be given as a single list or tuple argument. Note that the query string must not be passed as a unicode value, @@ -872,7 +872,7 @@ def has_table_privilege(self, cl, privilege='select'): def get(self, cl, arg, keyname=None): """Get a row from a database table or view. - This method is the basic mechanism to get a single row. The keyname + This method is the basic mechanism to get a single row. It assumes that the key specifies a unique row. If keyname is not specified then the primary key for the table is used. If arg is a dictionary then the value for the key is taken from it and it is modified to @@ -984,7 +984,7 @@ def update(self, cl, d=None, **kw): """Update an existing row in a database table. Similar to insert but updates an existing row. The update is based - on the OID value as munged by get or passed as keyword, or on the + on the OID value as munged by get() or passed as keyword, or on the primary key of the table. The dictionary is modified, if possible, to reflect any changes caused by the update due to triggers, rules, default values, etc. @@ -1075,7 +1075,7 @@ def delete(self, cl, d=None, **kw): """Delete an existing row in a database table. This method deletes the row from a table. It deletes based on the - OID value as munged by get or passed as keyword, or on the primary + OID value as munged by get() or passed as keyword, or on the primary key of the table. The return value is the number of deleted rows (i.e. 0 if the row did not exist and 1 if the row was deleted). From ebaaab0718e9f1a63bd3dc7b1eb459aa8c18bfcf Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Tue, 19 Jan 2016 17:00:26 +0000 Subject: [PATCH 110/144] Removed misleading sentence from docs The insert() method does in fact support inserting into views in newer PostgreSQL versions or when the necessary rules have been created. --- docs/contents/pg/db_wrapper.rst | 3 --- pg.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index 22fb92e6..3ba7cbde 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -328,9 +328,6 @@ added to or replace the entry in the dictionary. The dictionary is then, if possible, reloaded with the values actually inserted in order to pick up values modified by rules, triggers, etc. -Note: The method currently doesn't support insert into views -although PostgreSQL does. - update -- update a row in a database table ------------------------------------------ diff --git a/pg.py b/pg.py index c5b34587..7a0c2f09 100644 --- a/pg.py +++ b/pg.py @@ -938,9 +938,6 @@ def insert(self, cl, d=None, **kw): The dictionary is then, if possible, reloaded with the values actually inserted in order to pick up values modified by rules, triggers, etc. - Note: The method currently doesn't support insert into views - although PostgreSQL does. - """ qcl = self._add_schema(cl) qoid = _oid_key(qcl) From 2918b6e3be42a4affeae755f1a15ae676d889f78 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 20 Jan 2016 18:22:10 +0000 Subject: [PATCH 111/144] Back port some minor fixes from the trunk This also gives better error message if test runner does not support unittest2. --- docs/contents/pg/db_wrapper.rst | 18 +++++++++--------- pg.py | 2 +- tests/test_classic_connection.py | 8 ++++++++ tests/test_classic_dbwrapper.py | 8 ++++++++ 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index 3ba7cbde..c772109b 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -144,7 +144,7 @@ get/set_parameter -- get or set run-time parameters :returns: the current value(s) of the run-time parameter(s) :rtype: str, list or dict :raises TypeError: Invalid parameter type(s) - :raises ProgrammingError: Invalid parameter name(s) + :raises pg.ProgrammingError: Invalid parameter name(s) If the parameter is a string, the return value will also be a string that is the current setting of the run-time parameter with that name. @@ -162,7 +162,7 @@ of all existing configuration parameters. .. versionadded:: 4.2 -.. method:: DB.set_parameter(self, parameter, [value], [local]) +.. method:: DB.set_parameter(parameter, [value], [local]) Set the value of run-time parameters @@ -172,7 +172,7 @@ of all existing configuration parameters. :type param: str or None :raises TypeError: Invalid parameter type(s) :raises ValueError: Invalid value argument(s) - :raises ProgrammingError: Invalid parameter name(s) or values + :raises pg.ProgrammingError: Invalid parameter name(s) or values If the parameter and the value are strings, the run-time parameter will be set to that value. If no value or *None* is passed as a value, @@ -295,7 +295,7 @@ get -- get a row from a database table or view :param str keyname: name of field to use as key (optional) :returns: A dictionary - the keys are the attribute names, the values are the row values. - :raises ProgrammingError: no primary key or missing privilege + :raises pg.ProgrammingError: no primary key or missing privilege This method is the basic mechanism to get a single row. It assumes that the key specifies a unique row. If *keyname* is not specified, @@ -318,7 +318,7 @@ insert -- insert a row into a database table :param dict d: optional dictionary of values :returns: the inserted values in the database :rtype: dict - :raises ProgrammingError: missing privilege or conflict + :raises pg.ProgrammingError: missing privilege or conflict This method inserts a row into a table. If the optional dictionary is not supplied then the required values must be included as keyword/value @@ -339,7 +339,7 @@ update -- update a row in a database table :param dict d: optional dictionary of values :returns: the new row in the database :rtype: dict - :raises ProgrammingError: no primary key or missing privilege + :raises pg.ProgrammingError: no primary key or missing privilege Similar to insert but updates an existing row. The update is based on the OID value as munged by :meth:`DB.get` or passed as keyword, or on the primary @@ -412,7 +412,7 @@ delete -- delete a row from a database table :param str table: name of table :param dict d: optional dictionary of values :rtype: None - :raises ProgrammingError: table has no primary key, + :raises pg.ProgrammingError: table has no primary key, row is still referenced or missing privilege This method deletes the row from a table. It deletes based on the OID value @@ -420,10 +420,10 @@ as munged by :meth:`DB.get` or passed as keyword, or on the primary key of the table. The return value is the number of deleted rows (i.e. 0 if the row did not exist and 1 if the row was deleted). -truncate -- Quickly empty database tables +truncate -- quickly empty database tables ----------------------------------------- -.. method:: DB.truncate(self, table, [restart], [cascade], [only]): +.. method:: DB.truncate(table, [restart], [cascade], [only]) Empty a table or set of tables diff --git a/pg.py b/pg.py index 7a0c2f09..6910446f 100644 --- a/pg.py +++ b/pg.py @@ -1162,7 +1162,7 @@ def truncate(self, table, restart=False, cascade=False, only=False): q.append('CASCADE') q = ' '.join(q) self._do_debug(q) - return self.query(q) + return self.db.query(q) def notification_handler(self, event, callback, arg_dict=None, timeout=None, stop_event=None): diff --git a/tests/test_classic_connection.py b/tests/test_classic_connection.py index 030cce51..4227af2a 100755 --- a/tests/test_classic_connection.py +++ b/tests/test_classic_connection.py @@ -662,6 +662,8 @@ def testUnicodeQuery(self): class TestInserttable(unittest.TestCase): """Test inserttable method.""" + cls_set_up = False + @classmethod def setUpClass(cls): c = connect() @@ -671,6 +673,7 @@ def setUpClass(cls): "d numeric, f4 real, f8 double precision, m money," "c char(1), v4 varchar(4), c4 char(4), t text)") c.close() + cls.cls_set_up = True @classmethod def tearDownClass(cls): @@ -679,6 +682,7 @@ def tearDownClass(cls): c.close() def setUp(self): + self.assertTrue(self.cls_set_up) self.c = connect() self.c.query("set lc_monetary='C'") self.c.query("set datestyle='ISO,YMD'") @@ -787,12 +791,15 @@ def testInserttableMaxValues(self): class TestDirectSocketAccess(unittest.TestCase): """Test copy command with direct socket access.""" + cls_set_up = False + @classmethod def setUpClass(cls): c = connect() c.query("drop table if exists test cascade") c.query("create table test (i int, v varchar(16))") c.close() + cls.cls_set_up = True @classmethod def tearDownClass(cls): @@ -801,6 +808,7 @@ def tearDownClass(cls): c.close() def setUp(self): + self.assertTrue(self.cls_set_up) self.c = connect() self.c.query("set datestyle='ISO,YMD'") diff --git a/tests/test_classic_dbwrapper.py b/tests/test_classic_dbwrapper.py index 9be18848..1aa3a28c 100755 --- a/tests/test_classic_dbwrapper.py +++ b/tests/test_classic_dbwrapper.py @@ -277,6 +277,8 @@ class DB2: class TestDBClass(unittest.TestCase): """Test the methods of the DB class wrapped pg connection.""" + cls_set_up = False + @classmethod def setUpClass(cls): db = DB() @@ -288,6 +290,7 @@ def setUpClass(cls): db.query("create or replace view test_view as" " select i4, v4 from test") db.close() + cls.cls_set_up = True @classmethod def tearDownClass(cls): @@ -296,6 +299,7 @@ def tearDownClass(cls): db.close() def setUp(self): + self.assertTrue(self.cls_set_up) self.db = DB() query = self.db.query query('set client_encoding=utf8') @@ -1568,6 +1572,8 @@ def testDebugWithCallable(self): class TestSchemas(unittest.TestCase): """Test correct handling of schemas (namespaces).""" + cls_set_up = False + @classmethod def setUpClass(cls): db = DB() @@ -1591,6 +1597,7 @@ def setUpClass(cls): query("create table %s.t%d with oids as select 1 as n, %d as d" % (schema, num_schema, num_schema)) db.close() + cls.cls_set_up = True @classmethod def tearDownClass(cls): @@ -1607,6 +1614,7 @@ def tearDownClass(cls): db.close() def setUp(self): + self.assertTrue(self.cls_set_up) self.db = DB() def tearDown(self): From 222941e595508cd7a11c3bd7eaf5100e70d96770 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 21 Jan 2016 13:04:52 +0000 Subject: [PATCH 112/144] Do not call set_decimal in pgdb The set_decimal() function is only relevant for the pg module (its getresult() method). Don't call it in the pgdb module; the two modules should not interfere with each other. --- pgdb.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pgdb.py b/pgdb.py index 6f7cb5fd..3fdb7e8b 100644 --- a/pgdb.py +++ b/pgdb.py @@ -72,7 +72,6 @@ from time import localtime try: from decimal import Decimal - set_decimal(Decimal) except ImportError: # Python < 2.4, unsupported Decimal = float # use float instead of Decimal try: @@ -113,7 +112,6 @@ def decimal_type(decimal_type=None): global Decimal if decimal_type is not None: _cast['numeric'] = Decimal = decimal_type - set_decimal(Decimal) return Decimal From 8e507458e215e77860ba76d695fcf8fd5f732674 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Thu, 21 Jan 2016 19:07:16 +0000 Subject: [PATCH 113/144] Backport some minor doc fixes to the 4.x branch --- docs/contents/pg/db_wrapper.rst | 28 ++++++------ docs/contents/pg/module.rst | 13 ++++-- docs/contents/pgdb/types.rst | 79 ++++++++++++++++++++++----------- pg.py | 2 + pgdb.py | 8 ++-- 5 files changed, 82 insertions(+), 48 deletions(-) diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index c772109b..f334ac58 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -451,8 +451,14 @@ the parameter *only* can also be a list of corresponding boolean values. .. versionadded:: 4.2 -escape_literal -- escape a literal string for use within SQL ------------------------------------------------------------- +escape_literal/identifier/string/bytea -- escape for SQL +-------------------------------------------------------- + +The following methods escape text or binary strings so that they can be +inserted directly into an SQL command. Except for :meth:`DB.escape_byte`, +you don't need to call these methods for the strings passed as parameters +to :meth:`DB.query`. You also don't need to call any of these methods +when storing data using :meth:`DB.insert` and similar. .. method:: DB.escape_literal(string) @@ -469,9 +475,6 @@ from being interpreted specially by the SQL parser. .. versionadded:: 4.1 -escape_identifier -- escape an identifier string for use within SQL -------------------------------------------------------------------- - .. method:: DB.escape_identifier(string) Escape a string for use within SQL as an identifier @@ -488,9 +491,6 @@ contain upper case characters whose case should be preserved. .. versionadded:: 4.1 -escape_bytea -- escape binary data for use within SQL ------------------------------------------------------ - .. method:: DB.escape_bytea(datastring) Escape binary data for use within SQL as type ``bytea`` @@ -499,12 +499,12 @@ escape_bytea -- escape binary data for use within SQL :returns: the escaped string :rtype: str -Similar to the module function with the same name, but the -behavior of this method is adjusted depending on the connection properties -(in particular, whether standard-conforming strings are enabled). +Similar to the module function :func:`pg.escape_string` with the same name, +but the behavior of this method is adjusted depending on the connection +properties (such as character encoding). -unescape_bytea -- unescape data that has been retrieved as text ---------------------------------------------------------------- +unescape_bytea -- unescape data retrieved from the database +----------------------------------------------------------- .. method:: DB.unescape_bytea(string) @@ -514,7 +514,7 @@ unescape_bytea -- unescape data that has been retrieved as text :returns: byte string containing the binary data :rtype: str -See the module function with the same name. +See the module function :func:`pg.unescape_bytea` with the same name. use_regtypes -- determine use of regular type names --------------------------------------------------- diff --git a/docs/contents/pg/module.rst b/docs/contents/pg/module.rst index 83deb878..564e9adc 100644 --- a/docs/contents/pg/module.rst +++ b/docs/contents/pg/module.rst @@ -445,8 +445,10 @@ get/set_namedresult -- conversion to named tuples Get the function that converts to named tuples -This function returns the function used by PyGreSQL to construct the -result of the :meth:`pgqueryobject.namedresult` method. +This returns the function used by PyGreSQL to construct the result of the +:meth:`pgqueryobject.namedresult` method. + +.. versionadded:: 4.1 .. function:: set_namedresult(func) @@ -454,8 +456,11 @@ result of the :meth:`pgqueryobject.namedresult` method. :param func: the function to be used to convert results to named tuples -You can use this if you want to create different kinds of named tuples -returned by the :meth:`pgqueryobject.namedresult` method. +You can use this if you want to create different kinds of named tuples returned +by the :meth:`pgqueryobject.namedresult` method. If you set this function to +*None*, then it will become equal to :meth:`pgqueryobject.getresult`. + +.. versionadded:: 4.1 Module constants diff --git a/docs/contents/pgdb/types.rst b/docs/contents/pgdb/types.rst index ed03c175..4bc379e0 100644 --- a/docs/contents/pgdb/types.rst +++ b/docs/contents/pgdb/types.rst @@ -3,15 +3,22 @@ pgdbType -- Type objects and constructors .. py:currentmodule:: pgdb -.. class:: pgdbType - -The :attr:`pgdbCursor.description` attribute returns information about each -of the result columns of a query. The *type_code* must compare equal to one -of the :class:`pgdbType` objects defined below. Type objects can be equal to -more than one type code (e.g. :class:`DATETIME` is equal to the type codes -for date, time and timestamp columns). - -The :mod:`pgdb` module exports the following constructors and singletons: +Type constructors +----------------- + +For binding to an operation's input parameters, PostgreSQL needs to have +the input in a particular format. However, from the parameters to the +:meth:`pgdbCursor.execute` and :meth:`pgdbCursor.executemany` methods it +is not always obvious as which PostgreSQL data types they shall be bound. +For instance, a Python string could be bound as a simple ``char`` value, +or also as a ``date`` or a ``time``. To make the intention clear in such +cases, you can wrap the parameters in type helper objects. PyGreSQL provides +the constructors defined below to create such objects that can hold special +values. When passed to the cursor methods, PyGreSQL can then detect the +proper type of the input parameter and bind it accordingly. + +The :mod:`pgdb` module exports the following type constructors as part of +the DB-API 2 standard: .. function:: Date(year, month, day) @@ -41,70 +48,90 @@ The :mod:`pgdb` module exports the following constructors and singletons: Construct an object capable of holding a (long) binary string value -.. class:: STRING +.. note:: + + SQL ``NULL`` values are always represented by the Python *None* singleton + on input and output. + +Type objects +------------ + +.. class:: pgdbType + +The :attr:`pgdbCursor.description` attribute returns information about each +of the result columns of a query. The *type_code* must compare equal to one +of the :class:`pgdbType` objects defined below. Type objects can be equal to +more than one type code (e.g. :class:`DATETIME` is equal to the type codes +for date, time and timestamp columns). + +The pgdb module exports the following :class:`Type` objects as part of the +DB-API 2 standard: + +.. object:: STRING Used to describe columns that are string-based (e.g. ``char``, ``varchar``, ``text``) -.. class:: BINARY type +.. object:: BINARY Used to describe (long) binary columns (``bytea``) -.. class:: NUMBER +.. object:: NUMBER Used to describe numeric columns (e.g. ``int``, ``float``, ``numeric``, ``money``) -.. class:: DATETIME +.. object:: DATETIME Used to describe date/time columns (e.g. ``date``, ``time``, ``timestamp``, ``interval``) -.. class:: ROWID +.. object:: ROWID Used to describe the ``oid`` column of PostgreSQL database tables .. note:: - The following more specific types are not part of the DB-API 2 standard. + The following more specific type objects are not part of the DB-API 2 standard. -.. class:: BOOL +.. object:: BOOL Used to describe ``boolean`` columns -.. class:: SMALLINT +.. object:: SMALLINT Used to describe ``smallint`` columns -.. class:: INTEGER +.. object:: INTEGER Used to describe ``integer`` columns -.. class:: LONG +.. object:: LONG Used to describe ``bigint`` columns -.. class:: FLOAT +.. object:: FLOAT Used to describe ``float`` columns -.. class:: NUMERIC +.. object:: NUMERIC Used to describe ``numeric`` columns -.. class:: MONEY +.. object:: MONEY Used to describe ``money`` columns -.. class:: DATE +.. object:: DATE Used to describe ``date`` columns -.. class:: TIME +.. object:: TIME Used to describe ``time`` columns -.. class:: TIMESTAMP +.. object:: TIMESTAMP Used to describe ``timestamp`` columns -.. class:: INTERVAL +.. object:: INTERVAL Used to describe date and time ``interval`` columns + diff --git a/pg.py b/pg.py index 6910446f..80f5bd6d 100644 --- a/pg.py +++ b/pg.py @@ -140,6 +140,8 @@ def _prg_error(msg): return _db_error(msg, ProgrammingError) +# The notification handler + class NotificationHandler(object): """A PostgreSQL client-side asynchronous notification handler.""" diff --git a/pgdb.py b/pgdb.py index 3fdb7e8b..00505b50 100644 --- a/pgdb.py +++ b/pgdb.py @@ -671,7 +671,7 @@ def Time(hour, minute=0, second=0, microsecond=0): def Timestamp(year, month, day, hour=0, minute=0, second=0, microsecond=0): - """construct an object holding a time stamp value.""" + """Construct an object holding a time stamp value.""" return datetime(year, month, day, hour, minute, second, microsecond) @@ -681,17 +681,17 @@ def DateFromTicks(ticks): def TimeFromTicks(ticks): - """construct an object holding a time value from the given ticks value.""" + """Construct an object holding a time value from the given ticks value.""" return Time(*localtime(ticks)[3:6]) def TimestampFromTicks(ticks): - """construct an object holding a time stamp from the given ticks value.""" + """Construct an object holding a time stamp from the given ticks value.""" return Timestamp(*localtime(ticks)[:6]) class Binary(str): - """construct an object capable of holding a binary (long) string value.""" + """Construct an object capable of holding a binary (long) string value.""" # If run as script, print some information: From 960d0922e00072fedf425fc2115a6f7d2dc65a68 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 22 Jan 2016 14:27:21 +0000 Subject: [PATCH 114/144] Change name of links in mktar script The source distribution is now called *.tar.gz (since this is common and setup.py creates it like this), but the links were still *.gz Also, add README file to setup.cfg for PyPi --- mktar | 4 ++-- setup.cfg | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mktar b/mktar index 108d4bc9..93b85027 100755 --- a/mktar +++ b/mktar @@ -19,10 +19,10 @@ if [ -f BETA ] then VERSION=$VERSION-pre`date +"%y%m%d"` PACKAGE=pygresql.pkg-beta - SYMLINK=PyGreSQL-beta.tgz + SYMLINK=PyGreSQL-beta.tar.gz else PACKAGE=pygresql.pkg - SYMLINK=PyGreSQL.tgz + SYMLINK=PyGreSQL.tar.gz fi # Package up as a source tarball in the distribution directory. diff --git a/setup.cfg b/setup.cfg index c12b8440..ecaeed8f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,4 +7,7 @@ large_objects = 1 default_vars = 1 # enable string escaping functions # (PostgreSQL version 9.0 and higher) -escaping_funcs = 1 \ No newline at end of file +escaping_funcs = 1 + +[metadata] +description-file = README.rst From 4115c88695d6be4418c9c4982f3cec5fad1f084f Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Fri, 22 Jan 2016 16:19:40 +0000 Subject: [PATCH 115/144] Start preparing for minor release. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b8e42e5e..ea166504 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #! /usr/bin/python # $Id$ -"""Setup script for PyGreSQL version 4.2 +"""Setup script for PyGreSQL version 4.2.1 PyGreSQL is an open-source Python module that interfaces to a PostgreSQL database. It embeds the PostgreSQL query library to allow @@ -32,7 +32,7 @@ """ -version = '4.2' +version = '4.2.1' import sys From 68e3b6ba7febf6c72367306475a98714998818af Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 22 Jan 2016 16:49:58 +0000 Subject: [PATCH 116/144] As we grow older and more mature, so does PostgreSQL --- docs/about.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/about.rst b/docs/about.rst index 6cfd091d..03576b07 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -9,7 +9,7 @@ PostgreSQL features from a Python script. | For licensing details, see the full :doc:`copyright`. **PostgreSQL** is a highly scalable, SQL compliant, open source -object-relational database management system. With more than 15 years +object-relational database management system. With more than 20 years of development history, it is quickly becoming the de facto database for enterprise level open source solutions. Best of all, PostgreSQL's source code is available under the most liberal From dad924bf8f3168934a8ea82f2cfbb5de8d96140e Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 25 Jan 2016 22:19:50 +0000 Subject: [PATCH 117/144] Add release date of 4.2 to changelog --- docs/contents/changelog.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contents/changelog.rst b/docs/contents/changelog.rst index 12aff541..c1bfd8c8 100644 --- a/docs/contents/changelog.rst +++ b/docs/contents/changelog.rst @@ -1,8 +1,8 @@ ChangeLog ========= -Version 4.2 ------------ +Version 4.2 (2016-01-21) +------------------------ - The supported Python versions are 2.4 to 2.7. - PostgreSQL is supported in all versions from 8.3 to 9.5. - Set a better default for the user option "escaping-funcs". From 47befa33608a83132fc5a451c58a8da5caf889d5 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Tue, 26 Jan 2016 11:56:08 +0000 Subject: [PATCH 118/144] Add more safety checks to the mktar script Make sure the right files are packaged, and with the right permissions. Also specified the exact top-level source files in MANIFEST.in, in order to make sure that other files used for testing etc. do not accidentally get packaged. The mktar script now also checks the number of top-level files and warns if something does not fit. --- MANIFEST.in | 21 ++++++++++----- docs/community/source.rst | 4 +-- mktar | 57 +++++++++++++++++++++++++++++++++------ 3 files changed, 66 insertions(+), 16 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index feb1c0c7..ba95795b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,10 +1,19 @@ -include *.c -include *.h -include *.py -include *.cfg -include *.rst -include *.txt + +include pgmodule.c +include pgtypes.h +include pgfs.h + +include pg.py +include pgdb.py +include setup.py + +include setup.cfg + +include README.rst +include LICENSE.txt + recursive-include tests *.py + include docs/Makefile include docs/make.bat include docs/*.py diff --git a/docs/community/source.rst b/docs/community/source.rst index 32447326..e8bfc035 100644 --- a/docs/community/source.rst +++ b/docs/community/source.rst @@ -4,9 +4,9 @@ Access to the source repository We are using a central `Subversion `_ source code repository for PyGreSQL. -The repository can be checked out with the command:: +The current trunk of the repository can be checked out with the command:: - svn co svn://svn.pygresql.org/pygresql + svn co svn://svn.pygresql.org/pygresql/trunk You can also browse through the repository using the `PyGreSQL Trac browser `_. diff --git a/mktar b/mktar index 93b85027..57baf459 100755 --- a/mktar +++ b/mktar @@ -3,17 +3,35 @@ VERSION=4.2 DISTDIR=/u/pyg/files -# small safety tests -if [ ! -f pgmodule.c ] +# some safety tests +if [ ! -d $DISTDIR ] then - echo "Hmmm. Are you sure you are in the right directory?" + echo "Hmmm. Are you sure you are on the right server?" exit 1 fi -if [ ! -d $DISTDIR ] +if [ ! -f setup.py -o ! -f pgmodule.c -o ! -d tests -o ! -d docs ] then - echo "Hmmm. Are you sure you are on the right server?" + echo "Hmmm. Are you sure you are in the right directory?" exit 1 fi +FILES="*.c *.h *.py *.cfg *.rst *.txt" +NUMFILES=`ls $FILES | wc -l` +if [ "$NUMFILES" != "9" ] +then + echo "Hmmm. The number of top-level files seems to be wrong:" + ls $FILES + echo "Maybe you should do a clean checkout first." + echo "If something has changed, edit MANIFEST.in and mktar." + exit 1 +fi +FILES="mktar mkdocs docs tests pg.py pgdb.py pgmodule.c setup.cfg" +PERMS=`stat --printf="%a" $FILES` +if [ $? -ne 0 -o "$PERMS" != '755755755755644644644644' ] +then + echo "Hmmm. File permissions are not set properly." + echo "Use a filesystem with permissions and do a clean checkout first." + exit 1 +fi if [ -f BETA ] then @@ -28,24 +46,47 @@ fi # Package up as a source tarball in the distribution directory. echo "Making source tarball..." +echo + +umask 0022 # Make sure that the documentation has been built. -./mkdocs +if ! ./mkdocs +then + echo "Hmmm. The documentation could not be built." + exit 1 +fi # Package as source distribution. rm -rf build dist -python setup.py sdist +if ! python2 setup.py sdist +then + echo "Hmmm. The source distribution could not be created." + exit 1 +fi DF=`ls dist` +if [ $? -ne 0 -o -z "$DF" ] +then + echo "Hmmm. The source distribution could not be found." + exit 1 +fi + TF=$DISTDIR/$DF -cp dist/$DF $TF +if ! cp dist/$DF $TF +then + echo "Hmmm. The source distribution could not be copied." + exit 1 +fi + chmod 644 $TF rm -f $DISTDIR/$SYMLINK ln -s $DF $DISTDIR/$SYMLINK +echo echo "$TF has been built." From f5c5d70b9e48e16719598a68306081ac8b555925 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Wed, 27 Jan 2016 18:26:41 +0000 Subject: [PATCH 119/144] Add test module for the notification handler Forgot to check this in to the repository in r762. --- tests/test_classic_notification.py | 415 +++++++++++++++++++++++++++++ 1 file changed, 415 insertions(+) create mode 100755 tests/test_classic_notification.py diff --git a/tests/test_classic_notification.py b/tests/test_classic_notification.py new file mode 100755 index 00000000..06d1c4f5 --- /dev/null +++ b/tests/test_classic_notification.py @@ -0,0 +1,415 @@ +#! /usr/bin/python +# -*- coding: utf-8 -*- + +"""Test the classic PyGreSQL interface. + +Sub-tests for the notification handler object. + +Contributed by Christoph Zwerschke. + +These tests need a database to test against. + +""" + +try: + import unittest2 as unittest # for Python < 2.7 +except ImportError: + import unittest + +from time import sleep +from threading import Thread + +import pg # the module under test + +# We need a database to test against. If LOCAL_PyGreSQL.py exists we will +# get our information from that. Otherwise we use the defaults. +# The current user must have create schema privilege on the database. +dbname = 'unittest' +dbhost = None +dbport = 5432 + +debug = False # let DB wrapper print debugging output + +try: + from LOCAL_PyGreSQL import * +except ImportError: + pass + + +def DB(): + """Create a DB wrapper object connecting to the test database.""" + db = pg.DB(dbname, dbhost, dbport) + if debug: + db.debug = debug + return db + + +class TestSyncNotification(unittest.TestCase): + """Test notification handler running in the same thread.""" + + def setUp(self): + self.db = DB() + self.timeout = None + self.called = True + self.payloads = [] + + def tearDown(self): + if self.db: + self.db.close() + + def callback(self, arg_dict): + if arg_dict is None: + self.timeout = True + else: + self.timeout = False + self.payloads.append(arg_dict.get('extra')) + + def get_handler(self, event=None, arg_dict=None, stop_event=None): + if not event: + event = 'test_async_notification' + if not stop_event: + stop_event = 'stop_async_notification' + callback = self.callback + handler = self.db.notification_handler( + event, callback, arg_dict, 0, stop_event) + self.assertEqual(handler.event, event) + self.assertEqual(handler.stop_event, stop_event or 'stop_%s' % event) + self.assertIs(handler.callback, callback) + if arg_dict is None: + self.assertEqual(handler.arg_dict, {}) + else: + self.assertIs(handler.arg_dict, arg_dict) + self.assertEqual(handler.timeout, 0) + self.assertFalse(handler.listening) + return handler + + def testCloseHandler(self): + handler = self.get_handler() + self.assertIs(handler.db, self.db) + handler.close() + self.assertRaises(pg.InternalError, self.db.close) + self.db = None + self.assertIs(handler.db, None) + + def testDeleteHandler(self): + handler = self.get_handler('test_del') + self.assertIs(handler.db, self.db) + handler.listen() + self.db.query('notify test_del') + self.db.query('notify test_del') + del handler + self.db.query('notify test_del') + n = 0 + while self.db.getnotify() and n < 4: + n += 1 + self.assertEqual(n, 2) + + def testNotify(self): + handler = self.get_handler() + handler.listen() + self.assertRaises(TypeError, handler.notify, invalid=True) + handler.notify(payload='baz') + handler.notify(stop=True, payload='buz') + handler.unlisten() + self.db.close() + self.db = None + + def testNotifyWithArgsAndPayload(self): + arg_dict = {'foo': 'bar'} + handler = self.get_handler(arg_dict=arg_dict) + self.assertEqual(handler.timeout, 0) + handler.listen() + handler.notify(payload='baz') + handler.notify(payload='biz') + handler() + self.assertIsNotNone(self.timeout) + self.assertFalse(self.timeout) + self.assertEqual(self.payloads, ['baz', 'biz']) + self.assertEqual(arg_dict['foo'], 'bar') + self.assertEqual(arg_dict['event'], handler.event) + self.assertIsInstance(arg_dict['pid'], int) + self.assertEqual(arg_dict['extra'], 'biz') + self.assertTrue(handler.listening) + del self.payloads[:] + handler.notify(stop=True, payload='buz') + handler() + self.assertIsNotNone(self.timeout) + self.assertFalse(self.timeout) + self.assertEqual(self.payloads, ['buz']) + self.assertEqual(arg_dict['foo'], 'bar') + self.assertEqual(arg_dict['event'], handler.stop_event) + self.assertIsInstance(arg_dict['pid'], int) + self.assertEqual(arg_dict['extra'], 'buz') + self.assertFalse(handler.listening) + handler.unlisten() + + def testNotifyWrongEvent(self): + handler = self.get_handler('good_event') + self.assertEqual(handler.timeout, 0) + handler.listen() + handler.notify(payload="note 1") + self.db.query("notify bad_event, 'note 2'") + handler.notify(payload="note 3") + handler() + self.assertIsNotNone(self.timeout) + self.assertFalse(self.timeout) + self.assertEqual(self.payloads, ['note 1', 'note 3']) + self.assertTrue(handler.listening) + del self.payloads[:] + self.db.query('listen bad_event') + handler.notify(payload="note 4") + self.db.query("notify bad_event, 'note 5'") + handler.notify(payload="note 6") + try: + handler() + except pg.DatabaseError, error: + self.assertEqual(str(error), + 'Listening for "good_event" and "stop_good_event",' + ' but notified of "bad_event"') + self.assertIsNotNone(self.timeout) + self.assertFalse(self.timeout) + self.assertEqual(self.payloads, ['note 4']) + self.assertFalse(handler.listening) + + +class TestAsyncNotification(unittest.TestCase): + """Test notification handler running in a separate thread.""" + + def setUp(self): + self.db = DB() + + def tearDown(self): + self.doCleanups() + if self.db: + self.db.close() + + def callback(self, arg_dict): + if arg_dict is None: + self.timeout = True + elif arg_dict is self.arg_dict: + arg_dict = arg_dict.copy() + pid = arg_dict.get('pid') + if isinstance(pid, int): + arg_dict['pid'] = 1 + self.received.append(arg_dict) + else: + self.received.append(dict(error=arg_dict)) + + def start_handler(self, + event=None, arg_dict=None, timeout=5, stop_event=None): + db = DB() + if not event: + event = 'test_async_notification' + if not stop_event: + stop_event = 'stop_async_notification' + callback = self.callback + handler = db.notification_handler( + event, callback, arg_dict, timeout, stop_event) + self.handler = handler + self.assertIsInstance(handler, pg.NotificationHandler) + self.assertEqual(handler.event, event) + self.assertEqual(handler.stop_event, stop_event or 'stop_%s' % event) + self.event = handler.event + self.assertIs(handler.callback, callback) + if arg_dict is None: + self.assertEqual(handler.arg_dict, {}) + else: + self.assertIsInstance(handler.arg_dict, dict) + self.arg_dict = handler.arg_dict + self.assertEqual(handler.timeout, timeout) + self.assertFalse(handler.listening) + thread = Thread(target=handler, name='test_notification_thread') + self.thread = thread + thread.start() + self.stopped = timeout == 0 + self.addCleanup(self.stop_handler) + for n in range(500): + if handler.listening: + break + sleep(0.01) + self.assertTrue(handler.listening) + if not self.stopped: + self.assertTrue(thread.isAlive()) + self.timeout = False + self.received = [] + self.sent = [] + + def stop_handler(self): + handler = self.handler + thread = self.thread + if not self.stopped and self.handler.listening: + self.notify_handler(stop=True) + handler.close() + self.db = None + if thread.isAlive(): + thread.join(5) + self.assertFalse(handler.listening) + self.assertFalse(thread.isAlive()) + + def notify_handler(self, stop=False, payload=None): + event = self.event + if stop: + event = self.handler.stop_event + self.stopped = True + arg_dict = self.arg_dict.copy() + arg_dict.update(event=event, pid=1, extra=payload or '') + self.handler.notify(db=self.db, stop=stop, payload=payload) + self.sent.append(arg_dict) + + def notify_query(self, stop=False, payload=None): + event = self.event + if stop: + event = self.handler.stop_event + self.stopped = True + q = 'notify "%s"' % event + if payload: + q += ", '%s'" % payload + arg_dict = self.arg_dict.copy() + arg_dict.update(event=event, pid=1, extra=payload or '') + self.db.query(q) + self.sent.append(arg_dict) + + def wait(self): + for n in range(500): + if self.timeout: + return False + if len(self.received) >= len(self.sent): + return True + sleep(0.01) + + def receive(self, stop=False): + if not self.sent: + stop = True + if stop: + self.notify_handler(stop=True, payload='stop') + self.assertTrue(self.wait()) + self.assertFalse(self.timeout) + self.assertEqual(self.received, self.sent) + self.received = [] + self.sent = [] + self.assertEqual(self.handler.listening, not self.stopped) + + def testNotifyHandlerEmpty(self): + self.start_handler() + self.notify_handler(stop=True) + self.assertEqual(len(self.sent), 1) + self.receive() + + def testNotifyQueryEmpty(self): + self.start_handler() + self.notify_query(stop=True) + self.assertEqual(len(self.sent), 1) + self.receive() + + def testNotifyHandlerOnce(self): + self.start_handler() + self.notify_handler() + self.assertEqual(len(self.sent), 1) + self.receive() + self.receive(stop=True) + + def testNotifyQueryOnce(self): + self.start_handler() + self.notify_query() + self.receive() + self.notify_query(stop=True) + self.receive() + + def testNotifyWithArgs(self): + arg_dict = {'test': 42, 'more': 43, 'less': 41} + self.start_handler('test_args', arg_dict) + self.notify_query() + self.receive(stop=True) + + def testNotifySeveralTimes(self): + arg_dict = {'test': 1} + self.start_handler(arg_dict=arg_dict) + for count in range(3): + self.notify_query() + self.receive() + arg_dict['test'] += 1 + for count in range(2): + self.notify_handler() + self.receive() + arg_dict['test'] += 1 + for count in range(3): + self.notify_query() + self.receive(stop=True) + + def testNotifyOnceWithPayload(self): + self.start_handler() + self.notify_query(payload='test_payload') + self.receive(stop=True) + + def testNotifyWithArgsAndPayload(self): + self.start_handler(arg_dict={'foo': 'bar'}) + self.notify_query(payload='baz') + self.receive(stop=True) + + def testNotifyQuotedNames(self): + self.start_handler('Hello, World!') + self.notify_query(payload='How do you do?') + self.receive(stop=True) + + def testNotifyWithFivePayloads(self): + self.start_handler('gimme_5', {'test': 'Gimme 5'}) + for count in range(5): + self.notify_query(payload="Round %d" % count) + self.assertEqual(len(self.sent), 5) + self.receive(stop=True) + + def testReceiveImmediately(self): + self.start_handler('immediate', {'test': 'immediate'}) + for count in range(3): + self.notify_query(payload="Round %d" % count) + self.receive() + self.receive(stop=True) + + def testNotifyDistinctInTransaction(self): + self.start_handler('test_transaction', {'transaction': True}) + self.db.begin() + for count in range(3): + self.notify_query(payload='Round %d' % count) + self.db.commit() + self.receive(stop=True) + + def testNotifySameInTransaction(self): + self.start_handler('test_transaction', {'transaction': True}) + self.db.begin() + for count in range(3): + self.notify_query() + self.db.commit() + # these same notifications may be delivered as one, + # so we must not wait for all three to appear + self.sent = self.sent[:1] + self.receive(stop=True) + + def testNotifyNoTimeout(self): + self.start_handler(timeout=None) + self.assertIsNone(self.handler.timeout) + self.assertTrue(self.handler.listening) + sleep(0.02) + self.assertFalse(self.timeout) + self.receive(stop=True) + + def testNotifyZeroTimeout(self): + self.start_handler(timeout=0) + self.assertEqual(self.handler.timeout, 0) + self.assertTrue(self.handler.listening) + self.assertFalse(self.timeout) + + def testNotifyWithoutTimeout(self): + self.start_handler(timeout=1) + self.assertEqual(self.handler.timeout, 1) + sleep(0.02) + self.assertFalse(self.timeout) + self.receive(stop=True) + + def testNotifyWithTimeout(self): + self.start_handler(timeout=0.01) + sleep(0.02) + self.assertTrue(self.timeout) + + +if __name__ == '__main__': + unittest.main() From e645c4dfc68f9cbab296e9adfa88d5669b13b8f0 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Mon, 1 Feb 2016 05:56:27 +0000 Subject: [PATCH 120/144] Backport minor fixes from trunk to 4.x Fixed one reference counting issue with setting the notice receiver, also support unmounting the notice receiver callback. Replaced some overly complicated or unofficial ways of calling Python functions with cleaner code. --- pgmodule.c | 34 ++++++++++++++++---------------- tests/test_classic_connection.py | 5 ++++- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/pgmodule.c b/pgmodule.c index 580df30a..4f72500d 100644 --- a/pgmodule.c +++ b/pgmodule.c @@ -1787,10 +1787,11 @@ 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); - PyObject *args, *ret; + PyObject *ret; if (notice) { notice->pgcnx = arg; @@ -1801,10 +1802,8 @@ void notice_receiver(void *arg, const PGresult *res) Py_INCREF(Py_None); notice = (pgnoticeobject *)(void *)Py_None; } - args = Py_BuildValue("(O)", notice); - ret = PyObject_CallObject(proc, args); + ret = PyObject_CallFunction(proc, "(O)", notice); Py_XDECREF(ret); - Py_DECREF(args); } PyGILState_Release(gstate); } @@ -1963,9 +1962,15 @@ pg_set_notice_receiver(pgobject * self, PyObject * args) if (PyArg_ParseTuple(args, "O", &proc)) { - if (PyCallable_Check(proc)) + if (proc == Py_None) + { + Py_XDECREF(self->notice_receiver); + self->notice_receiver = NULL; + Py_INCREF(Py_None); ret = Py_None; + } + else if (PyCallable_Check(proc)) { - Py_XINCREF(proc); + Py_XINCREF(proc); Py_XDECREF(self->notice_receiver); self->notice_receiver = proc; PQsetNoticeReceiver(self->cnx, notice_receiver, self); Py_INCREF(Py_None); ret = Py_None; @@ -2202,15 +2207,14 @@ pgquery_getresult(pgqueryobject *self, PyObject *args) case PYGRES_DECIMAL: if (decimal) { - tmp_obj = Py_BuildValue("(s)", s); - val = PyEval_CallObject(decimal, tmp_obj); + val = PyObject_CallFunction(decimal, "(s)", s); } else { tmp_obj = PyString_FromString(s); val = PyFloat_FromString(tmp_obj, NULL); + Py_DECREF(tmp_obj); } - Py_DECREF(tmp_obj); break; case PYGRES_BOOL: @@ -2343,15 +2347,14 @@ pgquery_dictresult(pgqueryobject *self, PyObject *args) case PYGRES_DECIMAL: if (decimal) { - tmp_obj = Py_BuildValue("(s)", s); - val = PyEval_CallObject(decimal, tmp_obj); + val = PyObject_CallFunction(decimal, "(s)", s); } else { tmp_obj = PyString_FromString(s); val = PyFloat_FromString(tmp_obj, NULL); + Py_DECREF(tmp_obj); } - Py_DECREF(tmp_obj); break; case PYGRES_BOOL: @@ -2401,8 +2404,7 @@ static char pgquery_namedresult__doc__[] = static PyObject * pgquery_namedresult(pgqueryobject *self, PyObject *args) { - PyObject *arglist, - *ret; + PyObject *ret; /* checks args (args == NULL for an internal call) */ if (args && !PyArg_ParseTuple(args, "")) @@ -2419,9 +2421,7 @@ pgquery_namedresult(pgqueryobject *self, PyObject *args) return NULL; } - arglist = Py_BuildValue("(O)", self); - ret = PyObject_CallObject(namedresult, arglist); - Py_DECREF(arglist); + ret = PyObject_CallFunction(namedresult, "(O)", self); if (ret == NULL) return NULL; diff --git a/tests/test_classic_connection.py b/tests/test_classic_connection.py index 4227af2a..744bb615 100755 --- a/tests/test_classic_connection.py +++ b/tests/test_classic_connection.py @@ -903,14 +903,17 @@ def testGetNoticeReceiver(self): self.assertIsNone(self.c.get_notice_receiver()) def testSetNoticeReceiver(self): - self.assertRaises(TypeError, self.c.set_notice_receiver, None) self.assertRaises(TypeError, self.c.set_notice_receiver, 42) + self.assertRaises(TypeError, self.c.set_notice_receiver, 'invalid') self.assertIsNone(self.c.set_notice_receiver(lambda notice: None)) + self.assertIsNone(self.c.set_notice_receiver(None)) def testSetAndGetNoticeReceiver(self): r = lambda notice: None self.assertIsNone(self.c.set_notice_receiver(r)) self.assertIs(self.c.get_notice_receiver(), r) + self.assertIsNone(self.c.set_notice_receiver(None)) + self.assertIsNone(self.c.get_notice_receiver()) def testNoticeReceiver(self): self.c.query('''create function bilbo_notice() returns void AS $$ From e8649a3144b1e7bd74d9571db4a22a48a658e4c1 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 6 Feb 2016 17:30:34 +0000 Subject: [PATCH 121/144] Add requirements file for buildings the docs --- docs/requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..c354e8d9 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +cloud_sptheme>=1.7.1 \ No newline at end of file From ada51085b7657b5be12f0cc396be4576dceb0273 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 6 Feb 2016 19:27:09 +0000 Subject: [PATCH 122/144] Make Sphinx docs work without the Cloud theme Our custom navigation bar on the page header works only with the Cloud theme, so we use a toc as the start page when creating the docs without Cloud theme. --- docs/about.rst | 42 +++----------------------------------- docs/about.txt | 40 ++++++++++++++++++++++++++++++++++++ docs/conf.py | 35 +++++++++++++++++++++---------- docs/download/download.rst | 10 +++++++-- docs/index.rst | 2 +- docs/toc.rst | 14 +++++++++++++ 6 files changed, 90 insertions(+), 53 deletions(-) create mode 100644 docs/about.txt create mode 100644 docs/toc.rst diff --git a/docs/about.rst b/docs/about.rst index 03576b07..3e61d030 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -1,40 +1,4 @@ -**PyGreSQL** is an *open-source* `Python `_ module -that interfaces to a `PostgreSQL `_ database. -It embeds the PostgreSQL query library to allow easy use of the powerful -PostgreSQL features from a Python script. +About PyGreSQL +============== - | This software is copyright © 1995, Pascal Andre. - | Further modifications are copyright © 1997-2008 by D'Arcy J.M. Cain. - | Further modifications are copyright © 2009-2016 by the PyGreSQL team. - | For licensing details, see the full :doc:`copyright`. - -**PostgreSQL** is a highly scalable, SQL compliant, open source -object-relational database management system. With more than 20 years -of development history, it is quickly becoming the de facto database -for enterprise level open source solutions. -Best of all, PostgreSQL's source code is available under the most liberal -open source license: the BSD license. - -**Python** Python is an interpreted, interactive, object-oriented -programming language. It is often compared to Tcl, Perl, Scheme or Java. -Python combines remarkable power with very clear syntax. It has modules, -classes, exceptions, very high level dynamic data types, and dynamic typing. -There are interfaces to many system calls and libraries, as well as to -various windowing systems (X11, Motif, Tk, Mac, MFC). New built-in modules -are easily written in C or C++. Python is also usable as an extension -language for applications that need a programmable interface. -The Python implementation is copyrighted but freely usable and distributable, -even for commercial use. - -**PyGreSQL** is a Python module that interfaces to a PostgreSQL database. -It embeds the PostgreSQL query library to allow easy use of the powerful -PostgreSQL features from a Python script. - -PyGreSQL is developed and tested on a NetBSD system, but it also runs on -most other platforms where PostgreSQL and Python is running. It is based -on the PyGres95 code written by Pascal Andre (andre@chimay.via.ecp.fr). -D'Arcy (darcy@druid.net) renamed it to PyGreSQL starting with -version 2.0 and serves as the "BDFL" of PyGreSQL. - -The current version PyGreSQL 4.2 needs PostgreSQL 8.3 or newer and Python 2.5 -to 2.7. If you are using Python 3.x, you will need PyGreSQL 5.0 or newer. +.. include:: about.txt \ No newline at end of file diff --git a/docs/about.txt b/docs/about.txt new file mode 100644 index 00000000..03576b07 --- /dev/null +++ b/docs/about.txt @@ -0,0 +1,40 @@ +**PyGreSQL** is an *open-source* `Python `_ module +that interfaces to a `PostgreSQL `_ database. +It embeds the PostgreSQL query library to allow easy use of the powerful +PostgreSQL features from a Python script. + + | This software is copyright © 1995, Pascal Andre. + | Further modifications are copyright © 1997-2008 by D'Arcy J.M. Cain. + | Further modifications are copyright © 2009-2016 by the PyGreSQL team. + | For licensing details, see the full :doc:`copyright`. + +**PostgreSQL** is a highly scalable, SQL compliant, open source +object-relational database management system. With more than 20 years +of development history, it is quickly becoming the de facto database +for enterprise level open source solutions. +Best of all, PostgreSQL's source code is available under the most liberal +open source license: the BSD license. + +**Python** Python is an interpreted, interactive, object-oriented +programming language. It is often compared to Tcl, Perl, Scheme or Java. +Python combines remarkable power with very clear syntax. It has modules, +classes, exceptions, very high level dynamic data types, and dynamic typing. +There are interfaces to many system calls and libraries, as well as to +various windowing systems (X11, Motif, Tk, Mac, MFC). New built-in modules +are easily written in C or C++. Python is also usable as an extension +language for applications that need a programmable interface. +The Python implementation is copyrighted but freely usable and distributable, +even for commercial use. + +**PyGreSQL** is a Python module that interfaces to a PostgreSQL database. +It embeds the PostgreSQL query library to allow easy use of the powerful +PostgreSQL features from a Python script. + +PyGreSQL is developed and tested on a NetBSD system, but it also runs on +most other platforms where PostgreSQL and Python is running. It is based +on the PyGres95 code written by Pascal Andre (andre@chimay.via.ecp.fr). +D'Arcy (darcy@druid.net) renamed it to PyGreSQL starting with +version 2.0 and serves as the "BDFL" of PyGreSQL. + +The current version PyGreSQL 4.2 needs PostgreSQL 8.3 or newer and Python 2.5 +to 2.7. If you are using Python 3.x, you will need PyGreSQL 5.0 or newer. diff --git a/docs/conf.py b/docs/conf.py index 6b15bef9..4989dc5a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,9 +15,13 @@ import os import shlex -# import Cloud theme -# this will also automatically add the theme directory -import cloud_sptheme +# Import Cloud theme (this will also automatically add the theme directory). +# Note: We add a navigation bar to the cloud them using a custom layout. +try: + import cloud_sptheme + use_cloud_theme = True +except ImportError: + use_cloud_theme = False # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -34,7 +38,7 @@ extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ['_templates'] if use_cloud_theme else [] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: @@ -45,7 +49,7 @@ #source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = 'index' if use_cloud_theme else 'toc' # General information about the project. project = u'PyGreSQL' @@ -79,11 +83,17 @@ # List of pages which are included in other pages and therefore should # not appear in the toctree. -exclude_patterns += ['about.rst', +exclude_patterns += [ 'download/download.rst', 'download/files.rst', 'community/mailinglist.rst', 'community/source.rst', 'community/bugtracker.rst', 'community/support.rst', 'community/homes.rst'] +if use_cloud_theme: + # We use a naviagtion bar instead of the table of contents + # and we include the about page on the index page. + exclude_patterns += ['toc.rst', 'about.rst'] +else: + exclude_patterns += ['index.rst'] # The reST default role (used for this markup: `text`) for all documents. #default_role = None @@ -116,15 +126,18 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'cloud' +html_theme = 'cloud' if use_cloud_theme else 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -html_theme_options = { - 'roottarget': 'contents/index', - 'defaultcollapsed': True, - 'shaded_decor': True} +if use_cloud_theme: + html_theme_options = { + 'roottarget': 'contents/index', + 'defaultcollapsed': True, + 'shaded_decor': True} +else: + html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['_themes'] diff --git a/docs/download/download.rst b/docs/download/download.rst index d8c644c9..6ccacf1e 100644 --- a/docs/download/download.rst +++ b/docs/download/download.rst @@ -1,5 +1,5 @@ -Download PyGreSQL here: ------------------------ +Current PyGreSQL versions +------------------------- You can find PyGreSQL on the **Python Package Index** at * http://pypi.python.org/pypi/PyGreSQL/ @@ -18,3 +18,9 @@ A **Win32 package** for various Python versions is available at * http://pygresql.org/files/PyGreSQL-4.2.win32-py2.5.exe * http://pygresql.org/files/PyGreSQL-4.2.win32-py2.6.exe * http://pygresql.org/files/PyGreSQL-4.2.win32-py2.7.exe + +Older PyGreSQL versions +----------------------- + +You can look for older PyGreSQL versions at + * http://pygresql.org/files/ diff --git a/docs/index.rst b/docs/index.rst index 73686160..9b7883f4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,4 +12,4 @@ Welcome to PyGreSQL contents/index community/index -.. include:: about.rst +.. include:: about.txt diff --git a/docs/toc.rst b/docs/toc.rst new file mode 100644 index 00000000..f71cb9e0 --- /dev/null +++ b/docs/toc.rst @@ -0,0 +1,14 @@ +.. PyGreSQL documentation master file + +Welcome to PyGreSQL +=================== + +.. toctree:: + :maxdepth: 2 + + about + copyright + announce + download/index + contents/index + community/index From 74ebec90f338adf3bcd25d61c50e52116a7596f6 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 6 Feb 2016 20:23:33 +0000 Subject: [PATCH 123/144] Do not use our custom layout with RTD. This does not work since RTD overrides layout.html. Also, we only want the real homepage to have that special look. --- docs/conf.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 4989dc5a..3d9a25f6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,11 +17,15 @@ # Import Cloud theme (this will also automatically add the theme directory). # Note: We add a navigation bar to the cloud them using a custom layout. -try: - import cloud_sptheme - use_cloud_theme = True -except ImportError: - use_cloud_theme = False +if os.environ.get('READTHEDOCS', None) == 'True': + # We cannot use our custom layout her, since RTD overrides layout.html. + use_clouse_theme = False +else: + try: + import cloud_sptheme + use_cloud_theme = True + except ImportError: + use_cloud_theme = False # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the From 7c9f3c1f9e478d59101c17338ae4a4d6931479f2 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 6 Feb 2016 20:34:06 +0000 Subject: [PATCH 124/144] Fix silly typo. --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 3d9a25f6..6a9b8db5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,8 +18,8 @@ # Import Cloud theme (this will also automatically add the theme directory). # Note: We add a navigation bar to the cloud them using a custom layout. if os.environ.get('READTHEDOCS', None) == 'True': - # We cannot use our custom layout her, since RTD overrides layout.html. - use_clouse_theme = False + # We cannot use our custom layout here, since RTD overrides layout.html. + use_cloud_theme = False else: try: import cloud_sptheme From 14a948ba9c063dfbb56c5c389f88e872aadf4731 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 6 Feb 2016 21:31:33 +0000 Subject: [PATCH 125/144] Always use index.rst as master page Implement the case distinction on the home page with ifconfig. --- docs/conf.py | 6 ------ docs/index.rst | 30 ++++++++++++++++++++++-------- docs/toc.rst | 14 -------------- 3 files changed, 22 insertions(+), 28 deletions(-) delete mode 100644 docs/toc.rst diff --git a/docs/conf.py b/docs/conf.py index 6a9b8db5..69f20a10 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -92,12 +92,6 @@ 'community/mailinglist.rst', 'community/source.rst', 'community/bugtracker.rst', 'community/support.rst', 'community/homes.rst'] -if use_cloud_theme: - # We use a naviagtion bar instead of the table of contents - # and we include the about page on the index page. - exclude_patterns += ['toc.rst', 'about.rst'] -else: - exclude_patterns += ['index.rst'] # The reST default role (used for this markup: `text`) for all documents. #default_role = None diff --git a/docs/index.rst b/docs/index.rst index 9b7883f4..0f57a085 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,13 +3,27 @@ Welcome to PyGreSQL =================== -.. toctree:: - :hidden: +.. ifconfig:: html_theme != 'cloud' - copyright - announce - download/index - contents/index - community/index + .. toctree:: + :maxdepth: 2 -.. include:: about.txt + about + copyright + announce + download/index + contents/index + community/index + +.. ifconfig:: html_theme == 'cloud' + + .. toctree:: + :hidden: + + copyright + announce + download/index + contents/index + community/index + + .. include:: about.txt diff --git a/docs/toc.rst b/docs/toc.rst deleted file mode 100644 index f71cb9e0..00000000 --- a/docs/toc.rst +++ /dev/null @@ -1,14 +0,0 @@ -.. PyGreSQL documentation master file - -Welcome to PyGreSQL -=================== - -.. toctree:: - :maxdepth: 2 - - about - copyright - announce - download/index - contents/index - community/index From 5e7bf13023fdb03a23a53a6edf88e3d00c6741dd Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 6 Feb 2016 21:47:07 +0000 Subject: [PATCH 126/144] Avoid duplication in the title of the docs The actual documentation comes only at the second level in the breadcrumb. --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 69f20a10..6339ffda 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -142,7 +142,7 @@ # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +html_title = None if use_cloud_theme else 'PyGreSQL %s' % version # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None From 92bf32078f2672300e82e33e9be4edc203bc396a Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 6 Feb 2016 22:14:05 +0000 Subject: [PATCH 127/144] Use different file instead of ifconfig Unfortunately, Sphinx did not regard ifconfig when parsing the toc, so it could not be used. Instead, we simply copy a different file. --- docs/conf.py | 7 ++++++- docs/index.rst | 32 +++++++++----------------------- docs/start.txt | 15 +++++++++++++++ docs/toc.txt | 14 ++++++++++++++ 4 files changed, 44 insertions(+), 24 deletions(-) create mode 100644 docs/start.txt create mode 100644 docs/toc.txt diff --git a/docs/conf.py b/docs/conf.py index 6339ffda..6a6731c4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,6 +14,7 @@ import sys import os import shlex +import shutil # Import Cloud theme (this will also automatically add the theme directory). # Note: We add a navigation bar to the cloud them using a custom layout. @@ -27,6 +28,8 @@ except ImportError: use_cloud_theme = False +shutil.copyfile('start.txt' if use_cloud_theme else 'toc.txt', 'index.rst') + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -142,7 +145,9 @@ # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -html_title = None if use_cloud_theme else 'PyGreSQL %s' % version +html_title = 'PyGreSQL %s' % version +if use_cloud_theme: + html_title += ' documentation' # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None diff --git a/docs/index.rst b/docs/index.rst index 0f57a085..5166896a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,29 +1,15 @@ -.. PyGreSQL documentation master file +.. PyGreSQL index page without toc (for use with cloud theme) Welcome to PyGreSQL =================== -.. ifconfig:: html_theme != 'cloud' +.. toctree:: + :hidden: - .. toctree:: - :maxdepth: 2 + copyright + announce + download/index + contents/index + community/index - about - copyright - announce - download/index - contents/index - community/index - -.. ifconfig:: html_theme == 'cloud' - - .. toctree:: - :hidden: - - copyright - announce - download/index - contents/index - community/index - - .. include:: about.txt +.. include:: about.txt \ No newline at end of file diff --git a/docs/start.txt b/docs/start.txt new file mode 100644 index 00000000..5166896a --- /dev/null +++ b/docs/start.txt @@ -0,0 +1,15 @@ +.. PyGreSQL index page without toc (for use with cloud theme) + +Welcome to PyGreSQL +=================== + +.. toctree:: + :hidden: + + copyright + announce + download/index + contents/index + community/index + +.. include:: about.txt \ No newline at end of file diff --git a/docs/toc.txt b/docs/toc.txt new file mode 100644 index 00000000..441021b4 --- /dev/null +++ b/docs/toc.txt @@ -0,0 +1,14 @@ +.. PyGreSQL index page with toc (for use without cloud theme) + +Welcome to PyGreSQL +=================== + +.. toctree:: + :maxdepth: 2 + + about + copyright + announce + download/index + contents/index + community/index \ No newline at end of file From 36d7241a5c7c3112a44c497c93df7a8b237cdccb Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 7 Feb 2016 12:29:50 +0000 Subject: [PATCH 128/144] Minor fixes in the docs configuration --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 6a6731c4..babb1b21 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -56,11 +56,11 @@ #source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' if use_cloud_theme else 'toc' +master_doc = 'index' # General information about the project. project = u'PyGreSQL' -author = u'The PyGreSQL Team' +author = u'The PyGreSQL team' copyright = u'2016, ' + author # The version info for the project you're documenting, acts as replacement for From 3c1c3ef3ed158797e156f7a0e85be887afccd3d3 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 7 Feb 2016 14:29:46 +0000 Subject: [PATCH 129/144] Add Travis CI configuration --- travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 travis.yml diff --git a/travis.yml b/travis.yml new file mode 100644 index 00000000..6b3d8c5a --- /dev/null +++ b/travis.yml @@ -0,0 +1,9 @@ +language: python +# unfortunately, Travis cannot be used to test with Python 2.4 and 2.5 +python: + - "2.6" + - "2.7" +install: + - pip install . + - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi +script: python setup.py test \ No newline at end of file From 42f4d2583a27e3cf5c5559b275be859b806db0c1 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 7 Feb 2016 14:39:16 +0000 Subject: [PATCH 130/144] Fix name of Travis CI configuration file --- travis.yml => .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename travis.yml => .travis.yml (88%) diff --git a/travis.yml b/.travis.yml similarity index 88% rename from travis.yml rename to .travis.yml index 6b3d8c5a..afb28571 100644 --- a/travis.yml +++ b/.travis.yml @@ -6,4 +6,4 @@ python: install: - pip install . - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi -script: python setup.py test \ No newline at end of file +script: python setup.py test From 749091425496c65623e5f4733b9b236ade42d1cf Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 7 Feb 2016 14:49:29 +0000 Subject: [PATCH 131/144] Add database configuration for Travis --- .travis.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.travis.yml b/.travis.yml index afb28571..11062d27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,3 +7,9 @@ install: - pip install . - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi script: python setup.py test +addons: + postgresql: "9.1" +services: + - postgresql +before_script: + - psql -U postgres -c 'create database unittest' \ No newline at end of file From 04123de40c6cc27c1993bf82feb7c5148d2981ed Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sun, 7 Feb 2016 15:05:09 +0000 Subject: [PATCH 132/144] Add comment with link to Travis configuration docs --- .travis.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 11062d27..51d30a6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,24 @@ +# Travis CI configuration +# see https://docs.travis-ci.com/user/languages/python + language: python + # unfortunately, Travis cannot be used to test with Python 2.4 and 2.5 python: - "2.6" - "2.7" + install: - pip install . - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi + script: python setup.py test + addons: postgresql: "9.1" + services: - postgresql + before_script: - - psql -U postgres -c 'create database unittest' \ No newline at end of file + - psql -U postgres -c 'create database unittest' From 2069931f7a479eeb6c739ba1d14bd94170d7f8cc Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Thu, 18 Feb 2016 16:55:58 +0000 Subject: [PATCH 133/144] Set for 4.2.1 release. --- mktar | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mktar b/mktar index 57baf459..b07370d3 100755 --- a/mktar +++ b/mktar @@ -1,6 +1,6 @@ #! /bin/sh -VERSION=4.2 +VERSION=4.2.1 DISTDIR=/u/pyg/files # some safety tests From bc6cf25826eb6f64ddba95e9469d5707df25a7d9 Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Thu, 18 Feb 2016 18:20:45 +0000 Subject: [PATCH 134/144] Fix tests so that script runs under NetBSD. --- mktar | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mktar b/mktar index b07370d3..82bdfe8c 100755 --- a/mktar +++ b/mktar @@ -16,7 +16,7 @@ then fi FILES="*.c *.h *.py *.cfg *.rst *.txt" NUMFILES=`ls $FILES | wc -l` -if [ "$NUMFILES" != "9" ] +if [ $NUMFILES != 9 ] then echo "Hmmm. The number of top-level files seems to be wrong:" ls $FILES @@ -26,7 +26,7 @@ then fi FILES="mktar mkdocs docs tests pg.py pgdb.py pgmodule.c setup.cfg" PERMS=`stat --printf="%a" $FILES` -if [ $? -ne 0 -o "$PERMS" != '755755755755644644644644' ] +if [ $? -eq 0 -a "$PERMS" != '755755755755644644644644' ] then echo "Hmmm. File permissions are not set properly." echo "Use a filesystem with permissions and do a clean checkout first." From e5056ef0dd27a9014f8a2338b7371b0a99d99543 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 19 Feb 2016 14:13:58 +0000 Subject: [PATCH 135/144] Add release notes for 4.2.1 in the changelog --- docs/contents/changelog.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/contents/changelog.rst b/docs/contents/changelog.rst index c1bfd8c8..6804c682 100644 --- a/docs/contents/changelog.rst +++ b/docs/contents/changelog.rst @@ -1,6 +1,11 @@ ChangeLog ========= +Version 4.2.1 (2016-02-18) +-------------------------- +- Fixed a small bug when setting the notice receiver. +- Some more minor fixes and re-packaging with proper permissions. + Version 4.2 (2016-01-21) ------------------------ - The supported Python versions are 2.4 to 2.7. From d051acbf94a3636606df318e198f5f8d89838242 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 18 Mar 2016 12:22:21 +0000 Subject: [PATCH 136/144] Add system parameter to get_relations() Also fix a regression in the 4.x branch when using temporary tables, related to filtering system tables (as discussed on the mailing list). --- docs/contents/changelog.rst | 7 ++++++ docs/contents/pg/db_wrapper.rst | 22 +++++++++++------ pg.py | 43 ++++++++++++++++++++++----------- tests/test_classic_dbwrapper.py | 43 +++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 22 deletions(-) diff --git a/docs/contents/changelog.rst b/docs/contents/changelog.rst index 6804c682..134c766b 100644 --- a/docs/contents/changelog.rst +++ b/docs/contents/changelog.rst @@ -1,6 +1,13 @@ ChangeLog ========= +Version 4.2.2 +------------- +- The get_relations() and get_tables() methods now also return system views + and tables if you set the optional "system" parameter to True. +- Fixed a regression when using temporary tables with DB wrapper methods + (thanks to Patrick TJ McPhee for reporting). + Version 4.2.1 (2016-02-18) -------------------------- - Fixed a small bug when setting the notice receiver. diff --git a/docs/contents/pg/db_wrapper.rst b/docs/contents/pg/db_wrapper.rst index f334ac58..677ca232 100644 --- a/docs/contents/pg/db_wrapper.rst +++ b/docs/contents/pg/db_wrapper.rst @@ -86,31 +86,37 @@ convenience. get_relations -- get list of relations in connected database ------------------------------------------------------------ -.. method:: DB.get_relations(kinds) +.. method:: DB.get_relations([kinds], [system]) Get the list of relations in connected database :param str kinds: a string or sequence of type letters + :param bool system: whether system relations should be returned :returns: all relations of the given kinds in the database :rtype: list -The type letters are ``r`` = ordinary table, ``i`` = index, ``S`` = sequence, -``v`` = view, ``c`` = composite type, ``s`` = special, ``t`` = TOAST table. -If `kinds` is None or an empty string, all relations are returned (this is -also the default). Although you can do this with a simple select, it is -added here for convenience. +This method returns the list of relations in the connected database. Although +you can do this with a simple select, it is added here for convenience. You +can select which kinds of relations you are interested in by passing type +letters in the `kinds` parameter. The type letters are ``r`` = ordinary table, +``i`` = index, ``S`` = sequence, ``v`` = view, ``c`` = composite type, +``s`` = special, ``t`` = TOAST table. If `kinds` is None or an empty string, +all relations are returned (this is also the default). If `system` is set to +`True`, then system tables and views (temporary tables, toast tables, catalog +vies and tables) will be returned as well, otherwise they will be ignored. get_tables -- get list of tables in connected database ------------------------------------------------------ -.. method:: DB.get_tables() +.. method:: DB.get_tables([system]) Get the list of tables in connected database + :param bool system: whether system tables should be returned :returns: all tables in connected database :rtype: list -This is a shortcut for ``get_relations('r')`` that has been added for +This is a shortcut for ``get_relations('r', system)`` that has been added for convenience. get_attnames -- get the attribute names of a table diff --git a/pg.py b/pg.py index 80f5bd6d..fa968fb8 100644 --- a/pg.py +++ b/pg.py @@ -735,12 +735,11 @@ def pkey(self, cl, newpkey=None): q = ("SELECT s.nspname, r.relname, a.attname" " FROM pg_class r" " JOIN pg_namespace s ON s.oid = r.relnamespace" - " AND s.nspname NOT SIMILAR" - " TO 'pg/_%|information/_schema' ESCAPE '/'" " JOIN pg_attribute a ON a.attrelid = r.oid" " AND NOT a.attisdropped" " JOIN pg_index i ON i.indrelid = r.oid" - " AND i.indisprimary AND a.attnum " + any_indkey) + " AND i.indisprimary AND a.attnum %s" + " AND r.relkind IN ('r', 'v')" % any_indkey) for r in self.db.query(q).getresult(): cl, pkey = _join_parts(r[:2]), r[2] self._pkeys.setdefault(cl, []).append(pkey) @@ -757,27 +756,35 @@ def get_databases(self): return [s[0] for s in self.db.query('SELECT datname FROM pg_database').getresult()] - def get_relations(self, kinds=None): + def get_relations(self, kinds=None, system=False): """Get list of relations in connected database of specified kinds. If kinds is None or empty, all kinds of relations are returned. Otherwise kinds can be a string or sequence of type letters specifying which kind of relations you want to list. + Set the system flag if you want to get the system relations as well. """ - where = kinds and " AND r.relkind IN (%s)" % ','.join( - ["'%s'" % k for k in kinds]) or '' + where = [] + if kinds: + where.append("r.relkind IN (%s)" % + ','.join(["'%s'" % k for k in kinds])) + if not system: + where.append("s.nspname NOT SIMILAR" + " TO 'pg/_%|information/_schema' ESCAPE '/'") + where = where and " WHERE %s" % ' AND '.join(where) or '' q = ("SELECT s.nspname, r.relname" " FROM pg_class r" - " JOIN pg_namespace s ON s.oid = r.relnamespace" - " WHERE s.nspname NOT SIMILAR" - " TO 'pg/_%%|information/_schema' ESCAPE '/' %s" + " JOIN pg_namespace s ON s.oid = r.relnamespace%s" " ORDER BY 1, 2") % where return [_join_parts(r) for r in self.db.query(q).getresult()] - def get_tables(self): - """Return list of tables in connected database.""" - return self.get_relations('r') + def get_tables(self, system=False): + """Return list of tables in connected database. + + Set the system flag if you want to get the system tables as well. + """ + return self.get_relations('r', system) def get_attnames(self, cl, newattnames=None): """Given the name of a table, digs out the set of attribute names. @@ -801,8 +808,6 @@ def get_attnames(self, cl, newattnames=None): # May as well cache them: if qcl in self._attnames: return self._attnames[qcl] - if qcl not in self.get_relations('rv'): - raise _prg_error('Class %s does not exist' % qcl) q = ("SELECT a.attname, t.typname%s" " FROM pg_class r" @@ -810,10 +815,20 @@ def get_attnames(self, cl, newattnames=None): " JOIN pg_attribute a ON a.attrelid = r.oid" " JOIN pg_type t ON t.oid = a.atttypid" " WHERE s.nspname = $1 AND r.relname = $2" + " AND r.relkind IN ('r', 'v')" " AND (a.attnum > 0 OR a.attname = 'oid')" " AND NOT a.attisdropped") % ( self._regtypes and '::regtype' or '',) q = self.db.query(q, cl).getresult() + if not q: + r = ("SELECT r.relnamespace" + " FROM pg_class r" + " JOIN pg_namespace s ON s.oid = r.relnamespace" + " WHERE s.nspname =$1 AND r.relname = $2" + " AND r.relkind IN ('r', 'v') LIMIT 1") + r = self.db.query(r, cl).getresult() + if not r: + raise _prg_error('Class %s does not exist' % qcl) if self._regtypes: t = dict(q) diff --git a/tests/test_classic_dbwrapper.py b/tests/test_classic_dbwrapper.py index 1aa3a28c..47d0f4c5 100755 --- a/tests/test_classic_dbwrapper.py +++ b/tests/test_classic_dbwrapper.py @@ -765,6 +765,18 @@ def testGetTables(self): result2 = get_tables() self.assertEqual(result2, result1) + def testGetSystemTables(self): + get_tables = self.db.get_tables + result = get_tables() + self.assertNotIn('pg_catalog.pg_class', result) + self.assertNotIn('information_schema.tables', result) + result = get_tables(system=False) + self.assertNotIn('pg_catalog.pg_class', result) + self.assertNotIn('information_schema.tables', result) + result = get_tables(system=True) + self.assertIn('pg_catalog.pg_class', result) + self.assertNotIn('information_schema.tables', result) + def testGetRelations(self): get_relations = self.db.get_relations result = get_relations() @@ -812,6 +824,18 @@ def testGetAttnames(self): self.assertEqual(attributes, result) self.db.query('drop table "%s"' % table) + def testGetSystemRelations(self): + get_relations = self.db.get_relations + result = get_relations() + self.assertNotIn('pg_catalog.pg_class', result) + self.assertNotIn('information_schema.tables', result) + result = get_relations(system=False) + self.assertNotIn('pg_catalog.pg_class', result) + self.assertNotIn('information_schema.tables', result) + result = get_relations(system=True) + self.assertIn('pg_catalog.pg_class', result) + self.assertIn('information_schema.tables', result) + def testHasTablePrivilege(self): can = self.db.has_table_privilege self.assertEqual(can('test'), True) @@ -1231,6 +1255,25 @@ def testTruncate(self): query("drop table test_table_2") query("drop table test_table") + def testTempCrud(self): + query = self.db.query + table = 'test_temp_table' + query("drop table if exists %s" % table) + query("create temporary table %s" + " (n int primary key, t varchar)" % table) + self.db.insert(table, dict(n=1, t='one')) + self.db.insert(table, dict(n=2, t='too')) + self.db.insert(table, dict(n=3, t='three')) + r = self.db.get(table, 2) + self.assertEqual(r['t'], 'too') + self.db.update(table, dict(n=2, t='two')) + r = self.db.get(table, 2) + self.assertEqual(r['t'], 'two') + self.db.delete(table, r) + r = query('select n, t from %s order by 1' % table).getresult() + self.assertEqual(r, [(1, 'one'), (3, 'three')]) + query("drop table %s" % table) + def testTruncateRestart(self): truncate = self.db.truncate self.assertRaises(TypeError, truncate, 'test_table', restart='invalid') From 106c93caec6f34a754d5eede442955d726353a86 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 18 Mar 2016 12:59:53 +0000 Subject: [PATCH 137/144] Bump PostgreSQL version in Travis config Seems Travis doesn't support Pg 9.1 any more for testing 4.x, but hopefully it supports Pg 9.5 for testing the trunk. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 51d30a6f..08a87b98 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ install: script: python setup.py test addons: - postgresql: "9.1" + postgresql: "9.2" services: - postgresql From b2df8075a60fea41be2addc831364d952d4c5945 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 18 Mar 2016 13:09:03 +0000 Subject: [PATCH 138/144] Revert PostgreSQL versions in Travis config Seems Travis still doesn't support Pg 9.5, and 9.1 should still work. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 08a87b98..51d30a6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ install: script: python setup.py test addons: - postgresql: "9.2" + postgresql: "9.1" services: - postgresql From 6c4acec6322ebd943e0e17c73660a748d5b83de9 Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Fri, 18 Mar 2016 14:20:48 +0000 Subject: [PATCH 139/144] Prepare to release 4.2.2. --- mktar | 2 +- setup.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mktar b/mktar index 82bdfe8c..732cd194 100755 --- a/mktar +++ b/mktar @@ -1,6 +1,6 @@ #! /bin/sh -VERSION=4.2.1 +VERSION=4.2.2 DISTDIR=/u/pyg/files # some safety tests diff --git a/setup.py b/setup.py index ea166504..8b4e6781 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #! /usr/bin/python # $Id$ -"""Setup script for PyGreSQL version 4.2.1 +"""Setup script for PyGreSQL version 4.2.2 PyGreSQL is an open-source Python module that interfaces to a PostgreSQL database. It embeds the PostgreSQL query library to allow @@ -32,7 +32,7 @@ """ -version = '4.2.1' +version = '4.2.2' import sys From 27dd17fe020be22563ee92e4ad5fc0f9b84f5828 Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Fri, 18 Mar 2016 14:29:30 +0000 Subject: [PATCH 140/144] Add release date. --- docs/contents/changelog.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contents/changelog.rst b/docs/contents/changelog.rst index 134c766b..c2aa6f3d 100644 --- a/docs/contents/changelog.rst +++ b/docs/contents/changelog.rst @@ -1,8 +1,8 @@ ChangeLog ========= -Version 4.2.2 -------------- +Version 4.2.2 (2016-03-18) +-------------------------- - The get_relations() and get_tables() methods now also return system views and tables if you set the optional "system" parameter to True. - Fixed a regression when using temporary tables with DB wrapper methods From 2c580248c74eed492010c19ea9fe7c6eb32f6acc Mon Sep 17 00:00:00 2001 From: "D'Arcy J.M. Cain" Date: Fri, 18 Mar 2016 15:01:06 +0000 Subject: [PATCH 141/144] Move files to a different folder on Vex.Net server. --- mktar | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mktar b/mktar index 732cd194..6698fe71 100755 --- a/mktar +++ b/mktar @@ -1,7 +1,7 @@ #! /bin/sh VERSION=4.2.2 -DISTDIR=/u/pyg/files +DISTDIR=/u/WEB/pyg/files # some safety tests if [ ! -d $DISTDIR ] From 3b6f5f55fd4532e32d403f712c20ef5da710fbf0 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 18 Mar 2016 18:03:01 +0000 Subject: [PATCH 142/144] Update download section in docs --- docs/download/download.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/download/download.rst b/docs/download/download.rst index 6ccacf1e..0219d13c 100644 --- a/docs/download/download.rst +++ b/docs/download/download.rst @@ -14,10 +14,11 @@ A **NetBSD package** is available in their pkgsrc collection * ftp://ftp.netbsd.org/pub/NetBSD/packages/pkgsrc/databases/py-postgresql/README.html A **FreeBSD package** is available in their ports collection * http://www.freebsd.org/cgi/cvsweb.cgi/ports/databases/py-PyGreSQL/ -A **Win32 package** for various Python versions is available at - * http://pygresql.org/files/PyGreSQL-4.2.win32-py2.5.exe - * http://pygresql.org/files/PyGreSQL-4.2.win32-py2.6.exe - * http://pygresql.org/files/PyGreSQL-4.2.win32-py2.7.exe +An **openSUSE package** is available through their build service at + * https://software.opensuse.org/package/PyGreSQL?search_term=pygresql +A **Win32 installer** for Python 2.6 and 2.7 is available at + * http://pygresql.org/files/PyGreSQL-4.2.2.win-amd64-py2.6.exe + * http://pygresql.org/files/PyGreSQL-4.2.2.win-amd64-py2.7.exe Older PyGreSQL versions ----------------------- From dd1d8eaffe36e9685f905e14f0ead136e2133658 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Sat, 16 Apr 2016 07:05:28 +0000 Subject: [PATCH 143/144] Let setup understand Postgres beta versions Postgres versions such as "PostgreSQL 9.5beta1" were not handled properly, because the "5beta1" part of the version was not converted to an int. --- setup.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 8b4e6781..66c59550 100755 --- a/setup.py +++ b/setup.py @@ -44,6 +44,7 @@ import os import platform +import re import warnings try: from setuptools import setup @@ -77,12 +78,10 @@ def pg_config(s): def pg_version(): """Return the PostgreSQL version as a tuple of integers.""" - parts = [] - for part in pg_config('version').split()[-1].split('.'): - if part.isdigit(): - part = int(part) - parts.append(part) - return tuple(parts or [8, 3]) + match = re.search(r'(\d+)\.(\d+)', pg_config('version')) + if match: + return tuple(map(int, match.groups())) + return (8, 3) pg_version = pg_version() From 119ee71691206062a449436f5a2ca4d232836dd2 Mon Sep 17 00:00:00 2001 From: Christoph Zwerschke Date: Fri, 24 Nov 2017 16:16:44 +0000 Subject: [PATCH 144/144] Fix the license entry in the setup file PyGreSQL is licensed under the PostgreSQL license (see LICENSE.txt) and not the Python Software Foundation license. --- LICENSE.txt | 3 +++ setup.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 268e101c..22c1be9a 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -9,6 +9,9 @@ Further modifications copyright (c) 1997-2008 by D'Arcy J.M. Cain Further modifications copyright (c) 2009-2016 by the PyGreSQL team. +PyGreSQL is released under the PostgreSQL License, a liberal Open Source +license, similar to the BSD or MIT licenses: + Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this diff --git a/setup.py b/setup.py index 66c59550..6b182fcc 100755 --- a/setup.py +++ b/setup.py @@ -178,7 +178,7 @@ def finalize_options(self): url="http://www.pygresql.org", download_url="ftp://ftp.pygresql.org/pub/distrib/", platforms=["any"], - license="Python", + license="PostgreSQL", py_modules=py_modules, ext_modules=[Extension('_pg', c_sources, include_dirs=include_dirs, library_dirs=library_dirs, @@ -190,7 +190,7 @@ def finalize_options(self): classifiers=[ "Development Status :: 6 - Mature", "Intended Audience :: Developers", - "License :: OSI Approved :: Python Software Foundation License", + "License :: OSI Approved :: The PostgreSQL License", "Operating System :: OS Independent", "Programming Language :: C", 'Programming Language :: Python',