From 0a04c8892d5d704f471f370e6b9a253f6add7c1c Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 12 Oct 2018 03:25:35 +0100 Subject: [PATCH] Added several ConnectionInfo attributes --- doc/src/extensions.rst | 6 ++ psycopg/conninfo_type.c | 146 ++++++++++++++++++++++++++++++++++++--- psycopg/python.h | 1 + tests/test_connection.py | 36 ++++++++++ 4 files changed, 179 insertions(+), 10 deletions(-) diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index 520858c5..271ca930 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -159,7 +159,13 @@ introspection etc. .. versionadded:: 2.8 + .. autoattribute:: dbname + .. autoattribute:: user + .. autoattribute:: password .. autoattribute:: host + .. autoattribute:: port + .. autoattribute:: options + .. autoattribute:: status diff --git a/psycopg/conninfo_type.c b/psycopg/conninfo_type.c index b10a7f9c..93440ac0 100644 --- a/psycopg/conninfo_type.c +++ b/psycopg/conninfo_type.c @@ -29,6 +29,79 @@ #include "psycopg/conninfo.h" +static const char connInfoType_doc[] = +"Details about the native PostgreSQL database connection.\n" +"\n" +"This class exposes several `informative functions`__ about the status\n" +"of the libpq connection.\n" +"\n" +"Objects of this class are exposed as the `connection.info` attribute.\n" +"\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"; + + +static const char dbname_doc[] = +"The database name of the connection.\n" +"\n" +"Wrapper for the `PQdb()`__ function.\n" +"\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQDB"; + +static PyObject * +dbname_get(connInfoObject *self) +{ + const char *val; + + val = PQdb(self->conn->pgconn); + if (!val) { + Py_RETURN_NONE; + } + return conn_text_from_chars(self->conn, val); +} + + +static const char user_doc[] = +"The user name of the connection.\n" +"\n" +"Wrapper for the `PQuser()`__ function.\n" +"\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQUSER"; + +static PyObject * +user_get(connInfoObject *self) +{ + const char *val; + + val = PQuser(self->conn->pgconn); + if (!val) { + Py_RETURN_NONE; + } + return conn_text_from_chars(self->conn, val); +} + + +static const char password_doc[] = +"The password of the connection.\n" +"\n" +".. seealso:: libpq docs for `PQpass()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQPASS"; + +static PyObject * +password_get(connInfoObject *self) +{ + const char *val; + + val = PQpass(self->conn->pgconn); + if (!val) { + Py_RETURN_NONE; + } + return conn_text_from_chars(self->conn, val); +} + + static const char host_doc[] = "The server host name of the connection.\n" "\n" @@ -53,8 +126,71 @@ host_get(connInfoObject *self) } +static const char port_doc[] = +"The port of the connection.\n" +"\n" +".. seealso:: libpq docs for `PQport()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQPORT"; + +static PyObject * +port_get(connInfoObject *self) +{ + const char *val; + + val = PQport(self->conn->pgconn); + if (!val || !val[0]) { + Py_RETURN_NONE; + } + return PyInt_FromString((char *)val, NULL, 10); +} + + +static const char options_doc[] = +"The command-line options passed in the the connection request.\n" +"\n" +".. seealso:: libpq docs for `PQoptions()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQOPTIONS"; + +static PyObject * +options_get(connInfoObject *self) +{ + const char *val; + + val = PQoptions(self->conn->pgconn); + if (!val) { + Py_RETURN_NONE; + } + return conn_text_from_chars(self->conn, val); +} + + +static const char status_doc[] = +"Return the status of the connection.\n" +"\n" +".. seealso:: libpq docs for `PQstatus()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQSTATUS"; + +static PyObject * +status_get(connInfoObject *self) +{ + ConnStatusType val; + + val = PQstatus(self->conn->pgconn); + return PyInt_FromLong((long)val); +} + + static struct PyGetSetDef connInfoObject_getsets[] = { + { "dbname", (getter)dbname_get, NULL, (char *)dbname_doc }, + { "user", (getter)user_get, NULL, (char *)user_doc }, + { "password", (getter)password_get, NULL, (char *)password_doc }, { "host", (getter)host_get, NULL, (char *)host_doc }, + { "port", (getter)port_get, NULL, (char *)port_doc }, + { "options", (getter)options_get, NULL, (char *)options_doc }, + { "status", (getter)status_get, NULL, (char *)status_doc }, {NULL} }; @@ -95,16 +231,6 @@ conninfo_dealloc(connInfoObject* self) /* object type */ -static const char connInfoType_doc[] = -"Details about the native PostgreSQL database connection.\n" -"\n" -"This class exposes several `informative functions`__ about the status\n" -"of the libpq connection.\n" -"\n" -"Objects of this class are exposed as the `connection.info` attribute.\n" -"\n" -".. __: https://www.postgresql.org/docs/current/static/libpq-status.html"; - PyTypeObject connInfoType = { PyVarObject_HEAD_INIT(NULL, 0) "psycopg2.extensions.ConnectionInfo", diff --git a/psycopg/python.h b/psycopg/python.h index fa894bf3..1276ecfd 100644 --- a/psycopg/python.h +++ b/psycopg/python.h @@ -78,6 +78,7 @@ typedef unsigned long Py_uhash_t; #define PyInt_Check PyLong_Check #define PyInt_AsLong PyLong_AsLong #define PyInt_FromLong PyLong_FromLong +#define PyInt_FromString PyLong_FromString #define PyInt_FromSsize_t PyLong_FromSsize_t #define PyExc_StandardError PyExc_Exception #define PyString_FromFormat PyUnicode_FromFormat diff --git a/tests/test_connection.py b/tests/test_connection.py index 7ec35947..728f7ec1 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1683,14 +1683,50 @@ while True: class TestConnectionInfo(ConnectingTestCase): + def setUp(self): + ConnectingTestCase.setUp(self) + + class BrokenConn(psycopg2.extensions.connection): + def __init__(self, *args, **kwargs): + # don't call superclass + pass + + # A "broken" connection + self.bconn = self.connect(connection_factory=BrokenConn) + + def test_dbname(self): + self.assert_(isinstance(self.conn.info.dbname, str)) + self.assert_(self.bconn.info.dbname is None) + + def test_user(self): + self.assert_(isinstance(self.conn.info.user, str)) + self.assert_(self.bconn.info.user is None) + + def test_password(self): + self.assert_(isinstance(self.conn.info.password, str)) + self.assert_(self.bconn.info.password is None) + def test_host(self): expected = dbhost if dbhost else "/" self.assertIn(expected, self.conn.info.host) + self.assert_(self.bconn.info.host is None) def test_host_readonly(self): with self.assertRaises(AttributeError): self.conn.info.host = 'override' + def test_port(self): + self.assert_(isinstance(self.conn.info.port, int)) + self.assert_(self.bconn.info.port is None) + + def test_options(self): + self.assert_(isinstance(self.conn.info.options, str)) + self.assert_(self.bconn.info.options is None) + + def test_status(self): + self.assertEqual(self.conn.info.status, 0) + self.assertEqual(self.bconn.info.status, 1) + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__)