Merge branch 'master' into encrypt-pass

This commit is contained in:
Daniele Varrazzo 2018-05-20 17:57:34 +01:00
commit 0161d54dbb
24 changed files with 206 additions and 84 deletions

View File

@ -6,12 +6,14 @@ language: python
python: python:
- 2.7 - 2.7
- 3.7-dev
- 3.6 - 3.6
- 3.5 - 3.5
- 3.4 - 3.4
install: install:
- python setup.py install - pip install -U pip setuptools wheel
- pip install .
- rm -rf psycopg2.egg-info - rm -rf psycopg2.egg-info
- sudo scripts/travis_prepare.sh - sudo scripts/travis_prepare.sh

10
LICENSE
View File

@ -1,5 +1,5 @@
psycopg2 and the LGPL psycopg2 and the LGPL
===================== ---------------------
psycopg2 is free software: you can redistribute it and/or modify it psycopg2 is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published under the terms of the GNU Lesser General Public License as published
@ -29,15 +29,15 @@ If not, see <http://www.gnu.org/licenses/>.
Alternative licenses Alternative licenses
==================== --------------------
If you prefer you can use the Zope Database Adapter ZPsycopgDA (i.e., If you prefer you can use the Zope Database Adapter ZPsycopgDA (i.e.,
every file inside the ZPsycopgDA directory) user the ZPL license as every file inside the ZPsycopgDA directory) using the ZPL license as
published on the Zope web site, http://www.zope.org/Resources/ZPL. published on the Zope web site, http://www.zope.org/Resources/ZPL.
Also, the following BSD-like license applies (at your option) to the Also, the following BSD-like license applies (at your option) to the
files following the pattern psycopg/adapter*.{h,c} and files following the pattern ``psycopg/adapter*.{h,c}`` and
psycopg/microprotocol*.{h,c}: ``psycopg/microprotocol*.{h,c}``:
Permission is granted to anyone to use this software for any purpose, Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it including commercial applications, and to alter it and redistribute it

4
NEWS
View File

@ -17,10 +17,14 @@ Other changes:
What's new in psycopg 2.7.5 What's new in psycopg 2.7.5
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Allow non-ascii chars in namedtuple fields (regression introduced fixing
:ticket:`#211`).
- Fixed adaptation of arrays of arrays of nulls (:ticket:`#325`).
- Fixed building on Solaris 11 and derivatives such as SmartOS and illumos - Fixed building on Solaris 11 and derivatives such as SmartOS and illumos
(:ticket:`#677`). (:ticket:`#677`).
- Maybe fixed building on MSYS2 (as reported in :ticket:`#658`). - Maybe fixed building on MSYS2 (as reported in :ticket:`#658`).
- Allow string subclasses in connection and other places (:ticket:`#679`). - Allow string subclasses in connection and other places (:ticket:`#679`).
- Don't raise an exception closing an unused named cursor (:ticket:`#716`).
What's new in psycopg 2.7.4 What's new in psycopg 2.7.4

View File

@ -54,8 +54,8 @@ external libraries, by installing the `psycopg2-binary`_ package from PyPI::
The binary package is a practical choice for development and testing but in The binary package is a practical choice for development and testing but in
production it is advised to use the package built from sources. production it is advised to use the package built from sources.
.. _PyPI: https://pypi.python.org/pypi/psycopg2 .. _PyPI: https://pypi.org/project/psycopg2/
.. _psycopg2-binary: https://pypi.python.org/pypi/psycopg2-binary .. _psycopg2-binary: https://pypi.org/project/psycopg2-binary/
.. _install: http://initd.org/psycopg/docs/install.html#install-from-source .. _install: http://initd.org/psycopg/docs/install.html#install-from-source
.. _faq: http://initd.org/psycopg/docs/faq.html#faq-compile .. _faq: http://initd.org/psycopg/docs/faq.html#faq-compile

View File

@ -29,8 +29,6 @@ doctest:
upload: upload:
# this command requires ssh configured to the proper target # this command requires ssh configured to the proper target
tar czf - -C html . | ssh psycoweb tar xzvf - -C docs/current tar czf - -C html . | ssh psycoweb tar xzvf - -C docs/current
# this command requires a .pypirc with the right privileges
# python src/tools/pypi_docs_upload.py psycopg2 $$(pwd)/html
clean: clean:
$(MAKE) $(SPHOPTS) -C src $@ $(MAKE) $(SPHOPTS) -C src $@

View File

@ -100,5 +100,5 @@ Test packages may be uploaded on the `PyPI testing site`__ using::
assuming `proper configuration`__ of ``~/.pypirc``. assuming `proper configuration`__ of ``~/.pypirc``.
.. __: https://testpypi.python.org/pypi/psycopg2 .. __: https://test.pypi.org/project/psycopg2/
.. __: https://wiki.python.org/moin/TestPyPI .. __: https://wiki.python.org/moin/TestPyPI

View File

@ -485,7 +485,7 @@ details. You can check the `psycogreen`_ project for further informations and
resources about the topic. resources about the topic.
.. _coroutine: http://en.wikipedia.org/wiki/Coroutine .. _coroutine: http://en.wikipedia.org/wiki/Coroutine
.. _greenlet: http://pypi.python.org/pypi/greenlet .. _greenlet: https://pypi.org/project/greenlet/
.. _green threads: http://en.wikipedia.org/wiki/Green_threads .. _green threads: http://en.wikipedia.org/wiki/Green_threads
.. _Eventlet: http://eventlet.net/ .. _Eventlet: http://eventlet.net/
.. _gevent: http://www.gevent.org/ .. _gevent: http://www.gevent.org/

View File

@ -61,8 +61,8 @@ except ImportError:
release = version release = version
intersphinx_mapping = { intersphinx_mapping = {
'py': ('http://docs.python.org/2', None), 'py': ('https://docs.python.org/2', None),
'py3': ('http://docs.python.org/3', None), 'py3': ('https://docs.python.org/3', None),
} }
# Pattern to generate links to the bug tracker # Pattern to generate links to the bug tracker

View File

@ -48,6 +48,7 @@ Psycopg 2 is both Unicode and Python 3 friendly.
errorcodes errorcodes
faq faq
news news
license
.. ifconfig:: builder != 'text' .. ifconfig:: builder != 'text'

View File

@ -45,7 +45,9 @@ Build prerequisites
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
The build prerequisites are to be met in order to install Psycopg from source The build prerequisites are to be met in order to install Psycopg from source
code, either from a source distribution package or from PyPI. code, from a source distribution package, GitHub_ or from PyPI.
.. _GitHub: https://github.com/psycopg/psycopg2
Psycopg is a C wrapper around the libpq_ PostgreSQL client library. To install Psycopg is a C wrapper around the libpq_ PostgreSQL client library. To install
it from sources you will need: it from sources you will need:
@ -140,7 +142,7 @@ Make sure to use an up-to-date version of :program:`pip` (you can upgrade it
using something like ``pip install -U pip``) using something like ``pip install -U pip``)
.. __: PyPI-binary_ .. __: PyPI-binary_
.. _PyPI-binary: https://pypi.python.org/pypi/psycopg2-binary/ .. _PyPI-binary: https://pypi.org/project/psycopg2-binary/
.. _wheel: http://pythonwheels.com/ .. _wheel: http://pythonwheels.com/
.. note:: .. note::
@ -302,10 +304,14 @@ Try the following. *In order:*
- Google for `!psycopg2` *your error message*. Especially useful the week - Google for `!psycopg2` *your error message*. Especially useful the week
after the release of a new OS X version. after the release of a new OS X version.
- Write to the `Mailing List`__. - Write to the `Mailing List`_.
- If you think that you have discovered a bug, test failure or missing feature
please raise a ticket in the `bug tracker`_.
- Complain on your blog or on Twitter that `!psycopg2` is the worst package - Complain on your blog or on Twitter that `!psycopg2` is the worst package
ever and about the quality time you have wasted figuring out the correct ever and about the quality time you have wasted figuring out the correct
:envvar:`ARCHFLAGS`. Especially useful from the Starbucks near you. :envvar:`ARCHFLAGS`. Especially useful from the Starbucks near you.
.. __: https://lists.postgresql.org/mj/mj_wwwusr?func=lists-long-full&extra=psycopg .. _mailing list: https://lists.postgresql.org/mj/mj_wwwusr?func=lists-long-full&extra=psycopg
.. _bug tracker: https://github.com/psycopg/psycopg2/issues

7
doc/src/license.rst Normal file
View File

@ -0,0 +1,7 @@
.. index::
single: License
License
=======
.. include:: ../../LICENSE

View File

@ -1,3 +1,7 @@
.. index::
single: Release notes
single: News
Release notes Release notes
============= =============

View File

@ -363,12 +363,15 @@ class NamedTupleCursor(_cursor):
return return
def _make_nt(self): def _make_nt(self):
# ascii except alnum and underscore
nochars = ' !"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~'
re_clean = _re.compile('[' + _re.escape(nochars) + ']')
def f(s): def f(s):
# NOTE: Python 3 actually allows unicode chars in fields s = re_clean.sub('_', s)
s = _re.sub('[^a-zA-Z0-9_]', '_', s)
# Python identifier cannot start with numbers, namedtuple fields # Python identifier cannot start with numbers, namedtuple fields
# cannot start with underscore. So... # cannot start with underscore. So...
if _re.match('^[0-9_]', s): if s[0] == '_' or '0' <= s[0] <= '9':
s = 'f' + s s = 'f' + s
return s return s

View File

@ -38,13 +38,14 @@ list_quote(listObject *self)
{ {
/* adapt the list by calling adapt() recursively and then wrapping /* adapt the list by calling adapt() recursively and then wrapping
everything into "ARRAY[]" */ everything into "ARRAY[]" */
PyObject *tmp = NULL, *str = NULL, *joined = NULL, *res = NULL; PyObject *res = NULL;
PyObject **qs = NULL;
Py_ssize_t bufsize = 0;
char *buf = NULL, *ptr;
/* list consisting of only NULL don't work with the ARRAY[] construct /* list consisting of only NULL don't work with the ARRAY[] construct
* so we use the {NULL,...} syntax. Note however that list of lists where * so we use the {NULL,...} syntax. The same syntax is also necessary
* some element is a list of only null still fails: for that we should use * to convert array of arrays containing only nulls. */
* the '{...}' syntax uniformly but we cannot do it in the current
* infrastructure. TODO in psycopg3 */
int all_nulls = 1; int all_nulls = 1;
Py_ssize_t i, len; Py_ssize_t i, len;
@ -53,47 +54,95 @@ list_quote(listObject *self)
/* empty arrays are converted to NULLs (still searching for a way to /* empty arrays are converted to NULLs (still searching for a way to
insert an empty array in postgresql */ insert an empty array in postgresql */
if (len == 0) return Bytes_FromString("'{}'"); if (len == 0) {
res = Bytes_FromString("'{}'");
goto exit;
}
tmp = PyTuple_New(len); if (!(qs = PyMem_New(PyObject *, len))) {
PyErr_NoMemory();
goto exit;
}
memset(qs, 0, len * sizeof(PyObject *));
for (i=0; i<len; i++) { for (i = 0; i < len; i++) {
PyObject *quoted;
PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i); PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i);
if (wrapped == Py_None) { if (wrapped == Py_None) {
Py_INCREF(psyco_null); Py_INCREF(psyco_null);
quoted = psyco_null; qs[i] = psyco_null;
} }
else { else {
quoted = microprotocol_getquoted(wrapped, if (!(qs[i] = microprotocol_getquoted(
(connectionObject*)self->connection); wrapped, (connectionObject*)self->connection))) {
if (quoted == NULL) goto error; goto exit;
all_nulls = 0; }
/* Lists of arrays containing only nulls are also not supported
* by the ARRAY construct so we should do some special casing */
if (!PyList_Check(wrapped) || Bytes_AS_STRING(qs[i])[0] == 'A') {
all_nulls = 0;
}
} }
bufsize += Bytes_GET_SIZE(qs[i]) + 1; /* this, and a comma */
/* here we don't loose a refcnt: SET_ITEM does not change the
reference count and we are just transferring ownership of the tmp
object to the tuple */
PyTuple_SET_ITEM(tmp, i, quoted);
} }
/* now that we have a tuple of adapted objects we just need to join them /* Create an array literal, usually ARRAY[...] but if the contents are
and put "ARRAY[] around the result */ * all NULL or array of NULL we must use the '{...}' syntax
str = Bytes_FromString(", "); */
joined = PyObject_CallMethod(str, "join", "(O)", tmp); if (!(ptr = buf = PyMem_Malloc(bufsize + 8))) {
if (joined == NULL) goto error; PyErr_NoMemory();
goto exit;
}
/* PG doesn't like ARRAY[NULL..] */
if (!all_nulls) { if (!all_nulls) {
res = Bytes_FromFormat("ARRAY[%s]", Bytes_AsString(joined)); strcpy(ptr, "ARRAY[");
} else { ptr += 6;
res = Bytes_FromFormat("'{%s}'", Bytes_AsString(joined)); for (i = 0; i < len; i++) {
Py_ssize_t sl;
sl = Bytes_GET_SIZE(qs[i]);
memcpy(ptr, Bytes_AS_STRING(qs[i]), sl);
ptr += sl;
*ptr++ = ',';
}
*(ptr - 1) = ']';
}
else {
*ptr++ = '\'';
*ptr++ = '{';
for (i = 0; i < len; i++) {
/* in case all the adapted things are nulls (or array of nulls),
* the quoted string is either NULL or an array of the form
* '{NULL,...}', in which case we have to strip the extra quotes */
char *s;
Py_ssize_t sl;
s = Bytes_AS_STRING(qs[i]);
sl = Bytes_GET_SIZE(qs[i]);
if (s[0] != '\'') {
memcpy(ptr, s, sl);
ptr += sl;
}
else {
memcpy(ptr, s + 1, sl - 2);
ptr += sl - 2;
}
*ptr++ = ',';
}
*(ptr - 1) = '}';
*ptr++ = '\'';
} }
error: res = Bytes_FromStringAndSize(buf, ptr - buf);
Py_XDECREF(tmp);
Py_XDECREF(str); exit:
Py_XDECREF(joined); if (qs) {
for (i = 0; i < len; i++) {
PyObject *q = qs[i];
Py_XDECREF(q);
}
PyMem_Free(qs);
}
PyMem_Free(buf);
return res; return res;
} }

View File

@ -59,6 +59,11 @@ psyco_curs_close(cursorObject *self)
char buffer[128]; char buffer[128];
PGTransactionStatusType status; PGTransactionStatusType status;
if (!self->query) {
Dprintf("skipping named cursor close because unused");
goto close;
}
if (self->conn) { if (self->conn) {
status = PQtransactionStatus(self->conn->pgconn); status = PQtransactionStatus(self->conn->pgconn);
} }
@ -66,17 +71,18 @@ psyco_curs_close(cursorObject *self)
status = PQTRANS_UNKNOWN; status = PQTRANS_UNKNOWN;
} }
if (!(status == PQTRANS_UNKNOWN || status == PQTRANS_INERROR)) { if (status == PQTRANS_UNKNOWN || status == PQTRANS_INERROR) {
EXC_IF_NO_MARK(self);
PyOS_snprintf(buffer, 127, "CLOSE %s", self->qname);
if (pq_execute(self, buffer, 0, 0, 1) == -1) return NULL;
}
else {
Dprintf("skipping named curs close because tx status %d", Dprintf("skipping named curs close because tx status %d",
(int)status); (int)status);
goto close;
} }
EXC_IF_NO_MARK(self);
PyOS_snprintf(buffer, 127, "CLOSE %s", self->qname);
if (pq_execute(self, buffer, 0, 0, 1) == -1) return NULL;
} }
close:
self->closed = 1; self->closed = 1;
Dprintf("psyco_curs_close: cursor at %p closed", self); Dprintf("psyco_curs_close: cursor at %p closed", self);

View File

@ -406,6 +406,11 @@ typecast_PYINTERVAL_cast(const char *str, Py_ssize_t len, PyObject *curs)
} }
break; break;
case 'P':
PyErr_SetString(NotSupportedError,
"iso_8601 intervalstyle currently not supported");
return NULL;
default: default:
break; break;
} }

View File

@ -56,15 +56,15 @@ fi
# Unsupported postgres versions that we still support # Unsupported postgres versions that we still support
# Images built by https://github.com/psycopg/psycopg2-wheels/tree/build-dinosaurs # Images built by https://github.com/psycopg/psycopg2-wheels/tree/build-dinosaurs
if [[ -n "$TEST_PAST" ]]; then if [[ -n "$TEST_PAST" ]]; then
run_test 7.4
run_test 8.0
run_test 8.1
run_test 8.2
run_test 8.3
run_test 8.4
run_test 9.0
run_test 9.1
run_test 9.2 run_test 9.2
run_test 9.1
run_test 9.0
run_test 8.4
run_test 8.3
run_test 8.2
run_test 8.1
run_test 8.0
run_test 7.4
fi fi
# Postgres built from master # Postgres built from master

View File

@ -435,6 +435,11 @@ class CursorTests(ConnectingTestCase):
self.assertEqual([(2,), (3,), (4,)], cur2.fetchmany(3)) self.assertEqual([(2,), (3,), (4,)], cur2.fetchmany(3))
self.assertEqual([(5,), (6,), (7,)], cur2.fetchall()) self.assertEqual([(5,), (6,), (7,)], cur2.fetchall())
@skip_before_postgres(8, 0)
def test_named_noop_close(self):
cur = self.conn.cursor('test')
cur.close()
@skip_before_postgres(8, 0) @skip_before_postgres(8, 0)
def test_scroll(self): def test_scroll(self):
cur = self.conn.cursor() cur = self.conn.cursor()

View File

@ -438,6 +438,14 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
r = cur.fetchone()[0] r = cur.fetchone()[0]
self.assertEqual(r, v, "%s -> %s != %s" % (s, r, v)) self.assertEqual(r, v, "%s -> %s != %s" % (s, r, v))
@skip_before_postgres(8, 4)
def test_interval_iso_8601_not_supported(self):
# We may end up supporting, but no pressure for it
cur = self.conn.cursor()
cur.execute("set local intervalstyle to iso_8601")
cur.execute("select '1 day 2 hours'::interval")
self.assertRaises(psycopg2.NotSupportedError, cur.fetchone)
# Only run the datetime tests if psycopg was compiled with support. # Only run the datetime tests if psycopg was compiled with support.
if not hasattr(psycopg2.extensions, 'PYDATETIME'): if not hasattr(psycopg2.extensions, 'PYDATETIME'):

View File

@ -19,7 +19,7 @@ from datetime import timedelta
import psycopg2 import psycopg2
import psycopg2.extras import psycopg2.extras
import unittest import unittest
from .testutils import ConnectingTestCase, skip_before_postgres from .testutils import ConnectingTestCase, skip_before_postgres, skip_before_python
class ExtrasDictCursorTests(ConnectingTestCase): class ExtrasDictCursorTests(ConnectingTestCase):
@ -357,6 +357,14 @@ class NamedTupleCursorTest(ConnectingTestCase):
self.assertEqual(rv.f_column_, 2) self.assertEqual(rv.f_column_, 2)
self.assertEqual(rv.f3, 3) self.assertEqual(rv.f3, 3)
@skip_before_python(3)
@skip_before_postgres(8)
def test_nonascii_name(self):
curs = self.conn.cursor()
curs.execute('select 1 as \xe5h\xe9')
rv = curs.fetchone()
self.assertEqual(getattr(rv, '\xe5h\xe9'), 1)
def test_minimal_generation(self): def test_minimal_generation(self):
# Instrument the class to verify it gets called the minimum number of times. # Instrument the class to verify it gets called the minimum number of times.
from psycopg2.extras import NamedTupleCursor from psycopg2.extras import NamedTupleCursor

View File

@ -224,16 +224,31 @@ class TypesBasicTests(ConnectingTestCase):
curs.execute("insert into na (boola) values (%s)", ([True, None],)) curs.execute("insert into na (boola) values (%s)", ([True, None],))
curs.execute("insert into na (boola) values (%s)", ([None, None],)) curs.execute("insert into na (boola) values (%s)", ([None, None],))
# TODO: array of array of nulls are not supported yet curs.execute("insert into na (textaa) values (%s)", ([[None]],))
# curs.execute("insert into na (textaa) values (%s)", ([[None]],))
curs.execute("insert into na (textaa) values (%s)", ([['a', None]],)) curs.execute("insert into na (textaa) values (%s)", ([['a', None]],))
# curs.execute("insert into na (textaa) values (%s)", ([[None, None]],)) curs.execute("insert into na (textaa) values (%s)", ([[None, None]],))
# curs.execute("insert into na (intaa) values (%s)", ([[None]],))
curs.execute("insert into na (intaa) values (%s)", ([[None]],))
curs.execute("insert into na (intaa) values (%s)", ([[42, None]],)) curs.execute("insert into na (intaa) values (%s)", ([[42, None]],))
# curs.execute("insert into na (intaa) values (%s)", ([[None, None]],)) curs.execute("insert into na (intaa) values (%s)", ([[None, None]],))
# curs.execute("insert into na (boolaa) values (%s)", ([[None]],))
curs.execute("insert into na (boolaa) values (%s)", ([[None]],))
curs.execute("insert into na (boolaa) values (%s)", ([[True, None]],)) curs.execute("insert into na (boolaa) values (%s)", ([[True, None]],))
# curs.execute("insert into na (boolaa) values (%s)", ([[None, None]],)) curs.execute("insert into na (boolaa) values (%s)", ([[None, None]],))
@testutils.skip_before_postgres(8, 2)
def testNestedArrays(self):
curs = self.conn.cursor()
for a in [
[[1]],
[[None]],
[[None, None, None]],
[[None, None], [1, None]],
[[None, None], [None, None]],
[[[None, None], [None, None]]],
]:
curs.execute("select %s::int[]", (a,))
self.assertEqual(curs.fetchone()[0], a)
@testutils.skip_from_python(3) @testutils.skip_from_python(3)
def testTypeRoundtripBuffer(self): def testTypeRoundtripBuffer(self):

View File

@ -179,8 +179,8 @@ class HstoreTestCase(ConnectingTestCase):
m = re.match(br'hstore\(ARRAY\[([^\]]+)\], ARRAY\[([^\]]+)\]\)', q) m = re.match(br'hstore\(ARRAY\[([^\]]+)\], ARRAY\[([^\]]+)\]\)', q)
self.assert_(m, repr(q)) self.assert_(m, repr(q))
kk = m.group(1).split(b", ") kk = m.group(1).split(b",")
vv = m.group(2).split(b", ") vv = m.group(2).split(b",")
ii = list(zip(kk, vv)) ii = list(zip(kk, vv))
ii.sort() ii.sort()

View File

@ -26,7 +26,7 @@ import psycopg2
import psycopg2.extensions as ext import psycopg2.extensions as ext
import unittest import unittest
from .testutils import ConnectingTestCase from .testutils import ConnectingTestCase, skip_before_postgres
class WithTestCase(ConnectingTestCase): class WithTestCase(ConnectingTestCase):
@ -215,6 +215,11 @@ class WithCursorTestCase(WithTestCase):
else: else:
self.fail("where is my exception?") self.fail("where is my exception?")
@skip_before_postgres(8, 0)
def test_named_with_noop(self):
with self.conn.cursor('named') as cur:
pass
def test_suite(): def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__) return unittest.TestLoader().loadTestsFromName(__name__)

View File

@ -1,13 +1,9 @@
# Tox (http://tox.testrun.org/) is a tool for running tests
# in multiple virtualenvs. This configuration file will run the
# test suite on all supported python versions. To use it, "pip install tox"
# and then run "tox" from this directory.
[tox] [tox]
envlist = py27 envlist = py{27,34,35,36}
[testenv] [testenv]
commands = make check commands = make check
whitelist_externals = make
[flake8] [flake8]
max-line-length = 85 max-line-length = 85