Merge branch 'master' into replication-protocol

Conflicts:
	tests/testconfig.py
This commit is contained in:
Daniele Varrazzo 2016-08-07 01:53:21 +01:00
commit 2a4d6027a4
28 changed files with 337 additions and 121 deletions

View File

@ -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

View File

@ -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

13
NEWS
View File

@ -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

View File

@ -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::

View File

@ -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

View File

@ -568,6 +568,29 @@ The ``connection`` class
.. versionadded:: 2.0.12
.. 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

View File

@ -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.

View File

@ -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/

View File

@ -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()`

View File

@ -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'

View File

@ -451,7 +451,7 @@ psyco_TimestampFromTicks(PyObject *self, PyObject *args)
tz);
exit:
Py_DECREF(tz);
Py_XDECREF(tz);
Py_XDECREF(m);
return res;
}

View File

@ -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,15 +162,34 @@ qstring_conform(qstringObject *self, PyObject *args)
static PyObject *
qstring_get_encoding(qstringObject *self)
{
const char *encoding = default_encoding;
if (self->conn) {
encoding = self->conn->codec;
}
const char *encoding;
encoding = _qstring_get_encoding(self);
return Text_FromUTF8(encoding);
}
static int
qstring_set_encoding(qstringObject *self, PyObject *pyenc)
{
int rv = -1;
const char *tmp;
char *cenc;
/* 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 **/
/* object member list */
@ -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,

View File

@ -39,6 +39,9 @@ typedef struct {
PyObject *buffer;
connectionObject *conn;
const char *encoding;
} qstringObject;
#ifdef __cplusplus

View File

@ -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

View File

@ -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")) {

View File

@ -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);

View File

@ -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)

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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."

View File

@ -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;

View File

@ -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;
}

View File

@ -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:

View File

@ -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")

View File

@ -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):

View File

@ -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()

View File

@ -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()

View File

@ -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: