summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MANIFEST.in6
-rw-r--r--Makefile9
-rw-r--r--NEWS13
-rw-r--r--doc/src/advanced.rst2
-rw-r--r--doc/src/conf.py4
-rw-r--r--doc/src/connection.rst23
-rw-r--r--doc/src/cursor.rst5
-rw-r--r--doc/src/install.rst24
-rw-r--r--doc/src/usage.rst10
-rw-r--r--lib/errorcodes.py4
-rw-r--r--psycopg/adapter_datetime.c2
-rw-r--r--psycopg/adapter_qstring.c80
-rw-r--r--psycopg/adapter_qstring.h3
-rw-r--r--psycopg/connection_type.c35
-rw-r--r--psycopg/cursor_type.c2
-rw-r--r--psycopg/lobject.h4
-rw-r--r--psycopg/lobject_int.c22
-rw-r--r--psycopg/lobject_type.c30
-rw-r--r--psycopg/pqpath.c14
-rw-r--r--psycopg/psycopg.h3
-rw-r--r--psycopg/psycopgmodule.c22
-rw-r--r--psycopg/utils.c29
-rwxr-xr-xscripts/make_errorcodes.py2
-rw-r--r--setup.py13
-rwxr-xr-xtests/test_connection.py7
-rwxr-xr-xtests/test_quote.py70
-rwxr-xr-xtests/test_types_basic.py16
-rw-r--r--tests/testutils.py2
28 files changed, 336 insertions, 120 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
index 00e4fc3..0d34fd3 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,10 +2,10 @@ recursive-include psycopg *.c *.h *.manifest
recursive-include lib *.py
recursive-include tests *.py
recursive-include examples *.py somehackers.jpg whereareyou.jpg
-recursive-include doc README SUCCESS COPYING.LESSER pep-0249.txt
-recursive-include doc Makefile requirements.txt
+include doc/README.rst doc/SUCCESS doc/COPYING.LESSER doc/pep-0249.txt
+include doc/Makefile doc/requirements.txt
recursive-include doc/src *.rst *.py *.css Makefile
recursive-include scripts *.py *.sh
include scripts/maketypes.sh scripts/buildtypes.py
include AUTHORS README.rst INSTALL LICENSE NEWS
-include PKG-INFO MANIFEST.in MANIFEST setup.py setup.cfg Makefile
+include MANIFEST.in setup.py setup.cfg Makefile
diff --git a/Makefile b/Makefile
index 232f0d0..a8f491e 100644
--- a/Makefile
+++ b/Makefile
@@ -92,14 +92,9 @@ $(PACKAGE)/tests/%.py: tests/%.py
$(PYTHON) setup.py build_py $(BUILD_OPT)
touch $@
-$(SDIST): MANIFEST $(SOURCE)
+$(SDIST): $(SOURCE)
$(PYTHON) setup.py sdist $(SDIST_OPT)
-MANIFEST: MANIFEST.in $(SOURCE)
- # Run twice as MANIFEST.in includes MANIFEST
- $(PYTHON) setup.py sdist --manifest-only
- $(PYTHON) setup.py sdist --manifest-only
-
# docs depend on the build as it partly use introspection.
doc/html/genindex.html: $(PLATLIB) $(PURELIB) $(SOURCE_DOC)
$(MAKE) -C doc html
@@ -111,5 +106,5 @@ doc/docs.zip: doc/html/genindex.html
(cd doc/html && zip -r ../docs.zip *)
clean:
- rm -rf build MANIFEST
+ rm -rf build
$(MAKE) -C doc clean
diff --git a/NEWS b/NEWS
index 9467fea..49ed56a 100644
--- a/NEWS
+++ b/NEWS
@@ -18,19 +18,28 @@ New features:
customized replacing them with any object exposing an `!append()` method
(:ticket:`#326`).
- Added `~psycopg2.extensions.quote_ident()` function (:ticket:`#359`).
+- Added `~connection.get_dsn_parameters()` connection method (:ticket:`#364`).
What's new in psycopg 2.6.2
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Report the server response status on errors (such as :ticket:`#281`).
+- Raise `!NotSupportedError` on unhandled server response status
+ (:ticket:`#352`).
+- Allow overriding string adapter encoding with no connection (:ticket:`#331`).
- The `~psycopg2.extras.wait_select` callback allows interrupting a
long-running query in an interactive shell using :kbd:`Ctrl-C`
(:ticket:`#333`).
-- Raise `!NotSupportedError` on unhandled server response status
- (:ticket:`#352`).
- Fixed `!PersistentConnectionPool` on Python 3 (:ticket:`#348`).
+- Fixed segfault on `repr()` of an unitialized connection (:ticket:`#361`).
+- Allow adapting bytes using QuotedString on Python 3 too (:ticket:`#365`).
+- Added support for setuptools/wheel (:ticket:`#370`).
+- Fix build on Windows with Python 3.5, VS 2015 (:ticket:`#380`).
- Fixed `!errorcodes.lookup` initialization thread-safety (:ticket:`#382`).
+- Fixed `!read()` exception propagation in copy_from (:ticket:`#412`).
+- Fixed possible NULL TZ decref (:ticket:`#424`).
+- `~psycopg2.errorcodes` map updated to PostgreSQL 9.5.
What's new in psycopg 2.6.1
diff --git a/doc/src/advanced.rst b/doc/src/advanced.rst
index e63fcff..f2e279f 100644
--- a/doc/src/advanced.rst
+++ b/doc/src/advanced.rst
@@ -47,7 +47,7 @@ it is the class where query building, execution and result type-casting into
Python variables happens.
The `~psycopg2.extras` module contains several examples of :ref:`connection
-and cursor sublcasses <cursor-subclasses>`.
+and cursor subclasses <cursor-subclasses>`.
.. note::
diff --git a/doc/src/conf.py b/doc/src/conf.py
index 18b81e0..94ffa34 100644
--- a/doc/src/conf.py
+++ b/doc/src/conf.py
@@ -42,9 +42,7 @@ master_doc = 'index'
# General information about the project.
project = u'Psycopg'
-from datetime import date
-year = date.today().year
-copyright = u'2001-%s, Federico Di Gregorio, Daniele Varrazzo' % year
+copyright = u'2001-2016, Federico Di Gregorio, Daniele Varrazzo'
# 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/doc/src/connection.rst b/doc/src/connection.rst
index cceef1e..c99c8bd 100644
--- a/doc/src/connection.rst
+++ b/doc/src/connection.rst
@@ -569,6 +569,29 @@ The ``connection`` class
.. index::
+ pair: Connection; Parameters
+
+ .. method:: get_dsn_parameters()
+
+ Get the effective dsn parameters for the connection as a dictionary.
+
+ The *password* parameter is removed from the result.
+
+ Example::
+
+ >>> conn.get_dsn_parameters()
+ {'dbname': 'test', 'user': 'postgres', 'port': '5432', 'sslmode': 'prefer'}
+
+ Requires libpq >= 9.3.
+
+ .. seealso:: libpq docs for `PQconninfo()`__ for details.
+
+ .. __: http://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-PQCONNINFO
+
+ .. versionadded:: 2.7
+
+
+ .. index::
pair: Transaction; Status
.. method:: get_transaction_status()
diff --git a/doc/src/cursor.rst b/doc/src/cursor.rst
index 73bb537..45e6278 100644
--- a/doc/src/cursor.rst
+++ b/doc/src/cursor.rst
@@ -494,6 +494,9 @@ The ``cursor`` class
.. rubric:: COPY-related methods
+ Efficiently copy data from file-like objects to the database and back. See
+ :ref:`copy` for an overview.
+
.. extension::
The :sql:`COPY` command is a PostgreSQL extension to the SQL standard.
@@ -502,7 +505,7 @@ The ``cursor`` class
.. method:: copy_from(file, table, sep='\\t', null='\\\\N', size=8192, columns=None)
Read data *from* the file-like object *file* appending them to
- the table named *table*. See :ref:`copy` for an overview.
+ the table named *table*.
:param file: file-like object to read data from. It must have both
`!read()` and `!readline()` methods.
diff --git a/doc/src/install.rst b/doc/src/install.rst
index ec1eeea..3a95adc 100644
--- a/doc/src/install.rst
+++ b/doc/src/install.rst
@@ -18,7 +18,7 @@ The current `!psycopg2` implementation supports:
NOTE: keep consistent with setup.py and the /features/ page.
- Python 2 versions from 2.5 to 2.7
-- Python 3 versions from 3.1 to 3.4
+- Python 3 versions from 3.1 to 3.5
- PostgreSQL versions from 7.4 to 9.4
.. _PostgreSQL: http://www.postgresql.org/
@@ -51,6 +51,16 @@ extension packages, *above all if you are a Windows or a Mac OS user*, please
use a pre-compiled package and go straight to the :ref:`module usage <usage>`
avoid bothering with the gory details.
+.. note::
+
+ Regardless of the way `!psycopg2` is installed, at runtime it will need to
+ use the libpq_ library. `!psycopg2` relies on the host OS to find the
+ library file (usually ``libpq.so`` or ``libpq.dll``): if the library is
+ installed in a standard location there is usually no problem; if the
+ library is in a non-standard location you will have to tell somehow
+ psycopg how to find it, which is OS-dependent (for instance setting a
+ suitable :envvar:`LD_LIBRARY_PATH` on Linux).
+
.. _install-from-package:
@@ -95,7 +105,17 @@ Install from a package
pair: Install; Windows
**Microsoft Windows**
- Jason Erickson maintains a packaged `Windows port of Psycopg`__ with
+ There are two options to install a precompiled `psycopg2` package under windows:
+
+ **Option 1:** Using `pip`__ (Included in python 2.7.9+ and python 3.4+)
+ and a binary wheel package. Launch windows' command prompt (`cmd.exe`)
+ and execute the following command::
+
+ pip install psycopg2
+
+ .. __: https://pip.pypa.io/en/stable/installing/
+
+ **Option 2:** Jason Erickson maintains a packaged `Windows port of Psycopg`__ with
installation executable. Download. Double click. Done.
.. __: http://www.stickpeople.com/projects/python/win-psycopg/
diff --git a/doc/src/usage.rst b/doc/src/usage.rst
index 9dd31df..3b42aeb 100644
--- a/doc/src/usage.rst
+++ b/doc/src/usage.rst
@@ -864,11 +864,19 @@ Using COPY TO and COPY FROM
Psycopg `cursor` objects provide an interface to the efficient
PostgreSQL |COPY|__ command to move data from files to tables and back.
+
+Currently no adaptation is provided between Python and PostgreSQL types on
+|COPY|: the file can be any Python file-like object but its format must be in
+the format accepted by `PostgreSQL COPY command`__ (data fromat, escaped
+characters, etc).
+
+.. __: COPY_
+
The methods exposed are:
`~cursor.copy_from()`
Reads data *from* a file-like object appending them to a database table
- (:sql:`COPY table FROM file` syntax). The source file must have both
+ (:sql:`COPY table FROM file` syntax). The source file must provide both
`!read()` and `!readline()` method.
`~cursor.copy_to()`
diff --git a/lib/errorcodes.py b/lib/errorcodes.py
index aa5a723..60181c1 100644
--- a/lib/errorcodes.py
+++ b/lib/errorcodes.py
@@ -199,6 +199,8 @@ INVALID_ESCAPE_SEQUENCE = '22025'
STRING_DATA_LENGTH_MISMATCH = '22026'
TRIM_ERROR = '22027'
ARRAY_SUBSCRIPT_ERROR = '2202E'
+INVALID_TABLESAMPLE_REPEAT = '2202G'
+INVALID_TABLESAMPLE_ARGUMENT = '2202H'
FLOATING_POINT_EXCEPTION = '22P01'
INVALID_TEXT_REPRESENTATION = '22P02'
INVALID_BINARY_REPRESENTATION = '22P03'
@@ -271,6 +273,7 @@ INVALID_SQLSTATE_RETURNED = '39001'
NULL_VALUE_NOT_ALLOWED = '39004'
TRIGGER_PROTOCOL_VIOLATED = '39P01'
SRF_PROTOCOL_VIOLATED = '39P02'
+EVENT_TRIGGER_PROTOCOL_VIOLATED = '39P03'
# Class 3B - Savepoint Exception
SAVEPOINT_EXCEPTION = '3B000'
@@ -408,6 +411,7 @@ PLPGSQL_ERROR = 'P0000'
RAISE_EXCEPTION = 'P0001'
NO_DATA_FOUND = 'P0002'
TOO_MANY_ROWS = 'P0003'
+ASSERT_FAILURE = 'P0004'
# Class XX - Internal Error
INTERNAL_ERROR = 'XX000'
diff --git a/psycopg/adapter_datetime.c b/psycopg/adapter_datetime.c
index 0571837..9d04df4 100644
--- a/psycopg/adapter_datetime.c
+++ b/psycopg/adapter_datetime.c
@@ -451,7 +451,7 @@ psyco_TimestampFromTicks(PyObject *self, PyObject *args)
tz);
exit:
- Py_DECREF(tz);
+ Py_XDECREF(tz);
Py_XDECREF(m);
return res;
}
diff --git a/psycopg/adapter_qstring.c b/psycopg/adapter_qstring.c
index 2e3ab0a..8c5a8f1 100644
--- a/psycopg/adapter_qstring.c
+++ b/psycopg/adapter_qstring.c
@@ -36,44 +36,56 @@ static const char *default_encoding = "latin1";
/* qstring_quote - do the quote process on plain and unicode strings */
+const char *
+_qstring_get_encoding(qstringObject *self)
+{
+ /* if the wrapped object is an unicode object we can encode it to match
+ conn->encoding but if the encoding is not specified we don't know what
+ to do and we raise an exception */
+ if (self->conn) {
+ return self->conn->codec;
+ }
+ else {
+ return self->encoding ? self->encoding : default_encoding;
+ }
+}
+
static PyObject *
qstring_quote(qstringObject *self)
{
PyObject *str = NULL;
char *s, *buffer = NULL;
Py_ssize_t len, qlen;
- const char *encoding = default_encoding;
+ const char *encoding;
PyObject *rv = NULL;
- /* if the wrapped object is an unicode object we can encode it to match
- conn->encoding but if the encoding is not specified we don't know what
- to do and we raise an exception */
- if (self->conn) {
- encoding = self->conn->codec;
- }
-
+ encoding = _qstring_get_encoding(self);
Dprintf("qstring_quote: encoding to %s", encoding);
- if (PyUnicode_Check(self->wrapped) && encoding) {
- str = PyUnicode_AsEncodedString(self->wrapped, encoding, NULL);
- Dprintf("qstring_quote: got encoded object at %p", str);
- if (str == NULL) goto exit;
+ if (PyUnicode_Check(self->wrapped)) {
+ if (encoding) {
+ str = PyUnicode_AsEncodedString(self->wrapped, encoding, NULL);
+ Dprintf("qstring_quote: got encoded object at %p", str);
+ if (str == NULL) goto exit;
+ }
+ else {
+ PyErr_SetString(PyExc_TypeError,
+ "missing encoding to encode unicode object");
+ goto exit;
+ }
}
-#if PY_MAJOR_VERSION < 3
- /* if the wrapped object is a simple string, we don't know how to
+ /* if the wrapped object is a binary string, we don't know how to
(re)encode it, so we pass it as-is */
- else if (PyString_Check(self->wrapped)) {
+ else if (Bytes_Check(self->wrapped)) {
str = self->wrapped;
/* INCREF to make it ref-wise identical to unicode one */
Py_INCREF(str);
}
-#endif
/* if the wrapped object is not a string, this is an error */
else {
- PyErr_SetString(PyExc_TypeError,
- "can't quote non-string object (or missing encoding)");
+ PyErr_SetString(PyExc_TypeError, "can't quote non-string object");
goto exit;
}
@@ -150,13 +162,32 @@ qstring_conform(qstringObject *self, PyObject *args)
static PyObject *
qstring_get_encoding(qstringObject *self)
{
- const char *encoding = default_encoding;
+ const char *encoding;
+ encoding = _qstring_get_encoding(self);
+ return Text_FromUTF8(encoding);
+}
- if (self->conn) {
- encoding = self->conn->codec;
- }
+static int
+qstring_set_encoding(qstringObject *self, PyObject *pyenc)
+{
+ int rv = -1;
+ const char *tmp;
+ char *cenc;
- return Text_FromUTF8(encoding);
+ /* get a C copy of the encoding (which may come from unicode) */
+ Py_INCREF(pyenc);
+ if (!(pyenc = psycopg_ensure_bytes(pyenc))) { goto exit; }
+ if (!(tmp = Bytes_AsString(pyenc))) { goto exit; }
+ if (0 > psycopg_strdup(&cenc, tmp, 0)) { goto exit; }
+
+ Dprintf("qstring_set_encoding: encoding set to %s", cenc);
+ PyMem_Free((void *)self->encoding);
+ self->encoding = cenc;
+ rv = 0;
+
+exit:
+ Py_XDECREF(pyenc);
+ return rv;
}
/** the QuotedString object **/
@@ -183,7 +214,7 @@ static PyMethodDef qstringObject_methods[] = {
static PyGetSetDef qstringObject_getsets[] = {
{ "encoding",
(getter)qstring_get_encoding,
- (setter)NULL,
+ (setter)qstring_set_encoding,
"current encoding of the adapter" },
{NULL}
};
@@ -216,6 +247,7 @@ qstring_dealloc(PyObject* obj)
Py_CLEAR(self->wrapped);
Py_CLEAR(self->buffer);
Py_CLEAR(self->conn);
+ PyMem_Free((void *)self->encoding);
Dprintf("qstring_dealloc: deleted qstring object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
diff --git a/psycopg/adapter_qstring.h b/psycopg/adapter_qstring.h
index b7b086f..8abdc5f 100644
--- a/psycopg/adapter_qstring.h
+++ b/psycopg/adapter_qstring.h
@@ -39,6 +39,9 @@ typedef struct {
PyObject *buffer;
connectionObject *conn;
+
+ const char *encoding;
+
} qstringObject;
#ifdef __cplusplus
diff --git a/psycopg/connection_type.c b/psycopg/connection_type.c
index 2c1dddf..485a92b 100644
--- a/psycopg/connection_type.c
+++ b/psycopg/connection_type.c
@@ -733,6 +733,37 @@ psyco_conn_get_parameter_status(connectionObject *self, PyObject *args)
return conn_text_from_chars(self, val);
}
+/* get_dsn_parameters method - Get connection parameters */
+
+#define psyco_conn_get_dsn_parameters_doc \
+"get_dsn_parameters() -- Get effective connection parameters.\n\n"
+
+static PyObject *
+psyco_conn_get_dsn_parameters(connectionObject *self)
+{
+#if PG_VERSION_NUM >= 90300
+ PyObject *res = NULL;
+ PQconninfoOption *options = NULL;
+
+ EXC_IF_CONN_CLOSED(self);
+
+ if (!(options = PQconninfo(self->pgconn))) {
+ PyErr_NoMemory();
+ goto exit;
+ }
+
+ res = psycopg_dict_from_conninfo_options(options, /* include_password = */ 0);
+
+exit:
+ PQconninfoFree(options);
+
+ return res;
+#else
+ PyErr_SetString(NotSupportedError, "PQconninfo not available in libpq < 9.3");
+ return NULL;
+#endif
+}
+
/* lobject method - allocate a new lobject */
@@ -977,6 +1008,8 @@ static struct PyMethodDef connectionObject_methods[] = {
METH_NOARGS, psyco_conn_get_transaction_status_doc},
{"get_parameter_status", (PyCFunction)psyco_conn_get_parameter_status,
METH_VARARGS, psyco_conn_get_parameter_status_doc},
+ {"get_dsn_parameters", (PyCFunction)psyco_conn_get_dsn_parameters,
+ METH_NOARGS, psyco_conn_get_dsn_parameters_doc},
{"get_backend_pid", (PyCFunction)psyco_conn_get_backend_pid,
METH_NOARGS, psyco_conn_get_backend_pid_doc},
{"lobject", (PyCFunction)psyco_conn_lobject,
@@ -1171,7 +1204,7 @@ connection_repr(connectionObject *self)
{
return PyString_FromFormat(
"<connection object at %p; dsn: '%s', closed: %ld>",
- self, self->dsn, self->closed);
+ self, (self->dsn ? self->dsn : "<unintialized>"), self->closed);
}
static int
diff --git a/psycopg/cursor_type.c b/psycopg/cursor_type.c
index cd8d5ca..fe79bbf 100644
--- a/psycopg/cursor_type.c
+++ b/psycopg/cursor_type.c
@@ -335,7 +335,7 @@ _psyco_curs_merge_query_args(cursorObject *self,
PyErr_Fetch(&err, &arg, &trace);
if (err && PyErr_GivenExceptionMatches(err, PyExc_TypeError)) {
- Dprintf("psyco_curs_execute: TypeError exception catched");
+ Dprintf("psyco_curs_execute: TypeError exception caught");
PyErr_NormalizeException(&err, &arg, &trace);
if (PyObject_HasAttrString(arg, "args")) {
diff --git a/psycopg/lobject.h b/psycopg/lobject.h
index b9c8c3d..73cf619 100644
--- a/psycopg/lobject.h
+++ b/psycopg/lobject.h
@@ -60,8 +60,8 @@ RAISES_NEG HIDDEN int lobject_export(lobjectObject *self, const char *filename);
RAISES_NEG HIDDEN Py_ssize_t lobject_read(lobjectObject *self, char *buf, size_t len);
RAISES_NEG HIDDEN Py_ssize_t lobject_write(lobjectObject *self, const char *buf,
size_t len);
-RAISES_NEG HIDDEN long lobject_seek(lobjectObject *self, long pos, int whence);
-RAISES_NEG HIDDEN long lobject_tell(lobjectObject *self);
+RAISES_NEG HIDDEN Py_ssize_t lobject_seek(lobjectObject *self, Py_ssize_t pos, int whence);
+RAISES_NEG HIDDEN Py_ssize_t lobject_tell(lobjectObject *self);
RAISES_NEG HIDDEN int lobject_truncate(lobjectObject *self, size_t len);
RAISES_NEG HIDDEN int lobject_close(lobjectObject *self);
diff --git a/psycopg/lobject_int.c b/psycopg/lobject_int.c
index 8788c10..279ef1e 100644
--- a/psycopg/lobject_int.c
+++ b/psycopg/lobject_int.c
@@ -376,12 +376,12 @@ lobject_read(lobjectObject *self, char *buf, size_t len)
/* lobject_seek - move the current position in the lo */
-RAISES_NEG long
-lobject_seek(lobjectObject *self, long pos, int whence)
+RAISES_NEG Py_ssize_t
+lobject_seek(lobjectObject *self, Py_ssize_t pos, int whence)
{
PGresult *pgres = NULL;
char *error = NULL;
- long where;
+ Py_ssize_t where;
Dprintf("lobject_seek: fd = %d, pos = %ld, whence = %d",
self->fd, pos, whence);
@@ -391,12 +391,12 @@ lobject_seek(lobjectObject *self, long pos, int whence)
#ifdef HAVE_LO64
if (self->conn->server_version < 90300) {
- where = (long)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
+ where = (Py_ssize_t)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
} else {
- where = lo_lseek64(self->conn->pgconn, self->fd, pos, whence);
+ where = (Py_ssize_t)lo_lseek64(self->conn->pgconn, self->fd, pos, whence);
}
#else
- where = (long)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
+ where = (Py_ssize_t)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
#endif
Dprintf("lobject_seek: where = %ld", where);
if (where < 0)
@@ -412,12 +412,12 @@ lobject_seek(lobjectObject *self, long pos, int whence)
/* lobject_tell - tell the current position in the lo */
-RAISES_NEG long
+RAISES_NEG Py_ssize_t
lobject_tell(lobjectObject *self)
{
PGresult *pgres = NULL;
char *error = NULL;
- long where;
+ Py_ssize_t where;
Dprintf("lobject_tell: fd = %d", self->fd);
@@ -426,12 +426,12 @@ lobject_tell(lobjectObject *self)
#ifdef HAVE_LO64
if (self->conn->server_version < 90300) {
- where = (long)lo_tell(self->conn->pgconn, self->fd);
+ where = (Py_ssize_t)lo_tell(self->conn->pgconn, self->fd);
} else {
- where = lo_tell64(self->conn->pgconn, self->fd);
+ where = (Py_ssize_t)lo_tell64(self->conn->pgconn, self->fd);
}
#else
- where = (long)lo_tell(self->conn->pgconn, self->fd);
+ where = (Py_ssize_t)lo_tell(self->conn->pgconn, self->fd);
#endif
Dprintf("lobject_tell: where = %ld", where);
if (where < 0)
diff --git a/psycopg/lobject_type.c b/psycopg/lobject_type.c
index a43325d..d15eb20 100644
--- a/psycopg/lobject_type.c
+++ b/psycopg/lobject_type.c
@@ -105,7 +105,7 @@ psyco_lobj_write(lobjectObject *self, PyObject *args)
goto exit;
}
- rv = PyInt_FromLong((long)res);
+ rv = PyInt_FromSsize_t((Py_ssize_t)res);
exit:
Py_XDECREF(data);
@@ -121,7 +121,7 @@ static PyObject *
psyco_lobj_read(lobjectObject *self, PyObject *args)
{
PyObject *res;
- long where, end;
+ Py_ssize_t where, end;
Py_ssize_t size = -1;
char *buffer;
@@ -165,10 +165,10 @@ psyco_lobj_read(lobjectObject *self, PyObject *args)
static PyObject *
psyco_lobj_seek(lobjectObject *self, PyObject *args)
{
- long offset, pos=0;
+ Py_ssize_t offset, pos=0;
int whence=0;
- if (!PyArg_ParseTuple(args, "l|i", &offset, &whence))
+ if (!PyArg_ParseTuple(args, "n|i", &offset, &whence))
return NULL;
EXC_IF_LOBJ_CLOSED(self);
@@ -187,8 +187,8 @@ psyco_lobj_seek(lobjectObject *self, PyObject *args)
#else
if (offset < INT_MIN || offset > INT_MAX) {
PyErr_Format(InterfaceError,
- "offset out of range (%ld): this psycopg version was not built "
- "with lobject 64 API support",
+ "offset out of range (" FORMAT_CODE_PY_SSIZE_T "): "
+ "this psycopg version was not built with lobject 64 API support",
offset);
return NULL;
}
@@ -197,7 +197,7 @@ psyco_lobj_seek(lobjectObject *self, PyObject *args)
if ((pos = lobject_seek(self, offset, whence)) < 0)
return NULL;
- return PyLong_FromLong(pos);
+ return PyInt_FromSsize_t(pos);
}
/* tell method - tell current position in the lobject */
@@ -208,7 +208,7 @@ psyco_lobj_seek(lobjectObject *self, PyObject *args)
static PyObject *
psyco_lobj_tell(lobjectObject *self, PyObject *args)
{
- long pos;
+ Py_ssize_t pos;
EXC_IF_LOBJ_CLOSED(self);
EXC_IF_LOBJ_LEVEL0(self);
@@ -217,7 +217,7 @@ psyco_lobj_tell(lobjectObject *self, PyObject *args)
if ((pos = lobject_tell(self)) < 0)
return NULL;
- return PyLong_FromLong(pos);
+ return PyInt_FromSsize_t(pos);
}
/* unlink method - unlink (destroy) the lobject */
@@ -274,9 +274,9 @@ psyco_lobj_get_closed(lobjectObject *self, void *closure)
static PyObject *
psyco_lobj_truncate(lobjectObject *self, PyObject *args)
{
- long len = 0;
+ Py_ssize_t len = 0;
- if (!PyArg_ParseTuple(args, "|l", &len))
+ if (!PyArg_ParseTuple(args, "|n", &len))
return NULL;
EXC_IF_LOBJ_CLOSED(self);
@@ -286,16 +286,16 @@ psyco_lobj_truncate(lobjectObject *self, PyObject *args)
#ifdef HAVE_LO64
if (len > INT_MAX && self->conn->server_version < 90300) {
PyErr_Format(NotSupportedError,
- "len out of range (%ld): server version %d "
- "does not support the lobject 64 API",
+ "len out of range (" FORMAT_CODE_PY_SSIZE_T "): "
+ "server version %d does not support the lobject 64 API",
len, self->conn->server_version);
return NULL;
}
#else
if (len > INT_MAX) {
PyErr_Format(InterfaceError,
- "len out of range (%ld): this psycopg version was not built "
- "with lobject 64 API support",
+ "len out of range (" FORMAT_CODE_PY_SSIZE_T "): "
+ "this psycopg version was not built with lobject 64 API support",
len);
return NULL;
}
diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c
index 6d6728c..220ae24 100644
--- a/psycopg/pqpath.c
+++ b/psycopg/pqpath.c
@@ -170,11 +170,11 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult **pgres)
if (conn == NULL) {
PyErr_SetString(DatabaseError,
- "psycopg went psycotic and raised a null error");
+ "psycopg went psychotic and raised a null error");
return;
}
- /* if the connection has somehow beed broken, we mark the connection
+ /* if the connection has somehow been broken, we mark the connection
object as closed but requiring cleanup */
if (conn->pgconn != NULL && PQstatus(conn->pgconn) == CONNECTION_BAD)
conn->closed = 2;
@@ -916,7 +916,7 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result, int
PyErr_SetString(OperationalError, PQerrorMessage(curs->conn->pgconn));
return -1;
}
- Dprintf("curs_execute: pg connection at %p OK", curs->conn->pgconn);
+ Dprintf("pq_execute: pg connection at %p OK", curs->conn->pgconn);
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(curs->conn->lock));
@@ -941,7 +941,7 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result, int
Py_UNBLOCK_THREADS;
}
- /* dont let pgres = NULL go to pq_fetch() */
+ /* don't let pgres = NULL go to pq_fetch() */
if (curs->pgres == NULL) {
pthread_mutex_unlock(&(curs->conn->lock));
Py_BLOCK_THREADS;
@@ -1409,7 +1409,11 @@ _pq_copy_in_v3(cursorObject *curs)
Py_DECREF(str);
}
}
- PyErr_Restore(t, ex, tb);
+ /* Clear the Py exception: it will be re-raised from the libpq */
+ Py_XDECREF(t);
+ Py_XDECREF(ex);
+ Py_XDECREF(tb);
+ PyErr_Clear();
}
res = PQputCopyEnd(curs->conn->pgconn, buf);
}
diff --git a/psycopg/psycopg.h b/psycopg/psycopg.h
index adda12d..3174f30 100644
--- a/psycopg/psycopg.h
+++ b/psycopg/psycopg.h
@@ -132,6 +132,9 @@ STEALS(1) HIDDEN PyObject * psycopg_ensure_bytes(PyObject *obj);
STEALS(1) HIDDEN PyObject * psycopg_ensure_text(PyObject *obj);
+HIDDEN PyObject *psycopg_dict_from_conninfo_options(PQconninfoOption *options,
+ int include_password);
+
/* Exceptions docstrings */
#define Error_doc \
"Base class for error exceptions."
diff --git a/psycopg/psycopgmodule.c b/psycopg/psycopgmodule.c
index c08cd70..d4a4c94 100644
--- a/psycopg/psycopgmodule.c
+++ b/psycopg/psycopgmodule.c
@@ -123,8 +123,8 @@ static PyObject *
psyco_parse_dsn(PyObject *self, PyObject *args, PyObject *kwargs)
{
char *err = NULL;
- PQconninfoOption *options = NULL, *o;
- PyObject *dict = NULL, *res = NULL, *dsn;
+ PQconninfoOption *options = NULL;
+ PyObject *res = NULL, *dsn;
static char *kwlist[] = {"dsn", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &dsn)) {
@@ -145,26 +145,10 @@ psyco_parse_dsn(PyObject *self, PyObject *args, PyObject *kwargs)
goto exit;
}
- if (!(dict = PyDict_New())) { goto exit; }
- for (o = options; o->keyword != NULL; o++) {
- if (o->val != NULL) {
- PyObject *value;
- if (!(value = Text_FromUTF8(o->val))) { goto exit; }
- if (PyDict_SetItemString(dict, o->keyword, value) != 0) {
- Py_DECREF(value);
- goto exit;
- }
- Py_DECREF(value);
- }
- }
-
- /* success */
- res = dict;
- dict = NULL;
+ res = psycopg_dict_from_conninfo_options(options, /* include_password = */ 1);
exit:
PQconninfoFree(options); /* safe on null */
- Py_XDECREF(dict);
Py_XDECREF(dsn);
return res;
diff --git a/psycopg/utils.c b/psycopg/utils.c
index ec8e47c..1b10c4a 100644
--- a/psycopg/utils.c
+++ b/psycopg/utils.c
@@ -247,3 +247,32 @@ psycopg_is_text_file(PyObject *f)
}
}
+/* Make a dict out of PQconninfoOption array */
+PyObject *
+psycopg_dict_from_conninfo_options(PQconninfoOption *options, int include_password)
+{
+ PyObject *dict, *res = NULL;
+ PQconninfoOption *o;
+
+ if (!(dict = PyDict_New())) { goto exit; }
+ for (o = options; o->keyword != NULL; o++) {
+ if (o->val != NULL &&
+ (include_password || strcmp(o->keyword, "password") != 0)) {
+ PyObject *value;
+ if (!(value = Text_FromUTF8(o->val))) { goto exit; }
+ if (PyDict_SetItemString(dict, o->keyword, value) != 0) {
+ Py_DECREF(value);
+ goto exit;
+ }
+ Py_DECREF(value);
+ }
+ }
+
+ res = dict;
+ dict = NULL;
+
+exit:
+ Py_XDECREF(dict);
+
+ return res;
+}
diff --git a/scripts/make_errorcodes.py b/scripts/make_errorcodes.py
index 122e0d5..58d05b8 100755
--- a/scripts/make_errorcodes.py
+++ b/scripts/make_errorcodes.py
@@ -33,7 +33,7 @@ def main():
file_start = read_base_file(filename)
# If you add a version to the list fix the docs (errorcodes.rst, err.rst)
classes, errors = fetch_errors(
- ['8.1', '8.2', '8.3', '8.4', '9.0', '9.1', '9.2', '9.3', '9.4'])
+ ['8.1', '8.2', '8.3', '8.4', '9.0', '9.1', '9.2', '9.3', '9.4', '9.5'])
f = open(filename, "w")
for line in file_start:
diff --git a/setup.py b/setup.py
index 210ad83..6414a88 100644
--- a/setup.py
+++ b/setup.py
@@ -41,6 +41,7 @@ Programming Language :: Python :: 3.1
Programming Language :: Python :: 3.2
Programming Language :: Python :: 3.3
Programming Language :: Python :: 3.4
+Programming Language :: Python :: 3.5
Programming Language :: C
Programming Language :: SQL
Topic :: Database
@@ -57,7 +58,10 @@ import os
import sys
import re
import subprocess
-from distutils.core import setup, Extension
+try:
+ from setuptools import setup, Extension
+except ImportError:
+ from distutils.core import setup, Extension
from distutils.command.build_ext import build_ext
from distutils.sysconfig import get_python_inc
from distutils.ccompiler import get_default_compiler
@@ -301,6 +305,10 @@ class psycopg_build_ext(build_ext):
except AttributeError:
ext_path = os.path.join(self.build_lib,
'psycopg2', '_psycopg.pyd')
+ # Make sure spawn() will work if compile() was never
+ # called. https://github.com/psycopg/psycopg2/issues/380
+ if not self.compiler.initialized:
+ self.compiler.initialize()
self.compiler.spawn(
['mt.exe', '-nologo', '-manifest',
os.path.join('psycopg', manifest),
@@ -343,7 +351,8 @@ class psycopg_build_ext(build_ext):
self.libraries.append("advapi32")
if self.compiler_is_msvc():
# MSVC requires an explicit "libpq"
- self.libraries.remove("pq")
+ if "pq" in self.libraries:
+ self.libraries.remove("pq")
self.libraries.append("secur32")
self.libraries.append("libpq")
self.libraries.append("shfolder")
diff --git a/tests/test_connection.py b/tests/test_connection.py
index e92c288..8aa5a2b 100755
--- a/tests/test_connection.py
+++ b/tests/test_connection.py
@@ -446,6 +446,13 @@ class MakeDsnTestCase(ConnectingTestCase):
self.assertRaises(psycopg2.ProgrammingError,
ext.make_dsn, url, nosuch="param")
+ @skip_before_libpq(9, 3)
+ def test_get_dsn_parameters(self):
+ conn = self.connect()
+ d = conn.get_dsn_parameters()
+ self.assertEqual(d['dbname'], dbname) # the only param we can check reliably
+ self.assertNotIn('password', d)
+
class IsolationLevelsTestCase(ConnectingTestCase):
diff --git a/tests/test_quote.py b/tests/test_quote.py
index 6e94562..25d1d31 100755
--- a/tests/test_quote.py
+++ b/tests/test_quote.py
@@ -23,12 +23,14 @@
# License for more details.
import sys
-from testutils import unittest, ConnectingTestCase, skip_before_libpq
+import testutils
+from testutils import unittest, ConnectingTestCase
import psycopg2
import psycopg2.extensions
from psycopg2.extensions import b
+
class QuotingTestCase(ConnectingTestCase):
r"""Checks the correct quoting of strings and binary objects.
@@ -51,7 +53,7 @@ class QuotingTestCase(ConnectingTestCase):
data = """some data with \t chars
to escape into, 'quotes' and \\ a backslash too.
"""
- data += "".join(map(chr, range(1,127)))
+ data += "".join(map(chr, range(1, 127)))
curs = self.conn.cursor()
curs.execute("SELECT %s;", (data,))
@@ -90,13 +92,13 @@ class QuotingTestCase(ConnectingTestCase):
if server_encoding != "UTF8":
return self.skipTest(
"Unicode test skipped since server encoding is %s"
- % server_encoding)
+ % server_encoding)
data = u"""some data with \t chars
to escape into, 'quotes', \u20ac euro sign and \\ a backslash too.
"""
- data += u"".join(map(unichr, [ u for u in range(1,65536)
- if not 0xD800 <= u <= 0xDFFF ])) # surrogate area
+ data += u"".join(map(unichr, [u for u in range(1, 65536)
+ if not 0xD800 <= u <= 0xDFFF])) # surrogate area
self.conn.set_client_encoding('UNICODE')
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE, self.conn)
@@ -156,7 +158,7 @@ class QuotingTestCase(ConnectingTestCase):
class TestQuotedString(ConnectingTestCase):
- def test_encoding(self):
+ def test_encoding_from_conn(self):
q = psycopg2.extensions.QuotedString('hi')
self.assertEqual(q.encoding, 'latin1')
@@ -166,13 +168,13 @@ class TestQuotedString(ConnectingTestCase):
class TestQuotedIdentifier(ConnectingTestCase):
- @skip_before_libpq(9, 0)
+ @testutils.skip_before_libpq(9, 0)
def test_identifier(self):
from psycopg2.extensions import quote_ident
self.assertEqual(quote_ident('blah-blah', self.conn), '"blah-blah"')
self.assertEqual(quote_ident('quote"inside', self.conn), '"quote""inside"')
- @skip_before_libpq(9, 0)
+ @testutils.skip_before_libpq(9, 0)
def test_unicode_ident(self):
from psycopg2.extensions import quote_ident
snowman = u"\u2603"
@@ -183,9 +185,59 @@ class TestQuotedIdentifier(ConnectingTestCase):
self.assertEqual(quote_ident(snowman, self.conn), quoted)
+class TestStringAdapter(ConnectingTestCase):
+ def test_encoding_default(self):
+ from psycopg2.extensions import adapt
+ a = adapt("hello")
+ self.assertEqual(a.encoding, 'latin1')
+ self.assertEqual(a.getquoted(), b("'hello'"))
+
+ # NOTE: we can't really test an encoding different from utf8, because
+ # when encoding without connection the libpq will use parameters from
+ # a previous one, so what would happens depends jn the tests run order.
+ # egrave = u'\xe8'
+ # self.assertEqual(adapt(egrave).getquoted(), "'\xe8'")
+
+ def test_encoding_error(self):
+ from psycopg2.extensions import adapt
+ snowman = u"\u2603"
+ a = adapt(snowman)
+ self.assertRaises(UnicodeEncodeError, a.getquoted)
+
+ def test_set_encoding(self):
+ # Note: this works-ish mostly in case when the standard db connection
+ # we test with is utf8, otherwise the encoding chosen by PQescapeString
+ # may give bad results.
+ from psycopg2.extensions import adapt
+ snowman = u"\u2603"
+ a = adapt(snowman)
+ a.encoding = 'utf8'
+ self.assertEqual(a.encoding, 'utf8')
+ self.assertEqual(a.getquoted(), b("'\xe2\x98\x83'"))
+
+ def test_connection_wins_anyway(self):
+ from psycopg2.extensions import adapt
+ snowman = u"\u2603"
+ a = adapt(snowman)
+ a.encoding = 'latin9'
+
+ self.conn.set_client_encoding('utf8')
+ a.prepare(self.conn)
+
+ self.assertEqual(a.encoding, 'utf_8')
+ self.assertEqual(a.getquoted(), b("'\xe2\x98\x83'"))
+
+ @testutils.skip_before_python(3)
+ def test_adapt_bytes(self):
+ snowman = u"\u2603"
+ self.conn.set_client_encoding('utf8')
+ a = psycopg2.extensions.QuotedString(snowman.encode('utf8'))
+ a.prepare(self.conn)
+ self.assertEqual(a.getquoted(), b("'\xe2\x98\x83'"))
+
+
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)
if __name__ == "__main__":
unittest.main()
-
diff --git a/tests/test_types_basic.py b/tests/test_types_basic.py
index 199dc1b..248712b 100755
--- a/tests/test_types_basic.py
+++ b/tests/test_types_basic.py
@@ -95,11 +95,11 @@ class TypesBasicTests(ConnectingTestCase):
except ValueError:
return self.skipTest("inf not available on this platform")
s = self.execute("SELECT %s AS foo", (float("inf"),))
- self.failUnless(str(s) == "inf", "wrong float quoting: " + str(s))
+ self.failUnless(str(s) == "inf", "wrong float quoting: " + str(s))
self.failUnless(type(s) == float, "wrong float conversion: " + repr(s))
s = self.execute("SELECT %s AS foo", (float("-inf"),))
- self.failUnless(str(s) == "-inf", "wrong float quoting: " + str(s))
+ self.failUnless(str(s) == "-inf", "wrong float quoting: " + str(s))
def testBinary(self):
if sys.version_info[0] < 3:
@@ -192,6 +192,7 @@ class TypesBasicTests(ConnectingTestCase):
self.assertRaises(psycopg2.DataError,
psycopg2.extensions.STRINGARRAY, b(s), curs)
+ @testutils.skip_before_postgres(8, 2)
def testArrayOfNulls(self):
curs = self.conn.cursor()
curs.execute("""
@@ -363,8 +364,8 @@ class AdaptSubclassTest(unittest.TestCase):
try:
self.assertEqual(b('b'), adapt(C()).getquoted())
finally:
- del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote]
- del psycopg2.extensions.adapters[B, psycopg2.extensions.ISQLQuote]
+ del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote]
+ del psycopg2.extensions.adapters[B, psycopg2.extensions.ISQLQuote]
@testutils.skip_from_python(3)
def test_no_mro_no_joy(self):
@@ -377,8 +378,7 @@ class AdaptSubclassTest(unittest.TestCase):
try:
self.assertRaises(psycopg2.ProgrammingError, adapt, B())
finally:
- del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote]
-
+ del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote]
@testutils.skip_before_python(3)
def test_adapt_subtype_3(self):
@@ -391,7 +391,7 @@ class AdaptSubclassTest(unittest.TestCase):
try:
self.assertEqual(b("a"), adapt(B()).getquoted())
finally:
- del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote]
+ del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote]
class ByteaParserTest(unittest.TestCase):
@@ -479,6 +479,7 @@ class ByteaParserTest(unittest.TestCase):
self.assertEqual(rv, tgt)
+
def skip_if_cant_cast(f):
@wraps(f)
def skip_if_cant_cast_(self, *args, **kwargs):
@@ -498,4 +499,3 @@ def test_suite():
if __name__ == "__main__":
unittest.main()
-
diff --git a/tests/testutils.py b/tests/testutils.py
index 70eb2cc..1d1ad05 100644
--- a/tests/testutils.py
+++ b/tests/testutils.py
@@ -101,7 +101,7 @@ class ConnectingTestCase(unittest.TestCase):
self._conns
except AttributeError, e:
raise AttributeError(
- "%s (did you remember calling ConnectingTestCase.setUp()?)"
+ "%s (did you forget to call ConnectingTestCase.setUp()?)"
% e)
if 'dsn' in kwargs: