From 0e2b516a3cc454fc762a17c847979bed799a7b1c Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Thu, 11 Oct 2018 22:42:52 +0100 Subject: [PATCH 01/12] Moving host attribute to a connection.info object --- psycopg/connection_type.c | 28 ++++---- psycopg/conninfo.h | 40 ++++++++++++ psycopg/conninfo_type.c | 131 ++++++++++++++++++++++++++++++++++++++ psycopg/psycopgmodule.c | 5 ++ setup.py | 4 +- tests/test_connection.py | 9 +-- 6 files changed, 193 insertions(+), 24 deletions(-) create mode 100644 psycopg/conninfo.h create mode 100644 psycopg/conninfo_type.c diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c index da421c01..db2ab346 100644 --- a/psycopg/connection_type.c +++ b/psycopg/connection_type.c @@ -29,6 +29,7 @@ #include "psycopg/connection.h" #include "psycopg/cursor.h" #include "psycopg/pqpath.h" +#include "psycopg/conninfo.h" #include "psycopg/lobject.h" #include "psycopg/green.h" #include "psycopg/xid.h" @@ -992,25 +993,20 @@ psyco_conn_get_backend_pid(connectionObject *self) return PyInt_FromLong((long)PQbackendPID(self->pgconn)); } -/* get the current host */ -#define psyco_conn_host_get_doc \ -"host -- Get the host name." +/* get info about the connection */ + +#define psyco_conn_info_get_doc \ +"info -- Get connection info." static PyObject * -psyco_conn_host_get(connectionObject *self) +psyco_conn_info_get(connectionObject *self) { - const char *val = NULL; - - EXC_IF_CONN_CLOSED(self); - - val = PQhost(self->pgconn); - if (!val) { - Py_RETURN_NONE; - } - return conn_text_from_chars(self, val); + return PyObject_CallFunctionObjArgs( + (PyObject *)&connInfoType, (PyObject *)self, NULL); } + /* reset the currect connection */ #define psyco_conn_reset_doc \ @@ -1262,9 +1258,9 @@ static struct PyGetSetDef connectionObject_getsets[] = { (getter)psyco_conn_deferrable_get, (setter)psyco_conn_deferrable_set, psyco_conn_deferrable_doc }, - { "host", - (getter)psyco_conn_host_get, NULL, - psyco_conn_host_get_doc }, + { "info", + (getter)psyco_conn_info_get, NULL, + psyco_conn_info_get_doc }, {NULL} }; #undef EXCEPTION_GETTER diff --git a/psycopg/conninfo.h b/psycopg/conninfo.h new file mode 100644 index 00000000..d04c520a --- /dev/null +++ b/psycopg/conninfo.h @@ -0,0 +1,40 @@ +/* connection.h - definition for the psycopg ConnectionInfo type + * + * Copyright (C) 2018 Daniele Varrazzo + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#ifndef PSYCOPG_CONNINFO_H +#define PSYCOPG_CONNINFO_H 1 + +#include "psycopg/connection.h" + +extern HIDDEN PyTypeObject connInfoType; + +typedef struct { + PyObject_HEAD + + connectionObject *conn; + +} connInfoObject; + +#endif /* PSYCOPG_CONNINFO_H */ diff --git a/psycopg/conninfo_type.c b/psycopg/conninfo_type.c new file mode 100644 index 00000000..b0258eeb --- /dev/null +++ b/psycopg/conninfo_type.c @@ -0,0 +1,131 @@ +/* conninfo_type.c - present information about the libpq connection + * + * Copyright (C) 2018 Daniele Varrazzo + * + * This file is part of psycopg. + * + * psycopg2 is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link this program with the OpenSSL library (or with + * modified versions of OpenSSL that use the same license as OpenSSL), + * and distribute linked combinations including the two. + * + * You must obey the GNU Lesser General Public License in all respects for + * all of the code used other than OpenSSL. + * + * psycopg2 is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ + +#define PSYCOPG_MODULE +#include "psycopg/psycopg.h" + +#include "psycopg/conninfo.h" + + +static const char host_doc[] = + "The server host name of the active connection."; + +static PyObject * +host_get(connInfoObject *self) +{ + const char *val; + + val = PQhost(self->conn->pgconn); + if (!val) { + Py_RETURN_NONE; + } + return conn_text_from_chars(self->conn, val); +} + + +static struct PyGetSetDef connInfoObject_getsets[] = { + { "host", (getter)host_get, NULL, (char *)host_doc }, + {NULL} +}; + +/* initialization and finalization methods */ + +static PyObject * +conninfo_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + return type->tp_alloc(type, 0); +} + +static int +conninfo_init(connInfoObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *conn = NULL; + + if (!PyArg_ParseTuple(args, "O", &conn)) + return -1; + + if (!PyObject_TypeCheck(conn, &connectionType)) { + PyErr_SetString(PyExc_TypeError, + "The argument must be a psycopg2 connection"); + return -1; + } + + Py_INCREF(conn); + self->conn = (connectionObject *)conn; + return 0; +} + +static void +conninfo_dealloc(connInfoObject* self) +{ + Py_CLEAR(self->conn); + Py_TYPE(self)->tp_free((PyObject *)self); +} + + +/* object type */ + +static const char connInfoType_doc[] = + "Details of a database connection."; + +PyTypeObject connInfoType = { + PyVarObject_HEAD_INIT(NULL, 0) + "psycopg2.extensions.ConnectionInfo", + sizeof(connInfoObject), 0, + (destructor)conninfo_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/ + connInfoType_doc, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + connInfoObject_getsets, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)conninfo_init, /*tp_init*/ + 0, /*tp_alloc*/ + conninfo_new, /*tp_new*/ +}; diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c index ff04b25d..92e408ba 100644 --- a/psycopg/psycopgmodule.c +++ b/psycopg/psycopgmodule.c @@ -40,6 +40,7 @@ #include "psycopg/microprotocols.h" #include "psycopg/microprotocols_proto.h" #include "psycopg/error.h" +#include "psycopg/conninfo.h" #include "psycopg/diagnostics.h" #include "psycopg/adapter_qstring.h" @@ -995,6 +996,9 @@ INIT_MODULE(_psycopg)(void) errorType.tp_base = (PyTypeObject *)PyExc_StandardError; if (PyType_Ready(&errorType) == -1) goto exit; + Py_TYPE(&connInfoType) = &PyType_Type; + if (PyType_Ready(&connInfoType) == -1) goto exit; + Py_TYPE(&diagnosticsType) = &PyType_Type; if (PyType_Ready(&diagnosticsType) == -1) goto exit; @@ -1084,6 +1088,7 @@ INIT_MODULE(_psycopg)(void) PyModule_AddObject(module, "Column", (PyObject*)&columnType); PyModule_AddObject(module, "Notify", (PyObject*)¬ifyType); PyModule_AddObject(module, "Xid", (PyObject*)&xidType); + PyModule_AddObject(module, "ConnectionInfo", (PyObject*)&connInfoType); PyModule_AddObject(module, "Diagnostics", (PyObject*)&diagnosticsType); PyModule_AddObject(module, "AsIs", (PyObject*)&asisType); PyModule_AddObject(module, "Binary", (PyObject*)&binaryType); diff --git a/setup.py b/setup.py index ded5a052..15cf267a 100644 --- a/setup.py +++ b/setup.py @@ -490,7 +490,7 @@ sources = [ 'replication_connection_type.c', 'replication_cursor_type.c', 'replication_message_type.c', - 'diagnostics_type.c', 'error_type.c', + 'diagnostics_type.c', 'error_type.c', 'conninfo_type.c', 'lobject_int.c', 'lobject_type.c', 'notify_type.c', 'xid_type.c', @@ -508,7 +508,7 @@ depends = [ 'replication_connection.h', 'replication_cursor.h', 'replication_message.h', - 'notify.h', 'pqpath.h', 'xid.h', 'column.h', + 'notify.h', 'pqpath.h', 'xid.h', 'column.h', 'conninfo.h', 'libpq_support.h', 'win32_support.h', 'adapter_asis.h', 'adapter_binary.h', 'adapter_datetime.h', diff --git a/tests/test_connection.py b/tests/test_connection.py index 498f3513..7ec35947 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1682,17 +1682,14 @@ while True: self.assert_(not err, err) -class TestConnectionProps(ConnectingTestCase): +class TestConnectionInfo(ConnectingTestCase): def test_host(self): - self.assertFalse(self.conn.closed) expected = dbhost if dbhost else "/" - self.assertIn(expected, self.conn.host) + self.assertIn(expected, self.conn.info.host) def test_host_readonly(self): - self.assertFalse(self.conn.closed) with self.assertRaises(AttributeError): - self.conn.host = 'override' - + self.conn.info.host = 'override' def test_suite(): From 9ddf59959fedb9b29d82436bda5726b76d29a958 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Thu, 11 Oct 2018 23:55:36 +0100 Subject: [PATCH 02/12] Adding ConnectionInfo object documentation I'm still fought whether docs should be in the C module or in the .rst. I'd prefer the first because DRY, but writing multiline strings in C really sucks. --- doc/src/connection.rst | 15 ++++----------- doc/src/extensions.rst | 9 +++++++++ lib/extensions.py | 2 +- psycopg/conninfo_type.c | 19 +++++++++++++++++-- 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/doc/src/connection.rst b/doc/src/connection.rst index bd192d6c..59c88bb3 100644 --- a/doc/src/connection.rst +++ b/doc/src/connection.rst @@ -600,19 +600,12 @@ The ``connection`` class .. index:: - pair: Backend; Host + pair: Connection; Info - .. attribute:: host + .. attribute:: info - The server host name of the active connection. - - This can be a host name, an IP address, or a directory path if the - connection is via Unix socket. (The path case can be distinguished - because it will always be an absolute path, beginning with ``/``.) - - .. seealso:: libpq docs for `PQhost()`__ for details. - - .. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQHOST + A `~psycopg2.extensions.ConnectionInfo` object exposing information + about the native libpq connection. .. versionadded:: 2.8.0 diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index 159774b8..520858c5 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -154,6 +154,15 @@ introspection etc. Close the object and remove it from the database. + +.. autoclass:: ConnectionInfo + + .. versionadded:: 2.8 + + .. autoattribute:: host + + + .. class:: Column Description of one result column, exposed as items of the diff --git a/lib/extensions.py b/lib/extensions.py index 3111d415..3e23906e 100644 --- a/lib/extensions.py +++ b/lib/extensions.py @@ -61,7 +61,7 @@ from psycopg2._psycopg import ( # noqa adapt, adapters, encodings, connection, cursor, lobject, Xid, libpq_version, parse_dsn, quote_ident, string_types, binary_types, new_type, new_array_type, register_type, - ISQLQuote, Notify, Diagnostics, Column, + ISQLQuote, Notify, Diagnostics, Column, ConnectionInfo, QueryCanceledError, TransactionRollbackError, set_wait_callback, get_wait_callback, encrypt_password, ) diff --git a/psycopg/conninfo_type.c b/psycopg/conninfo_type.c index b0258eeb..b10a7f9c 100644 --- a/psycopg/conninfo_type.c +++ b/psycopg/conninfo_type.c @@ -30,7 +30,15 @@ static const char host_doc[] = - "The server host name of the active connection."; +"The server host name of the connection.\n" +"\n" +"This can be a host name, an IP address, or a directory path if the\n" +"connection is via Unix socket. (The path case can be distinguished\n" +"because it will always be an absolute path, beginning with ``/``.)\n" +"\n" +".. seealso:: libpq docs for `PQhost()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQHOST"; static PyObject * host_get(connInfoObject *self) @@ -88,7 +96,14 @@ conninfo_dealloc(connInfoObject* self) /* object type */ static const char connInfoType_doc[] = - "Details of a database connection."; +"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) From 0a04c8892d5d704f471f370e6b9a253f6add7c1c Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 12 Oct 2018 03:25:35 +0100 Subject: [PATCH 03/12] 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__) From 1ac6359fefd890a0035a2ae431e57272f0269e6d Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Fri, 12 Oct 2018 04:18:07 +0100 Subject: [PATCH 04/12] Added other members to the ConnectionInfo class Starting deprecating softly some of the methods bloating the connection class. --- doc/src/connection.rst | 122 +++++++++++++++++++++++---------------- doc/src/extensions.rst | 15 +++++ psycopg/conninfo_type.c | 63 +++++++++++++++++++- tests/test_connection.py | 27 ++++++++- 4 files changed, 175 insertions(+), 52 deletions(-) diff --git a/doc/src/connection.rst b/doc/src/connection.rst index 59c88bb3..9c88b77c 100644 --- a/doc/src/connection.rst +++ b/doc/src/connection.rst @@ -342,6 +342,9 @@ The ``connection`` class obscured. + + .. rubric:: Transaction control methods and attributes. + .. index:: pair: Transaction; Autocommit pair: Transaction; Isolation level @@ -672,56 +675,6 @@ The ``connection`` class .. versionadded:: 2.7 - .. index:: - pair: Transaction; Status - - .. method:: get_transaction_status() - - Return the current session transaction status as an integer. Symbolic - constants for the values are defined in the module - `psycopg2.extensions`: see :ref:`transaction-status-constants` - for the available values. - - .. seealso:: libpq docs for `PQtransactionStatus()`__ for details. - - .. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQTRANSACTIONSTATUS - - - .. index:: - pair: Protocol; Version - - .. attribute:: protocol_version - - A read-only integer representing frontend/backend protocol being used. - Currently Psycopg supports only protocol 3, which allows connection - to PostgreSQL server from version 7.4. Psycopg versions previous than - 2.3 support both protocols 2 and 3. - - .. seealso:: libpq docs for `PQprotocolVersion()`__ for details. - - .. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPROTOCOLVERSION - - .. versionadded:: 2.0.12 - - - .. index:: - pair: Server; Version - - .. attribute:: server_version - - A read-only integer representing the backend version. - - The number is formed by converting the major, minor, and revision - numbers into two-decimal-digit numbers and appending them together. - For example, version 8.1.5 will be returned as ``80105``. - - .. seealso:: libpq docs for `PQserverVersion()`__ for details. - - .. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQSERVERVERSION - - .. versionadded:: 2.0.12 - - .. index:: pair: Connection; Status @@ -779,6 +732,7 @@ The ``connection`` class support. + .. rubric:: Methods related to asynchronous support. .. versionadded:: 2.2.0 @@ -825,6 +779,74 @@ The ``connection`` class Return `!True` if the connection is executing an asynchronous operation. + + .. rubric:: informative methods of the native connection + + .. note:: + + These methods are better accessed using the `~connection.info` + attributes and may be dropped in future versions. + + + .. index:: + pair: Transaction; Status + + .. method:: get_transaction_status() + + Also available as `~connection.info`\ `!.`\ + `~psycopg2.extensions.ConnectionInfo.transaction_status`. + + Return the current session transaction status as an integer. Symbolic + constants for the values are defined in the module + `psycopg2.extensions`: see :ref:`transaction-status-constants` + for the available values. + + .. seealso:: libpq docs for `PQtransactionStatus()`__ for details. + + .. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQTRANSACTIONSTATUS + + + .. index:: + pair: Protocol; Version + + .. attribute:: protocol_version + + Also available as `~connection.info`\ `!.`\ + `~psycopg2.extensions.ConnectionInfo.protocol_version`. + + A read-only integer representing frontend/backend protocol being used. + Currently Psycopg supports only protocol 3, which allows connection + to PostgreSQL server from version 7.4. Psycopg versions previous than + 2.3 support both protocols 2 and 3. + + .. seealso:: libpq docs for `PQprotocolVersion()`__ for details. + + .. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPROTOCOLVERSION + + .. versionadded:: 2.0.12 + + + .. index:: + pair: Server; Version + + .. attribute:: server_version + + Also available as `~connection.info`\ `!.`\ + `~psycopg2.extensions.ConnectionInfo.server_version`. + + A read-only integer representing the backend version. + + The number is formed by converting the major, minor, and revision + numbers into two-decimal-digit numbers and appending them together. + For example, version 8.1.5 will be returned as ``80105``. + + .. seealso:: libpq docs for `PQserverVersion()`__ for details. + + .. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQSERVERVERSION + + .. versionadded:: 2.0.12 + + .. testcode:: :hide: diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index 271ca930..9cef3d68 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -166,6 +166,21 @@ introspection etc. .. autoattribute:: port .. autoattribute:: options .. autoattribute:: status + .. autoattribute:: transaction_status + + .. autoattribute:: protocol_version + + Currently Psycopg supports only protocol 3, which allows connection + to PostgreSQL server from version 7.4. Psycopg versions previous than + 2.3 support both protocols 2 and 3. + + .. autoattribute:: server_version + + The number is formed by converting the major, minor, and revision + numbers into two-decimal-digit numbers and appending them together. + After PostgreSQL 10 the minor version was dropped, so the second group + of digits is always ``00``. For example, version 9.3.5 will be + returned as ``90305``, version 10.2 as ``100002``. diff --git a/psycopg/conninfo_type.c b/psycopg/conninfo_type.c index 93440ac0..134bb864 100644 --- a/psycopg/conninfo_type.c +++ b/psycopg/conninfo_type.c @@ -167,7 +167,7 @@ options_get(connInfoObject *self) static const char status_doc[] = -"Return the status of the connection.\n" +"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" @@ -183,6 +183,61 @@ status_get(connInfoObject *self) } +static const char transaction_status_doc[] = +"The current in-transaction status of the connection.\n" +"\n" +"Symbolic constants for the values are defined in the module\n" +"`psycopg2.extensions`: see :ref:`transaction-status-constants` for the\n" +"available values.\n" +"\n" +".. seealso:: libpq docs for `PQtransactionStatus()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQTRANSACTIONSTATUS"; + +static PyObject * +transaction_status_get(connInfoObject *self) +{ + PGTransactionStatusType val; + + val = PQtransactionStatus(self->conn->pgconn); + return PyInt_FromLong((long)val); +} + + +static const char protocol_version_doc[] = +"The frontend/backend protocol being used.\n" +"\n" +".. seealso:: libpq docs for `PQprotocolVersion()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQPROTOCOLVERSION"; + +static PyObject * +protocol_version_get(connInfoObject *self) +{ + int val; + + val = PQprotocolVersion(self->conn->pgconn); + return PyInt_FromLong((long)val); +} + + +static const char server_version_doc[] = +"Returns an integer representing the server version.\n" +"\n" +".. seealso:: libpq docs for `PQserverVersion()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQSERVERVERSION"; + +static PyObject * +server_version_get(connInfoObject *self) +{ + int val; + + val = PQserverVersion(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 }, @@ -191,6 +246,12 @@ static struct PyGetSetDef connInfoObject_getsets[] = { { "port", (getter)port_get, NULL, (char *)port_doc }, { "options", (getter)options_get, NULL, (char *)options_doc }, { "status", (getter)status_get, NULL, (char *)status_doc }, + { "transaction_status", (getter)transaction_status_get, NULL, + (char *)transaction_status_doc }, + { "protocol_version", (getter)protocol_version_get, NULL, + (char *)protocol_version_doc }, + { "server_version", (getter)server_version_get, NULL, + (char *)server_version_doc }, {NULL} }; diff --git a/tests/test_connection.py b/tests/test_connection.py index 728f7ec1..06735fbb 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1699,7 +1699,9 @@ class TestConnectionInfo(ConnectingTestCase): self.assert_(self.bconn.info.dbname is None) def test_user(self): - self.assert_(isinstance(self.conn.info.user, str)) + cur = self.conn.cursor() + cur.execute("select user") + self.assertEqual(self.conn.info.user, cur.fetchone()[0]) self.assert_(self.bconn.info.user is None) def test_password(self): @@ -1727,6 +1729,29 @@ class TestConnectionInfo(ConnectingTestCase): self.assertEqual(self.conn.info.status, 0) self.assertEqual(self.bconn.info.status, 1) + def test_transaction_status(self): + self.assertEqual(self.conn.info.transaction_status, 0) + cur = self.conn.cursor() + cur.execute("select 1") + self.assertEqual(self.conn.info.transaction_status, 2) + self.assertEqual(self.bconn.info.transaction_status, 4) + + def test_protocol_version(self): + self.assertEqual(self.conn.info.protocol_version, 3) + self.assertEqual(self.bconn.info.protocol_version, 0) + + def test_server_version(self): + cur = self.conn.cursor() + try: + cur.execute("show server_version_num") + except psycopg2.DatabaseError: + self.assert_(isinstance(self.conn.info.server_version, int)) + else: + self.assertEqual( + self.conn.info.server_version, int(cur.fetchone()[0])) + + self.assertEqual(self.bconn.info.server_version, 0) + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__) From 439dff974d868b564f660e444b348ad7f61a7989 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sat, 13 Oct 2018 00:47:04 +0100 Subject: [PATCH 05/12] Added ConnectionInfo.error_message --- doc/src/extensions.rst | 1 + psycopg/conninfo_type.c | 24 ++++++++++++++++++++++++ tests/test_connection.py | 12 ++++++++++++ 3 files changed, 37 insertions(+) diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index 9cef3d68..f15c4c1d 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -182,6 +182,7 @@ introspection etc. of digits is always ``00``. For example, version 9.3.5 will be returned as ``90305``, version 10.2 as ``100002``. + .. autoattribute:: error_message .. class:: Column diff --git a/psycopg/conninfo_type.c b/psycopg/conninfo_type.c index 134bb864..4dbaef2b 100644 --- a/psycopg/conninfo_type.c +++ b/psycopg/conninfo_type.c @@ -238,6 +238,28 @@ server_version_get(connInfoObject *self) } +static const char error_message_doc[] = +"The error message most recently generated by an operation on the connection.\n" +"\n" +"`!None` if there is no current message.\n" +"\n" +".. seealso:: libpq docs for `PQerrorMessage()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQERRORMESSAGE"; + +static PyObject * +error_message_get(connInfoObject *self) +{ + const char *val; + + val = PQerrorMessage(self->conn->pgconn); + if (!val || !val[0]) { + Py_RETURN_NONE; + } + return conn_text_from_chars(self->conn, val); +} + + static struct PyGetSetDef connInfoObject_getsets[] = { { "dbname", (getter)dbname_get, NULL, (char *)dbname_doc }, { "user", (getter)user_get, NULL, (char *)user_doc }, @@ -252,6 +274,8 @@ static struct PyGetSetDef connInfoObject_getsets[] = { (char *)protocol_version_doc }, { "server_version", (getter)server_version_get, NULL, (char *)server_version_doc }, + { "error_message", (getter)error_message_get, NULL, + (char *)error_message_doc }, {NULL} }; diff --git a/tests/test_connection.py b/tests/test_connection.py index 06735fbb..3d678573 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1752,6 +1752,18 @@ class TestConnectionInfo(ConnectingTestCase): self.assertEqual(self.bconn.info.server_version, 0) + def test_error_message(self): + self.assertIsNone(self.conn.info.error_message) + self.assertIsNotNone(self.bconn.info.error_message) + + cur = self.conn.cursor() + try: + cur.execute("select 1 from nosuchtable") + except psycopg2.DatabaseError: + pass + + self.assert_('nosuchtable' in self.conn.info.error_message) + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__) From 4f7bbdca26b9d4bac0de375d5a531e9ff913ac52 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sat, 13 Oct 2018 00:55:20 +0100 Subject: [PATCH 06/12] Added missing class signatures in the docs --- doc/src/extensions.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index f15c4c1d..a7ee289b 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -155,7 +155,7 @@ introspection etc. -.. autoclass:: ConnectionInfo +.. autoclass:: ConnectionInfo(connection) .. versionadded:: 2.8 @@ -185,7 +185,7 @@ introspection etc. .. autoattribute:: error_message -.. class:: Column +.. class:: Column(\*args, \*\*kwargs) Description of one result column, exposed as items of the `cursor.description` sequence. From cb3d5f9d925cd8b06e1ba5ed02d0997d84da26d6 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sat, 13 Oct 2018 01:36:07 +0100 Subject: [PATCH 07/12] Added all the missing ConnectionInfo attributes --- doc/src/extensions.rst | 5 ++ psycopg/conninfo_type.c | 122 +++++++++++++++++++++++++++++++++++++-- tests/test_connection.py | 28 +++++++++ 3 files changed, 151 insertions(+), 4 deletions(-) diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index a7ee289b..456d1da0 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -183,6 +183,11 @@ introspection etc. returned as ``90305``, version 10.2 as ``100002``. .. autoattribute:: error_message + .. autoattribute:: socket + .. autoattribute:: backend_pid + .. autoattribute:: needs_password + .. autoattribute:: used_password + .. autoattribute:: ssl_in_use .. class:: Column(\*args, \*\*kwargs) diff --git a/psycopg/conninfo_type.c b/psycopg/conninfo_type.c index 4dbaef2b..33f2beff 100644 --- a/psycopg/conninfo_type.c +++ b/psycopg/conninfo_type.c @@ -43,8 +43,7 @@ static const char connInfoType_doc[] = static const char dbname_doc[] = "The database name of the connection.\n" "\n" -"Wrapper for the `PQdb()`__ function.\n" -"\n" +".. seealso:: libpq docs for `PQdb()`__ for details.\n" ".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" "#LIBPQ-PQDB"; @@ -64,8 +63,7 @@ dbname_get(connInfoObject *self) static const char user_doc[] = "The user name of the connection.\n" "\n" -"Wrapper for the `PQuser()`__ function.\n" -"\n" +".. seealso:: libpq docs for `PQuser()`__ for details.\n" ".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" "#LIBPQ-PQUSER"; @@ -129,6 +127,8 @@ host_get(connInfoObject *self) static const char port_doc[] = "The port of the connection.\n" "\n" +":type: `!int`\n" +"\n" ".. seealso:: libpq docs for `PQport()`__ for details.\n" ".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" "#LIBPQ-PQPORT"; @@ -169,6 +169,8 @@ options_get(connInfoObject *self) static const char status_doc[] = "The status of the connection.\n" "\n" +":type: `!int`\n" +"\n" ".. seealso:: libpq docs for `PQstatus()`__ for details.\n" ".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" "#LIBPQ-PQSTATUS"; @@ -190,6 +192,8 @@ static const char transaction_status_doc[] = "`psycopg2.extensions`: see :ref:`transaction-status-constants` for the\n" "available values.\n" "\n" +":type: `!int`\n" +"\n" ".. seealso:: libpq docs for `PQtransactionStatus()`__ for details.\n" ".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" "#LIBPQ-PQTRANSACTIONSTATUS"; @@ -207,6 +211,8 @@ transaction_status_get(connInfoObject *self) static const char protocol_version_doc[] = "The frontend/backend protocol being used.\n" "\n" +":type: `!int`\n" +"\n" ".. seealso:: libpq docs for `PQprotocolVersion()`__ for details.\n" ".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" "#LIBPQ-PQPROTOCOLVERSION"; @@ -224,6 +230,8 @@ protocol_version_get(connInfoObject *self) static const char server_version_doc[] = "Returns an integer representing the server version.\n" "\n" +":type: `!int`\n" +"\n" ".. seealso:: libpq docs for `PQserverVersion()`__ for details.\n" ".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" "#LIBPQ-PQSERVERVERSION"; @@ -260,6 +268,104 @@ error_message_get(connInfoObject *self) } +static const char socket_doc[] = +"The file descriptor number of the connection socket to the server.\n" +"\n" +":type: `!int`\n" +"\n" +".. seealso:: libpq docs for `PQsocket()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQSOCKET"; + +static PyObject * +socket_get(connInfoObject *self) +{ + int val; + + val = PQsocket(self->conn->pgconn); + return PyInt_FromLong((long)val); +} + + +static const char backend_pid_doc[] = +"The process ID (PID) of the backend process handling this connection.\n" +"\n" +":type: `!int`\n" +"\n" +".. seealso:: libpq docs for `PQbackendPID()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQBACKENDPID"; + +static PyObject * +backend_pid_get(connInfoObject *self) +{ + int val; + + val = PQbackendPID(self->conn->pgconn); + return PyInt_FromLong((long)val); +} + + +static const char needs_password_doc[] = +"The connection authentication method required a password, but none was available.\n" +"\n" +":type: `!bool`\n" +"\n" +".. seealso:: libpq docs for `PQconnectionNeedsPassword()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQCONNECTIONNEEDSPASSWORD"; + +static PyObject * +needs_password_get(connInfoObject *self) +{ + PyObject *rv; + + rv = PQconnectionNeedsPassword(self->conn->pgconn) ? Py_True : Py_False; + Py_INCREF(rv); + return rv; +} + + +static const char used_password_doc[] = +"The connection authentication method used a password.\n" +"\n" +":type: `!bool`\n" +"\n" +".. seealso:: libpq docs for `PQconnectionUsedPassword()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQCONNECTIONUSEDPASSWORD"; + +static PyObject * +used_password_get(connInfoObject *self) +{ + PyObject *rv; + + rv = PQconnectionUsedPassword(self->conn->pgconn) ? Py_True : Py_False; + Py_INCREF(rv); + return rv; +} + + +static const char ssl_in_use_doc[] = +"`!True` if the connection uses SSL, `!False` if not.\n" +"\n" +":type: `!bool`\n" +"\n" +".. seealso:: libpq docs for `PQsslInUse()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQSSLINUSE"; + +static PyObject * +ssl_in_use_get(connInfoObject *self) +{ + PyObject *rv; + + rv = PQsslInUse(self->conn->pgconn) ? Py_True : Py_False; + Py_INCREF(rv); + return rv; +} + + static struct PyGetSetDef connInfoObject_getsets[] = { { "dbname", (getter)dbname_get, NULL, (char *)dbname_doc }, { "user", (getter)user_get, NULL, (char *)user_doc }, @@ -276,6 +382,14 @@ static struct PyGetSetDef connInfoObject_getsets[] = { (char *)server_version_doc }, { "error_message", (getter)error_message_get, NULL, (char *)error_message_doc }, + { "socket", (getter)socket_get, NULL, (char *)socket_doc }, + { "backend_pid", (getter)backend_pid_get, NULL, (char *)backend_pid_doc }, + { "used_password", (getter)used_password_get, NULL, + (char *)used_password_doc }, + { "needs_password", (getter)needs_password_get, NULL, + (char *)needs_password_doc }, + { "ssl_in_use", (getter)ssl_in_use_get, NULL, + (char *)ssl_in_use_doc }, {NULL} }; diff --git a/tests/test_connection.py b/tests/test_connection.py index 3d678573..c0bc6cc9 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1764,6 +1764,34 @@ class TestConnectionInfo(ConnectingTestCase): self.assert_('nosuchtable' in self.conn.info.error_message) + def test_socket(self): + self.assert_(self.conn.info.socket >= 0) + self.assert_(self.bconn.info.socket < 0) + + def test_backend_pid(self): + cur = self.conn.cursor() + try: + cur.execute("select pg_backend_pid()") + except psycopg2.DatabaseError: + self.assert_(self.conn.info.backend_pid > 0) + else: + self.assertEqual( + self.conn.info.backend_pid, int(cur.fetchone()[0])) + + self.assert_(self.bconn.info.backend_pid == 0) + + def test_needs_password(self): + self.assertIs(self.conn.info.needs_password, False) + self.assertIs(self.bconn.info.needs_password, False) + + def test_used_password(self): + self.assertIsInstance(self.conn.info.used_password, bool) + self.assertIs(self.bconn.info.used_password, False) + + def test_ssl_in_use(self): + self.assertIsInstance(self.conn.info.ssl_in_use, bool) + self.assertIs(self.bconn.info.ssl_in_use, False) + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__) From 9f6a3a5e963f01417cb82cf03739cf594c4d86c9 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sat, 13 Oct 2018 02:21:38 +0100 Subject: [PATCH 08/12] Added ConnectionInfo.ssl_attribute() --- doc/src/extensions.rst | 2 + psycopg/conninfo_type.c | 80 +++++++++++++++++++++++++++++++++++++++- tests/test_connection.py | 15 ++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index 456d1da0..02937409 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -188,6 +188,8 @@ introspection etc. .. autoattribute:: needs_password .. autoattribute:: used_password .. autoattribute:: ssl_in_use + .. automethod:: ssl_attribute(name) + .. autoattribute:: ssl_attribute_names .. class:: Column(\*args, \*\*kwargs) diff --git a/psycopg/conninfo_type.c b/psycopg/conninfo_type.c index 33f2beff..c7a21110 100644 --- a/psycopg/conninfo_type.c +++ b/psycopg/conninfo_type.c @@ -366,6 +366,76 @@ ssl_in_use_get(connInfoObject *self) } +static const char ssl_attribute_doc[] = +"Returns SSL-related information about the connection.\n" +"\n" +":param name: The name of the attribute to return.\n" +":type name: `!str`\n" +":return: The attribute value, `!None` if unknown.\n" +":rtype: `!str`\n" +"\n" +"Valid names are available in `ssl_attribute_names`.\n" +"\n" +".. seealso:: libpq docs for `PQsslAttribute()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQSSLATTRIBUTE"; + +static PyObject * +ssl_attribute(connInfoObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"name", NULL}; + const char *name; + const char *val; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &name)) { + return NULL; + } + + val = PQsslAttribute(self->conn->pgconn, name); + + if (!val) { + Py_RETURN_NONE; + } + else { + return conn_text_from_chars(self->conn, val); + } +} + +static const char ssl_attribute_names_doc[] = +"The list of the SSL attribute names available.\n" +"\n" +":type: `!list` of `!str`\n" +"\n" +".. seealso:: libpq docs for `PQsslAttributeNames()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQSSLATTRIBUTENAMES"; + +static PyObject * +ssl_attribute_names_get(connInfoObject *self) +{ + const char* const* names; + int i; + PyObject *l = NULL, *s = NULL, *rv = NULL; + + names = PQsslAttributeNames(self->conn->pgconn); + if (!(l = PyList_New(0))) { goto exit; } + + for (i = 0; names[i]; i++) { + if (!(s = conn_text_from_chars(self->conn, names[i]))) { goto exit; } + if (0 != PyList_Append(l, s)) { goto exit; } + Py_CLEAR(s); + } + + rv = l; + l = NULL; + +exit: + Py_XDECREF(l); + Py_XDECREF(s); + return rv; +} + + static struct PyGetSetDef connInfoObject_getsets[] = { { "dbname", (getter)dbname_get, NULL, (char *)dbname_doc }, { "user", (getter)user_get, NULL, (char *)user_doc }, @@ -390,6 +460,14 @@ static struct PyGetSetDef connInfoObject_getsets[] = { (char *)needs_password_doc }, { "ssl_in_use", (getter)ssl_in_use_get, NULL, (char *)ssl_in_use_doc }, + { "ssl_attribute_names", (getter)ssl_attribute_names_get, NULL, + (char *)ssl_attribute_names_doc }, + {NULL} +}; + +static struct PyMethodDef connInfoObject_methods[] = { + {"ssl_attribute", (PyCFunction)ssl_attribute, + METH_VARARGS|METH_KEYWORDS, ssl_attribute_doc}, {NULL} }; @@ -457,7 +535,7 @@ PyTypeObject connInfoType = { 0, /*tp_weaklistoffset*/ 0, /*tp_iter*/ 0, /*tp_iternext*/ - 0, /*tp_methods*/ + connInfoObject_methods, /*tp_methods*/ 0, /*tp_members*/ connInfoObject_getsets, /*tp_getset*/ 0, /*tp_base*/ diff --git a/tests/test_connection.py b/tests/test_connection.py index c0bc6cc9..0c8d40d6 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1792,6 +1792,21 @@ class TestConnectionInfo(ConnectingTestCase): self.assertIsInstance(self.conn.info.ssl_in_use, bool) self.assertIs(self.bconn.info.ssl_in_use, False) + def test_ssl_attribute(self): + attribs = self.conn.info.ssl_attribute_names + self.assert_(attribs) + if self.conn.info.ssl_in_use: + for attrib in attribs: + self.assertIsInstance(self.conn.info.ssl_attribute(attrib), str) + else: + for attrib in attribs: + self.assertIsNone(self.conn.info.ssl_attribute(attrib)) + + self.assertIsNone(self.conn.info.ssl_attribute('wat')) + + for attrib in attribs: + self.assertIsNone(self.bconn.info.ssl_attribute(attrib)) + def test_suite(): return unittest.TestLoader().loadTestsFromName(__name__) From d138e42ee5ac39797ae6b563ddd87eca3166b458 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sat, 13 Oct 2018 02:40:17 +0100 Subject: [PATCH 09/12] Added ConnectionInfo.parameter_status() --- doc/src/connection.rst | 84 +++++++++++++++++++++------------------- doc/src/extensions.rst | 1 + psycopg/conninfo_type.c | 36 +++++++++++++++++ tests/test_connection.py | 15 +++++++ 4 files changed, 97 insertions(+), 39 deletions(-) diff --git a/doc/src/connection.rst b/doc/src/connection.rst index 9c88b77c..5310458b 100644 --- a/doc/src/connection.rst +++ b/doc/src/connection.rst @@ -613,45 +613,6 @@ The ``connection`` class .. versionadded:: 2.8.0 - .. index:: - pair: Backend; PID - - .. method:: get_backend_pid() - - Returns the process ID (PID) of the backend server process handling - this connection. - - Note that the PID belongs to a process executing on the database - server host, not the local host! - - .. seealso:: libpq docs for `PQbackendPID()`__ for details. - - .. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQBACKENDPID - - .. versionadded:: 2.0.8 - - - .. index:: - pair: Server; Parameters - - .. method:: get_parameter_status(parameter) - - Look up a current parameter setting of the server. - - Potential values for ``parameter`` are: ``server_version``, - ``server_encoding``, ``client_encoding``, ``is_superuser``, - ``session_authorization``, ``DateStyle``, ``TimeZone``, - ``integer_datetimes``, and ``standard_conforming_strings``. - - If server did not report requested parameter, return `!None`. - - .. seealso:: libpq docs for `PQparameterStatus()`__ for details. - - .. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPARAMETERSTATUS - - .. versionadded:: 2.0.12 - - .. index:: pair: Connection; Parameters @@ -847,6 +808,51 @@ The ``connection`` class .. versionadded:: 2.0.12 + .. index:: + pair: Backend; PID + + .. method:: get_backend_pid() + + Also available as `~connection.info`\ `!.`\ + `~psycopg2.extensions.ConnectionInfo.backend_pid`. + + Returns the process ID (PID) of the backend server process handling + this connection. + + Note that the PID belongs to a process executing on the database + server host, not the local host! + + .. seealso:: libpq docs for `PQbackendPID()`__ for details. + + .. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQBACKENDPID + + .. versionadded:: 2.0.8 + + + .. index:: + pair: Server; Parameters + + .. method:: get_parameter_status(parameter) + + Also available as `~connection.info`\ `!.`\ + `~psycopg2.extensions.ConnectionInfo.parameter_status()`. + + Look up a current parameter setting of the server. + + Potential values for ``parameter`` are: ``server_version``, + ``server_encoding``, ``client_encoding``, ``is_superuser``, + ``session_authorization``, ``DateStyle``, ``TimeZone``, + ``integer_datetimes``, and ``standard_conforming_strings``. + + If server did not report requested parameter, return `!None`. + + .. seealso:: libpq docs for `PQparameterStatus()`__ for details. + + .. __: https://www.postgresql.org/docs/current/static/libpq-status.html#LIBPQ-PQPARAMETERSTATUS + + .. versionadded:: 2.0.12 + + .. testcode:: :hide: diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index 02937409..10ed81a6 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -167,6 +167,7 @@ introspection etc. .. autoattribute:: options .. autoattribute:: status .. autoattribute:: transaction_status + .. automethod:: parameter_status(name) .. autoattribute:: protocol_version diff --git a/psycopg/conninfo_type.c b/psycopg/conninfo_type.c index c7a21110..ed89c6c8 100644 --- a/psycopg/conninfo_type.c +++ b/psycopg/conninfo_type.c @@ -208,6 +208,40 @@ transaction_status_get(connInfoObject *self) } +static const char parameter_status_doc[] = +"Looks up a current parameter setting of the server.\n" +"\n" +":param name: The name of the parameter to return.\n" +":type name: `!str`\n" +":return: The parameter value, `!None` if the parameter is unknown.\n" +":rtype: `!str`\n" +"\n" +".. seealso:: libpq docs for `PQparameterStatus()`__ for details.\n" +".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" + "#LIBPQ-PQPARAMETERSTATUS"; + +static PyObject * +parameter_status(connInfoObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"name", NULL}; + const char *name; + const char *val; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &name)) { + return NULL; + } + + val = PQparameterStatus(self->conn->pgconn, name); + + if (!val) { + Py_RETURN_NONE; + } + else { + return conn_text_from_chars(self->conn, val); + } +} + + static const char protocol_version_doc[] = "The frontend/backend protocol being used.\n" "\n" @@ -468,6 +502,8 @@ static struct PyGetSetDef connInfoObject_getsets[] = { static struct PyMethodDef connInfoObject_methods[] = { {"ssl_attribute", (PyCFunction)ssl_attribute, METH_VARARGS|METH_KEYWORDS, ssl_attribute_doc}, + {"parameter_status", (PyCFunction)parameter_status, + METH_VARARGS|METH_KEYWORDS, parameter_status_doc}, {NULL} }; diff --git a/tests/test_connection.py b/tests/test_connection.py index 0c8d40d6..0b683892 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1736,6 +1736,21 @@ class TestConnectionInfo(ConnectingTestCase): self.assertEqual(self.conn.info.transaction_status, 2) self.assertEqual(self.bconn.info.transaction_status, 4) + def test_parameter_status(self): + cur = self.conn.cursor() + try: + cur.execute("show server_version") + except psycopg2.DatabaseError: + self.assertIsInstance( + self.conn.info.parameter_status('server_version'), str) + else: + self.assertEqual( + self.conn.info.parameter_status('server_version'), + cur.fetchone()[0]) + + self.assertIsNone(self.conn.info.parameter_status('wat')) + self.assertIsNone(self.bconn.info.parameter_status('server_version')) + def test_protocol_version(self): self.assertEqual(self.conn.info.protocol_version, 3) self.assertEqual(self.bconn.info.protocol_version, 0) From 704e6797e7dd4ac0046caa3230a8faa1f247859f Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sat, 13 Oct 2018 03:09:39 +0100 Subject: [PATCH 10/12] Guard from some info functions not available in some libpq versions --- psycopg/conninfo_type.c | 45 +++++++++++++++++++++++++++++++++------- tests/test_connection.py | 11 ++++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/psycopg/conninfo_type.c b/psycopg/conninfo_type.c index ed89c6c8..740840fd 100644 --- a/psycopg/conninfo_type.c +++ b/psycopg/conninfo_type.c @@ -383,6 +383,9 @@ used_password_get(connInfoObject *self) static const char ssl_in_use_doc[] = "`!True` if the connection uses SSL, `!False` if not.\n" "\n" +"Only available if psycopg was built with libpq >= 9.5; raise\n" +"`~psycopg2.NotSupportedError` otherwise.\n" +"\n" ":type: `!bool`\n" "\n" ".. seealso:: libpq docs for `PQsslInUse()`__ for details.\n" @@ -392,10 +395,15 @@ static const char ssl_in_use_doc[] = static PyObject * ssl_in_use_get(connInfoObject *self) { - PyObject *rv; + PyObject *rv = NULL; +#if PG_VERSION_NUM >= 90500 rv = PQsslInUse(self->conn->pgconn) ? Py_True : Py_False; Py_INCREF(rv); +#else + PyErr_SetString(NotSupportedError, + "'ssl_in_use' not available in libpq < 9.5"); +#endif return rv; } @@ -408,6 +416,9 @@ static const char ssl_attribute_doc[] = ":return: The attribute value, `!None` if unknown.\n" ":rtype: `!str`\n" "\n" +"Only available if psycopg was built with libpq >= 9.5; raise\n" +"`~psycopg2.NotSupportedError` otherwise.\n" +"\n" "Valid names are available in `ssl_attribute_names`.\n" "\n" ".. seealso:: libpq docs for `PQsslAttribute()`__ for details.\n" @@ -419,20 +430,29 @@ ssl_attribute(connInfoObject *self, PyObject *args, PyObject *kwargs) { static char *kwlist[] = {"name", NULL}; const char *name; - const char *val; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &name)) { return NULL; } - val = PQsslAttribute(self->conn->pgconn, name); +#if PG_VERSION_NUM >= 90500 + { + const char *val; - if (!val) { - Py_RETURN_NONE; - } - else { - return conn_text_from_chars(self->conn, val); + val = PQsslAttribute(self->conn->pgconn, name); + + if (!val) { + Py_RETURN_NONE; + } + else { + return conn_text_from_chars(self->conn, val); + } } +#else + PyErr_SetString(NotSupportedError, + "'ssl_attribute()' not available in libpq < 9.5"); + return NULL; +#endif } static const char ssl_attribute_names_doc[] = @@ -440,6 +460,9 @@ static const char ssl_attribute_names_doc[] = "\n" ":type: `!list` of `!str`\n" "\n" +"Only available if psycopg was built with libpq >= 9.5; raise\n" +"`~psycopg2.NotSupportedError` otherwise.\n" +"\n" ".. seealso:: libpq docs for `PQsslAttributeNames()`__ for details.\n" ".. __: https://www.postgresql.org/docs/current/static/libpq-status.html" "#LIBPQ-PQSSLATTRIBUTENAMES"; @@ -447,6 +470,7 @@ static const char ssl_attribute_names_doc[] = static PyObject * ssl_attribute_names_get(connInfoObject *self) { +#if PG_VERSION_NUM >= 90500 const char* const* names; int i; PyObject *l = NULL, *s = NULL, *rv = NULL; @@ -467,6 +491,11 @@ exit: Py_XDECREF(l); Py_XDECREF(s); return rv; +#else + PyErr_SetString(NotSupportedError, + "'ssl_attribute_names not available in libpq < 9.5"); + return NULL; +#endif } diff --git a/tests/test_connection.py b/tests/test_connection.py index 0b683892..cb70bb0a 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -1803,10 +1803,21 @@ class TestConnectionInfo(ConnectingTestCase): self.assertIsInstance(self.conn.info.used_password, bool) self.assertIs(self.bconn.info.used_password, False) + @skip_before_libpq(9, 5) def test_ssl_in_use(self): self.assertIsInstance(self.conn.info.ssl_in_use, bool) self.assertIs(self.bconn.info.ssl_in_use, False) + @skip_after_libpq(9, 5) + def test_ssl_not_supported(self): + with self.assertRaises(psycopg2.NotSupportedError): + self.conn.info.ssl_in_use + with self.assertRaises(psycopg2.NotSupportedError): + self.conn.info.ssl_attribute_names + with self.assertRaises(psycopg2.NotSupportedError): + self.conn.info.ssl_attribute('wat') + + @skip_before_libpq(9, 5) def test_ssl_attribute(self): attribs = self.conn.info.ssl_attribute_names self.assert_(attribs) From 44bd2927c50c516bde149021ee5560f4b295d5b7 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sat, 13 Oct 2018 03:28:42 +0100 Subject: [PATCH 11/12] Use the connection.info properties instead of the legacy methods --- doc/src/extensions.rst | 2 +- doc/src/module.rst | 7 ++-- lib/_json.py | 2 +- lib/_range.py | 4 +-- lib/extras.py | 6 ++-- lib/pool.py | 2 +- psycopg/notify_type.c | 2 +- tests/test_async.py | 6 ++-- tests/test_connection.py | 74 +++++++++++++++++++------------------- tests/test_cursor.py | 14 ++++---- tests/test_lobject.py | 8 ++--- tests/test_notify.py | 6 ++-- tests/test_quote.py | 2 +- tests/test_types_extras.py | 12 +++---- tests/testutils.py | 8 ++--- 15 files changed, 78 insertions(+), 77 deletions(-) diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst index 10ed81a6..990fc6db 100644 --- a/doc/src/extensions.rst +++ b/doc/src/extensions.rst @@ -820,7 +820,7 @@ Transaction status constants ---------------------------- These values represent the possible status of a transaction: the current value -can be read using the `connection.get_transaction_status()` method. +can be read using the `connection.info.transaction_status` property. .. data:: TRANSACTION_STATUS_IDLE diff --git a/doc/src/module.rst b/doc/src/module.rst index 034c7bb0..b3c201fa 100644 --- a/doc/src/module.rst +++ b/doc/src/module.rst @@ -117,9 +117,10 @@ The module interface respects the standard defined in the |DBAPI|_. Integer constant reporting the version of the ``libpq`` library this ``psycopg2`` module was compiled with (in the same format of - `~connection.server_version`). If this value is greater or equal than - ``90100`` then you may query the version of the actually loaded library - using the `~psycopg2.extensions.libpq_version()` function. + `~psycopg2.extensions.ConnectionInfo.server_version`). If this value is + greater or equal than ``90100`` then you may query the version of the + actually loaded library using the `~psycopg2.extensions.libpq_version()` + function. .. index:: diff --git a/lib/_json.py b/lib/_json.py index 71c2b76c..0438c25b 100644 --- a/lib/_json.py +++ b/lib/_json.py @@ -185,7 +185,7 @@ def _get_json_oids(conn_or_curs, name='json'): conn_status = conn.status # column typarray not available before PG 8.3 - typarray = conn.server_version >= 80300 and "typarray" or "NULL" + typarray = conn.info.server_version >= 80300 and "typarray" or "NULL" # get the oid for the hstore curs.execute( diff --git a/lib/_range.py b/lib/_range.py index e72c8789..1747b201 100644 --- a/lib/_range.py +++ b/lib/_range.py @@ -352,9 +352,9 @@ class RangeCaster(object): from psycopg2.extras import _solve_conn_curs conn, curs = _solve_conn_curs(conn_or_curs) - if conn.server_version < 90200: + if conn.info.server_version < 90200: raise ProgrammingError("range types not available in version %s" - % conn.server_version) + % conn.info.server_version) # Store the transaction status of the connection to revert it after use conn_status = conn.status diff --git a/lib/extras.py b/lib/extras.py index af59d2f1..4dea3f93 100644 --- a/lib/extras.py +++ b/lib/extras.py @@ -806,7 +806,7 @@ class HstoreAdapter(object): self.conn = conn # use an old-style getquoted implementation if required - if conn.server_version < 90000: + if conn.info.server_version < 90000: self.getquoted = self._getquoted_8 def _getquoted_8(self): @@ -911,7 +911,7 @@ class HstoreAdapter(object): conn_status = conn.status # column typarray not available before PG 8.3 - typarray = conn.server_version >= 80300 and "typarray" or "NULL" + typarray = conn.info.server_version >= 80300 and "typarray" or "NULL" rv0, rv1 = [], [] @@ -1097,7 +1097,7 @@ class CompositeCaster(object): schema = 'public' # column typarray not available before PG 8.3 - typarray = conn.server_version >= 80300 and "typarray" or "NULL" + typarray = conn.info.server_version >= 80300 and "typarray" or "NULL" # get the type oid and attributes curs.execute("""\ diff --git a/lib/pool.py b/lib/pool.py index 6c26f7d0..734c136d 100644 --- a/lib/pool.py +++ b/lib/pool.py @@ -105,7 +105,7 @@ class AbstractConnectionPool(object): # Return the connection into a consistent state before putting # it back into the pool if not conn.closed: - status = conn.get_transaction_status() + status = conn.info.transaction_status if status == _ext.TRANSACTION_STATUS_UNKNOWN: # server connection lost conn.close() diff --git a/psycopg/notify_type.c b/psycopg/notify_type.c index aab9a43b..4677d578 100644 --- a/psycopg/notify_type.c +++ b/psycopg/notify_type.c @@ -40,7 +40,7 @@ static const char notify_doc[] = static const char pid_doc[] = "The ID of the backend process that sent the notification.\n\n" "Note: if the sending session was handled by Psycopg, you can use\n" - "`~connection.get_backend_pid()` to know its PID."; + "`~connection.info.backend_pid` to know its PID."; static const char channel_doc[] = "The name of the channel to which the notification was sent."; diff --git a/tests/test_async.py b/tests/test_async.py index dfdc44ea..066e352a 100755 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -192,7 +192,7 @@ class AsyncTests(ConnectingTestCase): self.assertTrue(self.conn.isexecuting()) # getting transaction status works - self.assertEquals(self.conn.get_transaction_status(), + self.assertEquals(self.conn.info.transaction_status, ext.TRANSACTION_STATUS_ACTIVE) self.assertTrue(self.conn.isexecuting()) @@ -359,7 +359,7 @@ class AsyncTests(ConnectingTestCase): self.assertEquals(self.sync_conn.notifies, []) - pid = self.conn.get_backend_pid() + pid = self.conn.info.backend_pid for _ in range(5): self.wait(self.sync_conn) if not self.sync_conn.notifies: @@ -418,7 +418,7 @@ class AsyncTests(ConnectingTestCase): def test_notices(self): del self.conn.notices[:] cur = self.conn.cursor() - if self.conn.server_version >= 90300: + if self.conn.info.server_version >= 90300: cur.execute("set client_min_messages=debug1") self.wait(cur) cur.execute("create temp table chatty (id serial primary key);") diff --git a/tests/test_connection.py b/tests/test_connection.py index cb70bb0a..40f3e765 100755 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -87,13 +87,13 @@ class ConnectionTests(ConnectingTestCase): conn.autocommit = True conn.isolation_level = 'serializable' conn.readonly = True - if self.conn.server_version >= 90100: + if self.conn.info.server_version >= 90100: conn.deferrable = False self.assert_(conn.autocommit) self.assertEqual(conn.isolation_level, ext.ISOLATION_LEVEL_SERIALIZABLE) self.assert_(conn.readonly is True) - if self.conn.server_version >= 90100: + if self.conn.info.server_version >= 90100: self.assert_(conn.deferrable is False) conn.reset() @@ -101,13 +101,13 @@ class ConnectionTests(ConnectingTestCase): self.assert_(not conn.autocommit) self.assertEqual(conn.isolation_level, ext.ISOLATION_LEVEL_DEFAULT) self.assert_(conn.readonly is None) - if self.conn.server_version >= 90100: + if self.conn.info.server_version >= 90100: self.assert_(conn.deferrable is None) def test_notices(self): conn = self.conn cur = conn.cursor() - if self.conn.server_version >= 90300: + if self.conn.info.server_version >= 90300: cur.execute("set client_min_messages=debug1") cur.execute("create temp table chatty (id serial primary key);") self.assertEqual("CREATE TABLE", cur.statusmessage) @@ -116,7 +116,7 @@ class ConnectionTests(ConnectingTestCase): def test_notices_consistent_order(self): conn = self.conn cur = conn.cursor() - if self.conn.server_version >= 90300: + if self.conn.info.server_version >= 90300: cur.execute("set client_min_messages=debug1") cur.execute(""" create temp table table1 (id serial); @@ -136,7 +136,7 @@ class ConnectionTests(ConnectingTestCase): def test_notices_limited(self): conn = self.conn cur = conn.cursor() - if self.conn.server_version >= 90300: + if self.conn.info.server_version >= 90300: cur.execute("set client_min_messages=debug1") for i in range(0, 100, 10): sql = " ".join(["create temp table table%d (id serial);" % j @@ -153,7 +153,7 @@ class ConnectionTests(ConnectingTestCase): conn = self.conn self.conn.notices = deque() cur = conn.cursor() - if self.conn.server_version >= 90300: + if self.conn.info.server_version >= 90300: cur.execute("set client_min_messages=debug1") cur.execute(""" @@ -183,7 +183,7 @@ class ConnectionTests(ConnectingTestCase): conn = self.conn self.conn.notices = None # will make an error swallowes ok cur = conn.cursor() - if self.conn.server_version >= 90300: + if self.conn.info.server_version >= 90300: cur.execute("set client_min_messages=debug1") cur.execute("create temp table table1 (id serial);") @@ -199,7 +199,7 @@ class ConnectionTests(ConnectingTestCase): def test_tpc_unsupported(self): cnn = self.conn - if cnn.server_version >= 80100: + if cnn.info.server_version >= 80100: return self.skipTest("tpc is supported") self.assertRaises(psycopg2.NotSupportedError, @@ -533,7 +533,7 @@ class IsolationLevelsTestCase(ConnectingTestCase): conn.set_isolation_level(level) # the only values available on prehistoric PG versions - if conn.server_version < 80000: + if conn.info.server_version < 80000: if level in ( ext.ISOLATION_LEVEL_READ_UNCOMMITTED, ext.ISOLATION_LEVEL_REPEATABLE_READ): @@ -589,35 +589,35 @@ class IsolationLevelsTestCase(ConnectingTestCase): cur = conn.cursor() self.assertEqual(ext.TRANSACTION_STATUS_IDLE, - conn.get_transaction_status()) + conn.info.transaction_status) cur.execute("insert into isolevel values (10);") self.assertEqual(ext.TRANSACTION_STATUS_INTRANS, - conn.get_transaction_status()) + conn.info.transaction_status) conn.set_isolation_level( psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE) self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_IDLE, - conn.get_transaction_status()) + conn.info.transaction_status) cur.execute("select count(*) from isolevel;") self.assertEqual(0, cur.fetchone()[0]) cur.execute("insert into isolevel values (10);") self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_INTRANS, - conn.get_transaction_status()) + conn.info.transaction_status) conn.set_isolation_level( psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_IDLE, - conn.get_transaction_status()) + conn.info.transaction_status) cur.execute("select count(*) from isolevel;") self.assertEqual(0, cur.fetchone()[0]) cur.execute("insert into isolevel values (10);") self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_IDLE, - conn.get_transaction_status()) + conn.info.transaction_status) conn.set_isolation_level( psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED) self.assertEqual(psycopg2.extensions.TRANSACTION_STATUS_IDLE, - conn.get_transaction_status()) + conn.info.transaction_status) cur.execute("select count(*) from isolevel;") self.assertEqual(1, cur.fetchone()[0]) self.assertEqual(conn.isolation_level, @@ -713,7 +713,7 @@ class IsolationLevelsTestCase(ConnectingTestCase): self.conn.isolation_level = ext.ISOLATION_LEVEL_REPEATABLE_READ cur.execute("SHOW transaction_isolation;") - if self.conn.server_version > 80000: + if self.conn.info.server_version > 80000: self.assertEqual(self.conn.isolation_level, ext.ISOLATION_LEVEL_REPEATABLE_READ) self.assertEqual(cur.fetchone()[0], 'repeatable read') @@ -732,7 +732,7 @@ class IsolationLevelsTestCase(ConnectingTestCase): self.conn.isolation_level = ext.ISOLATION_LEVEL_READ_UNCOMMITTED cur.execute("SHOW transaction_isolation;") - if self.conn.server_version > 80000: + if self.conn.info.server_version > 80000: self.assertEqual(self.conn.isolation_level, ext.ISOLATION_LEVEL_READ_UNCOMMITTED) self.assertEqual(cur.fetchone()[0], 'read uncommitted') @@ -761,7 +761,7 @@ class IsolationLevelsTestCase(ConnectingTestCase): self.conn.isolation_level = "repeatable read" cur.execute("SHOW transaction_isolation;") - if self.conn.server_version > 80000: + if self.conn.info.server_version > 80000: self.assertEqual(self.conn.isolation_level, ext.ISOLATION_LEVEL_REPEATABLE_READ) self.assertEqual(cur.fetchone()[0], 'repeatable read') @@ -780,7 +780,7 @@ class IsolationLevelsTestCase(ConnectingTestCase): self.conn.isolation_level = "read uncommitted" cur.execute("SHOW transaction_isolation;") - if self.conn.server_version > 80000: + if self.conn.info.server_version > 80000: self.assertEqual(self.conn.isolation_level, ext.ISOLATION_LEVEL_READ_UNCOMMITTED) self.assertEqual(cur.fetchone()[0], 'read uncommitted') @@ -1203,7 +1203,7 @@ class TransactionControlTests(ConnectingTestCase): self.conn.set_session( ext.ISOLATION_LEVEL_REPEATABLE_READ) cur.execute("SHOW transaction_isolation;") - if self.conn.server_version > 80000: + if self.conn.info.server_version > 80000: self.assertEqual(cur.fetchone()[0], 'repeatable read') else: self.assertEqual(cur.fetchone()[0], 'serializable') @@ -1218,7 +1218,7 @@ class TransactionControlTests(ConnectingTestCase): self.conn.set_session( isolation_level=ext.ISOLATION_LEVEL_READ_UNCOMMITTED) cur.execute("SHOW transaction_isolation;") - if self.conn.server_version > 80000: + if self.conn.info.server_version > 80000: self.assertEqual(cur.fetchone()[0], 'read uncommitted') else: self.assertEqual(cur.fetchone()[0], 'read committed') @@ -1233,7 +1233,7 @@ class TransactionControlTests(ConnectingTestCase): self.conn.set_session("repeatable read") cur.execute("SHOW transaction_isolation;") - if self.conn.server_version > 80000: + if self.conn.info.server_version > 80000: self.assertEqual(cur.fetchone()[0], 'repeatable read') else: self.assertEqual(cur.fetchone()[0], 'serializable') @@ -1246,7 +1246,7 @@ class TransactionControlTests(ConnectingTestCase): self.conn.set_session("read uncommitted") cur.execute("SHOW transaction_isolation;") - if self.conn.server_version > 80000: + if self.conn.info.server_version > 80000: self.assertEqual(cur.fetchone()[0], 'read uncommitted') else: self.assertEqual(cur.fetchone()[0], 'read committed') @@ -1502,42 +1502,42 @@ class AutocommitTests(ConnectingTestCase): def test_default_no_autocommit(self): self.assert_(not self.conn.autocommit) self.assertEqual(self.conn.status, ext.STATUS_READY) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE) cur = self.conn.cursor() cur.execute('select 1;') self.assertEqual(self.conn.status, ext.STATUS_BEGIN) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, ext.TRANSACTION_STATUS_INTRANS) self.conn.rollback() self.assertEqual(self.conn.status, ext.STATUS_READY) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE) def test_set_autocommit(self): self.conn.autocommit = True self.assert_(self.conn.autocommit) self.assertEqual(self.conn.status, ext.STATUS_READY) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE) cur = self.conn.cursor() cur.execute('select 1;') self.assertEqual(self.conn.status, ext.STATUS_READY) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE) self.conn.autocommit = False self.assert_(not self.conn.autocommit) self.assertEqual(self.conn.status, ext.STATUS_READY) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE) cur.execute('select 1;') self.assertEqual(self.conn.status, ext.STATUS_BEGIN) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, ext.TRANSACTION_STATUS_INTRANS) def test_set_intrans_error(self): @@ -1550,24 +1550,24 @@ class AutocommitTests(ConnectingTestCase): self.conn.set_session(autocommit=True) self.assert_(self.conn.autocommit) self.assertEqual(self.conn.status, ext.STATUS_READY) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE) cur = self.conn.cursor() cur.execute('select 1;') self.assertEqual(self.conn.status, ext.STATUS_READY) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE) self.conn.set_session(autocommit=False) self.assert_(not self.conn.autocommit) self.assertEqual(self.conn.status, ext.STATUS_READY) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE) cur.execute('select 1;') self.assertEqual(self.conn.status, ext.STATUS_BEGIN) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, ext.TRANSACTION_STATUS_INTRANS) self.conn.rollback() @@ -1575,7 +1575,7 @@ class AutocommitTests(ConnectingTestCase): self.assert_(self.conn.autocommit) cur.execute('select 1;') self.assertEqual(self.conn.status, ext.STATUS_READY) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, ext.TRANSACTION_STATUS_IDLE) cur.execute("SHOW transaction_isolation;") self.assertEqual(cur.fetchone()[0], 'serializable') diff --git a/tests/test_cursor.py b/tests/test_cursor.py index d048f3ec..d91a7fc2 100755 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -229,22 +229,22 @@ class CursorTests(ConnectingTestCase): curs.execute("select data from withhold order by data") self.assertEqual(curs.fetchone(), (10,)) self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_BEGIN) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, psycopg2.extensions.TRANSACTION_STATUS_INTRANS) self.conn.commit() self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, psycopg2.extensions.TRANSACTION_STATUS_IDLE) self.assertEqual(curs.fetchone(), (20,)) self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, psycopg2.extensions.TRANSACTION_STATUS_IDLE) curs.close() self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, psycopg2.extensions.TRANSACTION_STATUS_IDLE) def test_withhold_autocommit(self): @@ -256,17 +256,17 @@ class CursorTests(ConnectingTestCase): self.assertEqual(curs.fetchone(), (10,)) self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, psycopg2.extensions.TRANSACTION_STATUS_IDLE) self.conn.commit() self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, psycopg2.extensions.TRANSACTION_STATUS_IDLE) curs.close() self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY) - self.assertEqual(self.conn.get_transaction_status(), + self.assertEqual(self.conn.info.transaction_status, psycopg2.extensions.TRANSACTION_STATUS_IDLE) def test_scrollable(self): diff --git a/tests/test_lobject.py b/tests/test_lobject.py index a9966313..54d676c6 100755 --- a/tests/test_lobject.py +++ b/tests/test_lobject.py @@ -37,7 +37,7 @@ from .testutils import (decorate_all_tests, skip_if_tpc_disabled, def skip_if_no_lo(f): @wraps(f) def skip_if_no_lo_(self): - if self.conn.server_version < 80100: + if self.conn.info.server_version < 80100: return self.skipTest("large objects only supported from PG 8.1") else: return f(self) @@ -403,7 +403,7 @@ decorate_all_tests(LargeObjectTests, skip_if_no_lo, skip_lo_if_green) def skip_if_no_truncate(f): @wraps(f) def skip_if_no_truncate_(self): - if self.conn.server_version < 80300: + if self.conn.info.server_version < 80300: return self.skipTest( "the server doesn't support large object truncate") @@ -459,9 +459,9 @@ decorate_all_tests(LargeObjectTruncateTests, def _has_lo64(conn): """Return (bool, msg) about the lo64 support""" - if conn.server_version < 90300: + if conn.info.server_version < 90300: return (False, "server version %s doesn't support the lo64 API" - % conn.server_version) + % conn.info.server_version) if 'lo64' not in psycopg2.__version__: return False, "this psycopg build doesn't support the lo64 API" diff --git a/tests/test_notify.py b/tests/test_notify.py index bce061e7..b0196e7e 100755 --- a/tests/test_notify.py +++ b/tests/test_notify.py @@ -61,7 +61,7 @@ import %(module)s as psycopg2 import %(module)s.extensions as ext conn = psycopg2.connect(%(dsn)r) conn.set_isolation_level(ext.ISOLATION_LEVEL_AUTOCOMMIT) -print(conn.get_backend_pid()) +print(conn.info.backend_pid) curs = conn.cursor() curs.execute("NOTIFY " %(name)r %(payload)r) curs.close() @@ -147,9 +147,9 @@ conn.close() @slow def test_notify_payload(self): - if self.conn.server_version < 90000: + if self.conn.info.server_version < 90000: return self.skipTest("server version %s doesn't support notify payload" - % self.conn.server_version) + % self.conn.info.server_version) self.autocommit(self.conn) self.listen('foo') pid = int(self.notify('foo', payload="Hello, world!").communicate()[0]) diff --git a/tests/test_quote.py b/tests/test_quote.py index ee723506..c14d80cc 100755 --- a/tests/test_quote.py +++ b/tests/test_quote.py @@ -90,7 +90,7 @@ class QuotingTestCase(ConnectingTestCase): else: res = curs.fetchone()[0].tobytes() - if res[0] in (b'x', ord(b'x')) and self.conn.server_version >= 90000: + if res[0] in (b'x', ord(b'x')) and self.conn.info.server_version >= 90000: return self.skipTest( "bytea broken with server >= 9.0, libpq < 9") diff --git a/tests/test_types_extras.py b/tests/test_types_extras.py index ca31c05f..18ac6eaa 100755 --- a/tests/test_types_extras.py +++ b/tests/test_types_extras.py @@ -137,7 +137,7 @@ def skip_if_no_hstore(f): class HstoreTestCase(ConnectingTestCase): def test_adapt_8(self): - if self.conn.server_version >= 90000: + if self.conn.info.server_version >= 90000: return self.skipTest("skipping dict adaptation with PG pre-9 syntax") from psycopg2.extras import HstoreAdapter @@ -163,7 +163,7 @@ class HstoreTestCase(ConnectingTestCase): self.assertQuotedEqual(ii[3], b"('d' => '" + encc + b"')") def test_adapt_9(self): - if self.conn.server_version < 90000: + if self.conn.info.server_version < 90000: return self.skipTest("skipping dict adaptation with PG 9 syntax") from psycopg2.extras import HstoreAdapter @@ -448,10 +448,10 @@ class HstoreTestCase(ConnectingTestCase): def skip_if_no_composite(f): @wraps(f) def skip_if_no_composite_(self): - if self.conn.server_version < 80000: + if self.conn.info.server_version < 80000: return self.skipTest( "server version %s doesn't support composite types" - % self.conn.server_version) + % self.conn.info.server_version) return f(self) @@ -1436,10 +1436,10 @@ class RangeTestCase(unittest.TestCase): def skip_if_no_range(f): @wraps(f) def skip_if_no_range_(self): - if self.conn.server_version < 90200: + if self.conn.info.server_version < 90200: return self.skipTest( "server version %s doesn't support range types" - % self.conn.server_version) + % self.conn.info.server_version) return f(self) diff --git a/tests/testutils.py b/tests/testutils.py index 11138f0e..98753819 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -234,9 +234,9 @@ def skip_before_postgres(*ver): def skip_before_postgres_(f): @wraps(f) def skip_before_postgres__(self): - if self.conn.server_version < int("%d%02d%02d" % ver): + if self.conn.info.server_version < int("%d%02d%02d" % ver): return self.skipTest("skipped because PostgreSQL %s" - % self.conn.server_version) + % self.conn.info.server_version) else: return f(self) @@ -251,9 +251,9 @@ def skip_after_postgres(*ver): def skip_after_postgres_(f): @wraps(f) def skip_after_postgres__(self): - if self.conn.server_version >= int("%d%02d%02d" % ver): + if self.conn.info.server_version >= int("%d%02d%02d" % ver): return self.skipTest("skipped because PostgreSQL %s" - % self.conn.server_version) + % self.conn.info.server_version) else: return f(self) From c567556d7194cb5bed71a2f8df5370c9854a4574 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sat, 13 Oct 2018 03:32:56 +0100 Subject: [PATCH 12/12] Fixed NEWS file to mention the connection.info object --- NEWS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 21db9dd8..0c6b14fb 100644 --- a/NEWS +++ b/NEWS @@ -10,7 +10,8 @@ New features: - Added `~psycopg2.extensions.Column.table_oid` and `~psycopg2.extensions.Column.table_column` attributes on `cursor.description` items (:ticket:`#661`). -- Added `connection.host` property (:ticket:`#726`). +- Added `connection.info` object to retrieve various PostgreSQL connection + information (:ticket:`#726`). - `~psycopg2.sql.Identifier` can represent qualified names in SQL composition (:ticket:`#732`). - `!str()` on `~psycopg2.extras.Range` produces a human-readable representation