Merge branch 'master' into drop-2to3

This commit is contained in:
Daniele Varrazzo 2018-05-20 23:56:29 +01:00
commit e8a831dda2
41 changed files with 924 additions and 369 deletions

View File

@ -13,43 +13,17 @@ environment:
matrix:
# For Python versions available on Appveyor, see
# http://www.appveyor.com/docs/installed-software#python
- {PYVER: "27", PYTHON_ARCH: "32"}
- {PYVER: "27", PYTHON_ARCH: "64"}
- {PYVER: "34", PYTHON_ARCH: "32"}
- {PYVER: "34", PYTHON_ARCH: "64"}
- {PYVER: "35", PYTHON_ARCH: "32"}
- {PYVER: "35", PYTHON_ARCH: "64"}
- {PYVER: "36", PYTHON_ARCH: "32"}
- {PYVER: "36", PYTHON_ARCH: "64"}
# Py 2.7 = VS Ver. 9.0 (VS 2008)
# Py 3.4 = VS Ver. 10.0 (VS 2010)
# Py 3.5, 3.6 = VS Ver. 14.0 (VS 2015)
- PYTHON: C:\Python27-x64
PYTHON_ARCH: 64
VS_VER: 9.0
- PYTHON: C:\Python27
PYTHON_ARCH: 32
VS_VER: 9.0
- PYTHON: C:\Python36-x64
PYTHON_ARCH: 64
VS_VER: 14.0
- PYTHON: C:\Python36
PYTHON_ARCH: 32
VS_VER: 14.0
- PYTHON: C:\Python35-x64
PYTHON_ARCH: 64
VS_VER: 14.0
- PYTHON: C:\Python35
PYTHON_ARCH: 32
VS_VER: 14.0
- PYTHON: C:\Python34-x64
DISTUTILS_USE_SDK: '1'
PYTHON_ARCH: 64
VS_VER: 10.0
- PYTHON: C:\Python34
PYTHON_ARCH: 32
VS_VER: 10.0
OPENSSL_VERSION: "1_0_2n"
POSTGRES_VERSION: "10_1"
PSYCOPG2_TESTDB: psycopg2_test
PSYCOPG2_TESTDB_USER: postgres
@ -77,7 +51,22 @@ cache:
init:
# Uncomment next line to get RDP access during the build.
#- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
#
# Set env variable according to the build environment
- SET PYTHON=C:\Python%PYVER%
- IF "%PYTHON_ARCH%"=="64" SET PYTHON=%PYTHON%-x64
# Py 2.7 = VS Ver. 9.0 (VS 2008)
# Py 3.3, 3.4 = VS Ver. 10.0 (VS 2010)
# Py 3.5, 3.6 = VS Ver. 14.0 (VS 2015)
- IF "%PYVER%"=="27" SET VS_VER=9.0
- IF "%PYVER%"=="33" SET VS_VER=10.0
- IF "%PYVER%"=="34" SET VS_VER=10.0
- IF "%PYVER%"=="35" SET VS_VER=14.0
- IF "%PYVER%"=="36" SET VS_VER=14.0
- IF "%VS_VER%"=="10.0" IF "%PYTHON_ARCH%"=="64" SET DISTUTILS_USE_SDK=1
# Set Python to the path
- SET PATH=%PYTHON%;%PYTHON%\Scripts;C:\Program Files\Git\mingw64\bin;%PATH%
@ -153,8 +142,8 @@ install:
}
# Download OpenSSL source
- CD C:\Others
- IF NOT EXIST OpenSSL_1_0_2m.zip (
curl -fsSL -o OpenSSL_1_0_2m.zip https://github.com/openssl/openssl/archive/OpenSSL_1_0_2m.zip
- IF NOT EXIST OpenSSL_%OPENSSL_VERSION%.zip (
curl -fsSL -o OpenSSL_%OPENSSL_VERSION%.zip https://github.com/openssl/openssl/archive/OpenSSL_%OPENSSL_VERSION%.zip
)
# To use OpenSSL >= 1.1.0, both libpq and psycopg build environments have
@ -166,15 +155,15 @@ install:
# - nmake build_libs install_dev
- IF NOT EXIST %OPENSSLTOP%\lib\ssleay32.lib (
CD %BUILD_DIR% &&
7z x C:\Others\OpenSSL_1_0_2m.zip &&
CD openssl-OpenSSL_1_0_2m &&
7z x C:\Others\OpenSSL_%OPENSSL_VERSION%.zip &&
CD openssl-OpenSSL_%OPENSSL_VERSION% &&
perl Configure %TARGET% no-asm no-shared no-zlib --prefix=%OPENSSLTOP% --openssldir=%OPENSSLTOP% &&
CALL ms\%DO% &&
nmake -f ms\nt.mak init headers lib &&
COPY inc32\openssl\*.h %OPENSSLTOP%\include\openssl &&
COPY out32\*.lib %OPENSSLTOP%\lib &&
CD %BASE_DIR% &&
RMDIR /S /Q %BUILD_DIR%\openssl-OpenSSL_1_0_2m
RMDIR /S /Q %BUILD_DIR%\openssl-OpenSSL_%OPENSSL_VERSION%
)
# Setup directories for building PostgreSQL librarires
@ -188,8 +177,8 @@ install:
# Download PostgreSQL source
- CD C:\Others
- IF NOT EXIST postgres-REL_10_1.zip (
curl -fsSL -o postgres-REL_10_1.zip https://github.com/postgres/postgres/archive/REL_10_1.zip
- IF NOT EXIST postgres-REL_%POSTGRES_VERSION%.zip (
curl -fsSL -o postgres-REL_%POSTGRES_VERSION%.zip https://github.com/postgres/postgres/archive/REL_%POSTGRES_VERSION%.zip
)
# Setup build config file (config.pl)
@ -200,11 +189,11 @@ install:
# Prepare local include directory for building from
# Build pg_config in place
# NOTE: Cannot set and use the same variable inside an IF
- SET PGBUILD=%BUILD_DIR%\postgres-REL_10_1
- SET PGBUILD=%BUILD_DIR%\postgres-REL_%POSTGRES_VERSION%
- IF NOT EXIST %PGTOP%\lib\libpq.lib (
CD %BUILD_DIR% &&
7z x C:\Others\postgres-REL_10_1.zip &&
CD postgres-REL_10_1\src\tools\msvc &&
7z x C:\Others\postgres-REL_%POSTGRES_VERSION%.zip &&
CD postgres-REL_%POSTGRES_VERSION%\src\tools\msvc &&
ECHO $config-^>{ldap} = 0; > config.pl &&
ECHO $config-^>{openssl} = "%OPENSSLTOP:\=\\%"; >> config.pl &&
ECHO.>> config.pl &&

View File

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

10
LICENSE
View File

@ -1,5 +1,5 @@
psycopg2 and the LGPL
=====================
---------------------
psycopg2 is free software: you can redistribute it and/or modify it
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
====================
--------------------
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.
Also, the following BSD-like license applies (at your option) to the
files following the pattern psycopg/adapter*.{h,c} and
psycopg/microprotocol*.{h,c}:
files following the pattern ``psycopg/adapter*.{h,c}`` and
``psycopg/microprotocol*.{h,c}``:
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it

34
NEWS
View File

@ -4,6 +4,10 @@ Current release
What's new in psycopg 2.8
-------------------------
New features:
- Added `~psycopg2.extensions.encrypt_password()` function (:ticket:`#576`).
Other changes:
- Dropped support for Python 2.6, 3.2, 3.3.
@ -15,17 +19,41 @@ Other changes:
install``.
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
(:ticket:`#677`).
- Maybe fixed building on MSYS2 (as reported in :ticket:`#658`).
- 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
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Moving away from installing the wheel package by default.
Packages installed from wheel raise a warning on import. Added package
``psycopg2-binary`` to install from wheel instead (:ticket:`#543`).
- Convert fields names into valid Python identifiers in
`~psycopg2.extras.NamedTupleCursor` (:ticket:`#211`).
- Fixed Solaris 10 support (:ticket:`#532`).
- `cursor.mogrify()` can be called on closed cursors (:ticket:`#579`).
- Fixed setting session characteristics in corner cases on autocommit
connections (:ticket:`#580`).
- Fixed `~psycopg2.extras.MinTimeLoggingCursor` on Python 3 (:ticket:`#609`).
- Fixed parsing of array of points as floats (:ticket:`#613`).
- Fixed `~psycopg2.__libpq_version__` building with libpq >= 10.1
(:ticket:`632`).
- Fixed `~cursor.rowcount` after `~cursor.executemany()` with :sql:`RETURNING` statements
(:ticket:`633`).
- Wheel packages compiled against PostgreSQL 10.1 libpq and OpenSSL 1.0.2m.
- Fixed `~cursor.rowcount` after `~cursor.executemany()` with :sql:`RETURNING`
statements (:ticket:`633`).
- Fixed compatibility problem with pypy3 (:ticket:`#649`).
- Wheel packages compiled against PostgreSQL 10.1 libpq and OpenSSL 1.0.2n.
- Wheel packages for Python 2.6 no more available (support dropped from
wheel building infrastructure).
What's new in psycopg 2.7.3.2

View File

@ -25,29 +25,40 @@ Documentation is included in the ``doc`` directory and is `available online`__.
.. __: http://initd.org/psycopg/docs/
For any other resource (source code repository, bug tracker, mailing list)
please check the `project homepage`__.
Installation
------------
If your ``pip`` version supports wheel_ packages it should be possible to
install a binary version of Psycopg including all the dependencies from PyPI_.
Just run::
Building Psycopg requires a few prerequisites (a C compiler, some development
packages): please check the install_ and the faq_ documents in the ``doc`` dir
or online for the details.
If prerequisites are met, you can install psycopg like any other Python
package, using ``pip`` to download it from PyPI_::
$ pip install -U pip # make sure your pip is up-to-date
$ pip install psycopg2
If you want to build Psycopg from source you will need some prerequisites (a C
compiler, development packages): please check the install_ and the faq_
documents in the ``doc`` dir for the details.
or using ``setup.py`` if you have downloaded the source package locally::
.. _wheel: http://pythonwheels.com/
.. _PyPI: https://pypi.python.org/pypi/psycopg2
$ python setup.py build
$ sudo python setup.py install
You can also obtain a stand-alone package, not requiring a compiler or
external libraries, by installing the `psycopg2-binary`_ package from PyPI::
$ pip install psycopg2-binary
The binary package is a practical choice for development and testing but in
production it is advised to use the package built from sources.
.. _PyPI: https://pypi.org/project/psycopg2/
.. _psycopg2-binary: https://pypi.org/project/psycopg2-binary/
.. _install: http://initd.org/psycopg/docs/install.html#install-from-source
.. _faq: http://initd.org/psycopg/docs/faq.html#faq-compile
For any other resource (source code repository, bug tracker, mailing list)
please check the `project homepage`__.
.. __: http://initd.org/psycopg/

View File

@ -29,8 +29,6 @@ doctest:
upload:
# this command requires ssh configured to the proper target
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:
$(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``.
.. __: https://testpypi.python.org/pypi/psycopg2
.. __: https://test.pypi.org/project/psycopg2/
.. __: 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.
.. _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
.. _Eventlet: http://eventlet.net/
.. _gevent: http://www.gevent.org/

View File

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

View File

@ -41,11 +41,6 @@ The ``connection`` class
previously only valid PostgreSQL identifiers were accepted as
cursor name.
.. warning::
It is unsafe to expose the *name* to an untrusted source, for
instance you shouldn't allow *name* to be read from a HTML form.
Consider it as part of the query, not as a query parameter.
The *cursor_factory* argument can be used to create non-standard
cursors. The class returned must be a subclass of
`psycopg2.extensions.cursor`. See :ref:`subclassing-cursor` for

View File

@ -555,6 +555,38 @@ Other functions
.. __: http://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQESCAPEIDENTIFIER
.. method:: encrypt_password(password, user, scope=None, algorithm=None)
Return the encrypted form of a PostgreSQL password.
:param password: the cleartext password to encrypt
:param user: the name of the user to use the password for
:param scope: the scope to encrypt the password into; if *algorithm* is
``md5`` it can be `!None`
:type scope: `connection` or `cursor`
:param algorithm: the password encryption algorithm to use
The *algorithm* ``md5`` is always supported. Other algorithms are only
supported if the client libpq version is at least 10 and may require a
compatible server version: check the `PostgreSQL encryption
documentation`__ to know the algorithms supported by your server.
.. __: https://www.postgresql.org/docs/current/static/encryption-options.html
Using `!None` as *algorithm* will result in querying the server to know the
current server password encryption setting, which is a blocking operation:
query the server separately and specify a value for *algorithm* if you
want to maintain a non-blocking behaviour.
.. versionadded:: 2.8
.. seealso:: PostgreSQL docs for the `password_encryption`__ setting, libpq `PQencryptPasswordConn()`__, `PQencryptPassword()`__ functions.
.. __: https://www.postgresql.org/docs/current/static/runtime-config-connection.html#GUC-PASSWORD-ENCRYPTION
.. __: https://www.postgresql.org/docs/current/static/libpq-misc.html#LIBPQ-PQENCRYPTPASSWORDCONN
.. __: https://www.postgresql.org/docs/current/static/libpq-misc.html#LIBPQ-PQENCRYPTPASSWORD
.. index::
pair: Isolation level; Constants

View File

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

View File

@ -12,16 +12,6 @@ to use Psycopg on a different Python implementation (PyPy, Jython, IronPython)
there is an experimental `porting of Psycopg for Ctypes`__, but it is not as
mature as the C implementation yet.
The current `!psycopg2` implementation supports:
..
NOTE: keep consistent with setup.py and the /features/ page.
- Python version 2.7
- Python 3 versions from 3.4 to 3.6
- PostgreSQL server versions from 7.4 to 10
- PostgreSQL client library version from 9.1
.. _PostgreSQL: http://www.postgresql.org/
.. _Python: http://www.python.org/
.. _libpq: http://www.postgresql.org/docs/current/static/libpq.html
@ -32,94 +22,20 @@ The current `!psycopg2` implementation supports:
.. index::
single: Install; from PyPI
single: Install; wheel
single: Wheel
single: Prerequisites
Binary install from PyPI
------------------------
Prerequisites
-------------
`!psycopg2` is `available on PyPI`__ in the form of wheel_ packages for the
most common platform (Linux, OSX, Windows): this should make you able to
install a binary version of the module including all the dependencies simply
using:
The current `!psycopg2` implementation supports:
.. code-block:: console
..
NOTE: keep consistent with setup.py and the /features/ page.
$ pip install psycopg2
Make sure to use an up-to-date version of :program:`pip` (you can upgrade it
using something like ``pip install -U pip``)
.. __: PyPI_
.. _PyPI: https://pypi.python.org/pypi/psycopg2/
.. _wheel: http://pythonwheels.com/
.. note::
The binary packages come with their own versions of a few C libraries,
among which ``libpq`` and ``libssl``, which will be used regardless of other
libraries available on the client: upgrading the system libraries will not
upgrade the libraries used by `!psycopg2`. Please build `!psycopg2` from
source if you want to maintain binary upgradeability.
.. warning::
Because the `!psycopg` wheel package uses its own ``libssl`` binary, it is
incompatible with other extension modules binding with ``libssl`` as well,
for instance the Python `ssl` module: the result will likely be a
segfault. If you need using both `!psycopg2` and other libraries using
``libssl`` please :ref:`disable the use of wheel packages for Psycopg
<disable-wheel>`.
.. index::
single: Install; disable wheel
single: Wheel; disable
.. _disable-wheel:
Disabling wheel packages
^^^^^^^^^^^^^^^^^^^^^^^^
If you want to disable the use of wheel binary packages and use the system
system libraries available on your client you can use the :command:`pip`
|--no-binary option|__:
.. code-block:: console
$ pip install --no-binary psycopg2
.. |--no-binary option| replace:: ``--no-binary`` option
.. __: https://pip.pypa.io/en/stable/reference/pip_install/#install-no-binary
which can be specified in your :file:`requirements.txt` files too, e.g. use:
.. code-block:: none
psycopg2>=2.7,<2.8 --no-binary psycopg2
to use the last bugfix release of the `!psycopg2` 2.7 package, specifying to
always compile it from source. Of course in this case you will have to meet
the :ref:`build prerequisites <build-prerequisites>`.
.. index::
single: Install; from source
.. _install-from-source:
Install from source
-------------------
.. _source-package:
You can download a copy of Psycopg source files from the `Psycopg download
page`__ or from PyPI_.
.. __: http://initd.org/psycopg/download/
- Python version 2.7
- Python 3 versions from 3.4 to 3.6
- PostgreSQL server versions from 7.4 to 10
- PostgreSQL client library version from 9.1
@ -128,8 +44,10 @@ page`__ or from PyPI_.
Build prerequisites
^^^^^^^^^^^^^^^^^^^
These notes illustrate how to compile Psycopg on Linux. If you want to compile
Psycopg on other platforms you may have to adjust some details accordingly.
The build prerequisites are to be met in order to install Psycopg from source
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
it from sources you will need:
@ -161,6 +79,12 @@ it from sources you will need:
Once everything is in place it's just a matter of running the standard:
.. code-block:: console
$ pip install psycopg2
or, from the directory containing the source code:
.. code-block:: console
$ python setup.py build
@ -197,12 +121,92 @@ which is OS-dependent (for instance setting a suitable
.. index::
single: Install; from PyPI
single: Install; wheel
single: Wheel
Binary install from PyPI
------------------------
`!psycopg2` is also `available on PyPI`__ in the form of wheel_ packages for
the most common platform (Linux, OSX, Windows): this should make you able to
install a binary version of the module, not requiring the above build or
runtime prerequisites, simply using:
.. code-block:: console
$ pip install psycopg2-binary
Make sure to use an up-to-date version of :program:`pip` (you can upgrade it
using something like ``pip install -U pip``)
.. __: PyPI-binary_
.. _PyPI-binary: https://pypi.org/project/psycopg2-binary/
.. _wheel: http://pythonwheels.com/
.. note::
The binary packages come with their own versions of a few C libraries,
among which ``libpq`` and ``libssl``, which will be used regardless of other
libraries available on the client: upgrading the system libraries will not
upgrade the libraries used by `!psycopg2`. Please build `!psycopg2` from
source if you want to maintain binary upgradeability.
.. warning::
The `!psycopg2` wheel package comes packaged, among the others, with its
own ``libssl`` binary. This may create conflicts with other extension
modules binding with ``libssl`` as well, for instance with the Python
`ssl` module: in some cases, under concurrency, the interaction between
the two libraries may result in a segfault. In case of doubts you are
advised to use a package built from source.
.. index::
single: Install; disable wheel
single: Wheel; disable
.. _disable-wheel:
Disabling wheel packages for Psycopg 2.7
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In version 2.7.x, `pip install psycopg2` would have tried to install the wheel
binary package of Psycopg. Because of the problems the wheel package have
displayed, `psycopg2-binary` has become a separate package, and from 2.8 it
has become the only way to install the binary package.
If you are using psycopg 2.7 and you want to disable the use of wheel binary
packages, relying on the system system libraries available on your client, you
can use the :command:`pip` |--no-binary option|__, e.g.:
.. code-block:: console
$ pip install --no-binary :all: psycopg2
.. |--no-binary option| replace:: ``--no-binary`` option
.. __: https://pip.pypa.io/en/stable/reference/pip_install/#install-no-binary
which can be specified in your :file:`requirements.txt` files too, e.g. use:
.. code-block:: none
psycopg2>=2.7,<2.8 --no-binary psycopg2
to use the last bugfix release of the `!psycopg2` 2.7 package, specifying to
always compile it from source. Of course in this case you will have to meet
the :ref:`build prerequisites <build-prerequisites>`.
.. index::
single: setup.py
single: setup.cfg
Non-standard builds
^^^^^^^^^^^^^^^^^^^
-------------------
If you have less standard requirements such as:
@ -242,7 +246,7 @@ order to create a debug package:
- Edit the ``setup.cfg`` file adding the ``PSYCOPG_DEBUG`` flag to the
``define`` option.
- :ref:`Compile and install <source-package>` the package.
- :ref:`Compile and install <build-prerequisites>` the package.
- Set the :envvar:`PSYCOPG_DEBUG` environment variable:
@ -300,10 +304,14 @@ Try the following. *In order:*
- Google for `!psycopg2` *your error message*. Especially useful the week
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
ever and about the quality time you have wasted figuring out the correct
: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
=============

View File

@ -24,13 +24,18 @@ directly in the client application.
.. method:: getconn(key=None)
Get a free connection and assign it to *key* if not `!None`.
Get a free connection from the pool.
The *key* parameter is optional: if used, the connection will be
associated to the key and calling `!getconn()` with the same key again
will return the same connection.
.. method:: putconn(conn, key=None, close=False)
Put away a connection.
If *close* is `!True`, discard the connection from the pool.
*key* should be used consistently with `getconn()`.
.. method:: closeall

View File

@ -5,6 +5,7 @@
import os
import sys
def main():
if len(sys.argv) != 3:
sys.stderr.write("usage: %s index.rst text-dir\n")
@ -17,20 +18,20 @@ def main():
return 0
def iter_file_base(fn):
f = open(fn)
have_line = iter(f).__next__
while not have_line().startswith('.. toctree'):
while not next(f).startswith('.. toctree'):
pass
while have_line().strip().startswith(':'):
while next(f).strip().startswith(':'):
pass
yield os.path.splitext(os.path.basename(fn))[0]
n = 0
while True:
line = have_line()
line = next(f)
if line.isspace():
continue
if line.startswith(".."):
@ -44,6 +45,7 @@ def iter_file_base(fn):
# maybe format changed?
raise Exception("Not enough files found. Format change in index.rst?")
def emit(basename, txt_dir):
f = open(os.path.join(txt_dir, basename + ".txt"))
for line in f:

View File

@ -63,7 +63,7 @@ from psycopg2._psycopg import ( # noqa
string_types, binary_types, new_type, new_array_type, register_type,
ISQLQuote, Notify, Diagnostics, Column,
QueryCanceledError, TransactionRollbackError,
set_wait_callback, get_wait_callback, )
set_wait_callback, get_wait_callback, encrypt_password, )
"""Isolation level values."""

View File

@ -344,7 +344,20 @@ class NamedTupleCursor(_cursor):
return
def _make_nt(self):
return namedtuple("Record", [d[0] for d in self.description or ()])
# ascii except alnum and underscore
nochars = ' !"#$%&\'()*+,-./:;<=>?@[\\]^`{|}~'
re_clean = _re.compile('[' + _re.escape(nochars) + ']')
def f(s):
s = re_clean.sub('_', s)
# Python identifier cannot start with numbers, namedtuple fields
# cannot start with underscore. So...
if s[0] == '_' or '0' <= s[0] <= '9':
s = 'f' + s
return s
return namedtuple("Record", [f(d[0]) for d in self.description or ()])
class LoggingConnection(_connection):

View File

@ -100,7 +100,7 @@ _pydatetime_string_delta(pydatetimeObject *self)
char buffer[8];
int i;
int a = obj->microseconds;
int a = PyDateTime_DELTA_GET_MICROSECONDS(obj);
for (i=0; i < 6 ; i++) {
buffer[5-i] = '0' + (a % 10);
@ -109,7 +109,9 @@ _pydatetime_string_delta(pydatetimeObject *self)
buffer[6] = '\0';
return Bytes_FromFormat("'%d days %d.%s seconds'::interval",
obj->days, obj->seconds, buffer);
PyDateTime_DELTA_GET_DAYS(obj),
PyDateTime_DELTA_GET_SECONDS(obj),
buffer);
}
static PyObject *

View File

@ -38,13 +38,14 @@ list_quote(listObject *self)
{
/* adapt the list by calling adapt() recursively and then wrapping
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
* so we use the {NULL,...} syntax. Note however that list of lists where
* some element is a list of only null still fails: for that we should use
* the '{...}' syntax uniformly but we cannot do it in the current
* infrastructure. TODO in psycopg3 */
* so we use the {NULL,...} syntax. The same syntax is also necessary
* to convert array of arrays containing only nulls. */
int all_nulls = 1;
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
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++) {
PyObject *quoted;
for (i = 0; i < len; i++) {
PyObject *wrapped = PyList_GET_ITEM(self->wrapped, i);
if (wrapped == Py_None) {
Py_INCREF(psyco_null);
quoted = psyco_null;
qs[i] = psyco_null;
}
else {
quoted = microprotocol_getquoted(wrapped,
(connectionObject*)self->connection);
if (quoted == NULL) goto error;
all_nulls = 0;
if (!(qs[i] = microprotocol_getquoted(
wrapped, (connectionObject*)self->connection))) {
goto exit;
}
/* 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;
}
}
/* 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);
bufsize += Bytes_GET_SIZE(qs[i]) + 1; /* this, and a comma */
}
/* now that we have a tuple of adapted objects we just need to join them
and put "ARRAY[] around the result */
str = Bytes_FromString(", ");
joined = PyObject_CallMethod(str, "join", "(O)", tmp);
if (joined == NULL) goto error;
/* Create an array literal, usually ARRAY[...] but if the contents are
* all NULL or array of NULL we must use the '{...}' syntax
*/
if (!(ptr = buf = PyMem_Malloc(bufsize + 8))) {
PyErr_NoMemory();
goto exit;
}
/* PG doesn't like ARRAY[NULL..] */
if (!all_nulls) {
res = Bytes_FromFormat("ARRAY[%s]", Bytes_AsString(joined));
} else {
res = Bytes_FromFormat("'{%s}'", Bytes_AsString(joined));
strcpy(ptr, "ARRAY[");
ptr += 6;
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:
Py_XDECREF(tmp);
Py_XDECREF(str);
Py_XDECREF(joined);
res = Bytes_FromStringAndSize(buf, ptr - buf);
exit:
if (qs) {
for (i = 0; i < len; i++) {
PyObject *q = qs[i];
Py_XDECREF(q);
}
PyMem_Free(qs);
}
PyMem_Free(buf);
return res;
}

View File

@ -67,6 +67,9 @@ const char *srv_state_guc[] = {
};
const int SRV_STATE_UNCHANGED = -1;
/* Return a new "string" from a char* from the database.
*
* On Py2 just get a string, on Py3 decode it in the connection codec.
@ -1188,8 +1191,10 @@ conn_set_session(connectionObject *self, int autocommit,
int rv = -1;
PGresult *pgres = NULL;
char *error = NULL;
int want_autocommit = autocommit == SRV_STATE_UNCHANGED ?
self->autocommit : autocommit;
if (deferrable != self->deferrable && self->server_version < 90100) {
if (deferrable != SRV_STATE_UNCHANGED && self->server_version < 90100) {
PyErr_SetString(ProgrammingError,
"the 'deferrable' setting is only available"
" from PostgreSQL 9.1");
@ -1209,24 +1214,24 @@ conn_set_session(connectionObject *self, int autocommit,
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&self->lock);
if (autocommit) {
/* we are in autocommit state, so no BEGIN will be issued:
if (want_autocommit) {
/* we are or are going in autocommit state, so no BEGIN will be issued:
* configure the session with the characteristics requested */
if (isolevel != self->isolevel) {
if (isolevel != SRV_STATE_UNCHANGED) {
if (0 > pq_set_guc_locked(self,
"default_transaction_isolation", srv_isolevels[isolevel],
&pgres, &error, &_save)) {
goto endlock;
}
}
if (readonly != self->readonly) {
if (readonly != SRV_STATE_UNCHANGED) {
if (0 > pq_set_guc_locked(self,
"default_transaction_read_only", srv_state_guc[readonly],
&pgres, &error, &_save)) {
goto endlock;
}
}
if (deferrable != self->deferrable) {
if (deferrable != SRV_STATE_UNCHANGED) {
if (0 > pq_set_guc_locked(self,
"default_transaction_deferrable", srv_state_guc[deferrable],
&pgres, &error, &_save)) {
@ -1251,7 +1256,7 @@ conn_set_session(connectionObject *self, int autocommit,
goto endlock;
}
}
if (self->deferrable != STATE_DEFAULT) {
if (self->server_version >= 90100 && self->deferrable != STATE_DEFAULT) {
if (0 > pq_set_guc_locked(self,
"default_transaction_deferrable", "default",
&pgres, &error, &_save)) {
@ -1260,10 +1265,18 @@ conn_set_session(connectionObject *self, int autocommit,
}
}
self->autocommit = autocommit;
self->isolevel = isolevel;
self->readonly = readonly;
self->deferrable = deferrable;
if (autocommit != SRV_STATE_UNCHANGED) {
self->autocommit = autocommit;
}
if (isolevel != SRV_STATE_UNCHANGED) {
self->isolevel = isolevel;
}
if (readonly != SRV_STATE_UNCHANGED) {
self->readonly = readonly;
}
if (deferrable != SRV_STATE_UNCHANGED) {
self->deferrable = deferrable;
}
rv = 0;
endlock:

View File

@ -39,6 +39,7 @@
extern HIDDEN const char *srv_isolevels[];
extern HIDDEN const char *srv_readonly[];
extern HIDDEN const char *srv_deferrable[];
extern HIDDEN const int SRV_STATE_UNCHANGED;
/** DBAPI methods **/
@ -561,10 +562,10 @@ psyco_conn_set_session(connectionObject *self, PyObject *args, PyObject *kwargs)
PyObject *deferrable = Py_None;
PyObject *autocommit = Py_None;
int c_isolevel = self->isolevel;
int c_readonly = self->readonly;
int c_deferrable = self->deferrable;
int c_autocommit = self->autocommit;
int c_isolevel = SRV_STATE_UNCHANGED;
int c_readonly = SRV_STATE_UNCHANGED;
int c_deferrable = SRV_STATE_UNCHANGED;
int c_autocommit = SRV_STATE_UNCHANGED;
static char *kwlist[] =
{"isolation_level", "readonly", "deferrable", "autocommit", NULL};
@ -637,7 +638,7 @@ psyco_conn_autocommit_set(connectionObject *self, PyObject *pyvalue)
if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
if (-1 == (value = PyObject_IsTrue(pyvalue))) { return -1; }
if (0 > conn_set_session(self, value,
self->isolevel, self->readonly, self->deferrable)) {
SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
return -1;
}
@ -668,8 +669,8 @@ psyco_conn_isolation_level_set(connectionObject *self, PyObject *pyvalue)
if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
if (0 > (value = _psyco_conn_parse_isolevel(pyvalue))) { return -1; }
if (0 > conn_set_session(self, self->autocommit,
value, self->readonly, self->deferrable)) {
if (0 > conn_set_session(self, SRV_STATE_UNCHANGED,
value, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
return -1;
}
@ -715,13 +716,13 @@ psyco_conn_set_isolation_level(connectionObject *self, PyObject *args)
if (level == 0) {
if (0 > conn_set_session(self, 1,
self->isolevel, self->readonly, self->deferrable)) {
SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
return NULL;
}
}
else {
if (0 > conn_set_session(self, 0,
level, self->readonly, self->deferrable)) {
level, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
return NULL;
}
}
@ -767,8 +768,8 @@ psyco_conn_readonly_set(connectionObject *self, PyObject *pyvalue)
if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
if (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; }
if (0 > conn_set_session(self, self->autocommit,
self->isolevel, value, self->deferrable)) {
if (0 > conn_set_session(self, SRV_STATE_UNCHANGED,
SRV_STATE_UNCHANGED, value, SRV_STATE_UNCHANGED)) {
return -1;
}
@ -813,8 +814,8 @@ psyco_conn_deferrable_set(connectionObject *self, PyObject *pyvalue)
if (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
if (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; }
if (0 > conn_set_session(self, self->autocommit,
self->isolevel, self->readonly, value)) {
if (0 > conn_set_session(self, SRV_STATE_UNCHANGED,
SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, value)) {
return -1;
}

View File

@ -59,6 +59,11 @@ psyco_curs_close(cursorObject *self)
char buffer[128];
PGTransactionStatusType status;
if (!self->query) {
Dprintf("skipping named cursor close because unused");
goto close;
}
if (self->conn) {
status = PQtransactionStatus(self->conn->pgconn);
}
@ -66,17 +71,18 @@ psyco_curs_close(cursorObject *self)
status = PQTRANS_UNKNOWN;
}
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 {
if (status == PQTRANS_UNKNOWN || status == PQTRANS_INERROR) {
Dprintf("skipping named curs close because tx status %d",
(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;
Dprintf("psyco_curs_close: cursor at %p closed", self);
@ -592,8 +598,6 @@ psyco_curs_mogrify(cursorObject *self, PyObject *args, PyObject *kwargs)
return NULL;
}
EXC_IF_CURS_CLOSED(self);
return _psyco_curs_mogrify(self, operation, vars);
}

View File

@ -72,6 +72,10 @@ HIDDEN PyObject *psyco_null = NULL;
/* The type of the cursor.description items */
HIDDEN PyObject *psyco_DescriptionType = NULL;
/* macro trick to stringify a macro expansion */
#define xstr(s) str(s)
#define str(s) #s
/** connect module-level function **/
#define psyco_connect_doc \
"_connect(dsn, [connection_factory], [async]) -- New database connection.\n\n"
@ -403,6 +407,105 @@ psyco_libpq_version(PyObject *self)
#endif
}
/* encrypt_password - Prepare the encrypted password form */
#define psyco_encrypt_password_doc \
"encrypt_password(password, user, [scope], [algorithm]) -- Prepares the encrypted form of a PostgreSQL password.\n\n"
static PyObject *
psyco_encrypt_password(PyObject *self, PyObject *args, PyObject *kwargs)
{
char *encrypted = NULL;
PyObject *password = NULL, *user = NULL;
PyObject *scope = Py_None, *algorithm = Py_None;
PyObject *res = NULL;
connectionObject *conn = NULL;
static char *kwlist[] = {"password", "user", "scope", "algorithm", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|OO", kwlist,
&password, &user, &scope, &algorithm)) {
return NULL;
}
/* for ensure_bytes */
Py_INCREF(user);
Py_INCREF(password);
Py_INCREF(algorithm);
if (scope != Py_None) {
if (PyObject_TypeCheck(scope, &cursorType)) {
conn = ((cursorObject*)scope)->conn;
}
else if (PyObject_TypeCheck(scope, &connectionType)) {
conn = (connectionObject*)scope;
}
else {
PyErr_SetString(PyExc_TypeError,
"the scope must be a connection or a cursor");
goto exit;
}
}
if (!(user = psycopg_ensure_bytes(user))) { goto exit; }
if (!(password = psycopg_ensure_bytes(password))) { goto exit; }
if (algorithm != Py_None) {
if (!(algorithm = psycopg_ensure_bytes(algorithm))) {
goto exit;
}
}
/* If we have to encrypt md5 we can use the libpq < 10 API */
if (algorithm != Py_None &&
strcmp(Bytes_AS_STRING(algorithm), "md5") == 0) {
encrypted = PQencryptPassword(
Bytes_AS_STRING(password), Bytes_AS_STRING(user));
}
/* If the algorithm is not md5 we have to use the API available from
* libpq 10. */
else {
#if PG_VERSION_NUM >= 100000
if (!conn) {
PyErr_SetString(ProgrammingError,
"password encryption (other than 'md5' algorithm)"
" requires a connection or cursor");
goto exit;
}
/* TODO: algo = None will block: forbid on async/green conn? */
encrypted = PQencryptPasswordConn(conn->pgconn,
Bytes_AS_STRING(password), Bytes_AS_STRING(user),
algorithm != Py_None ? Bytes_AS_STRING(algorithm) : NULL);
#else
PyErr_SetString(NotSupportedError,
"password encryption (other than 'md5' algorithm)"
" requires libpq 10");
goto exit;
#endif
}
if (encrypted) {
res = Text_FromUTF8(encrypted);
}
else {
const char *msg = PQerrorMessage(conn->pgconn);
PyErr_Format(ProgrammingError,
"password encryption failed: %s", msg ? msg : "no reason given");
goto exit;
}
exit:
if (encrypted) {
PQfreemem(encrypted);
}
Py_XDECREF(user);
Py_XDECREF(password);
Py_XDECREF(algorithm);
return res;
}
/* psyco_encodings_fill
Fill the module's postgresql<->python encoding table */
@ -852,6 +955,8 @@ static PyMethodDef psycopgMethods[] = {
METH_O, psyco_set_wait_callback_doc},
{"get_wait_callback", (PyCFunction)psyco_get_wait_callback,
METH_NOARGS, psyco_get_wait_callback_doc},
{"encrypt_password", (PyCFunction)psyco_encrypt_password,
METH_VARARGS|METH_KEYWORDS, psyco_encrypt_password_doc},
{NULL, NULL, 0, NULL} /* Sentinel */
};
@ -885,7 +990,7 @@ INIT_MODULE(_psycopg)(void)
psycopg_debug_enabled = 1;
#endif
Dprintf("initpsycopg: initializing psycopg %s", PSYCOPG_VERSION);
Dprintf("initpsycopg: initializing psycopg %s", xstr(PSYCOPG_VERSION));
/* initialize all the new types and then the module */
Py_TYPE(&connectionType) = &PyType_Type;
@ -1017,7 +1122,7 @@ INIT_MODULE(_psycopg)(void)
if (!(psyco_DescriptionType = psyco_make_description_type())) { goto exit; }
/* set some module's parameters */
PyModule_AddStringConstant(module, "__version__", PSYCOPG_VERSION);
PyModule_AddStringConstant(module, "__version__", xstr(PSYCOPG_VERSION));
PyModule_AddStringConstant(module, "__doc__", "psycopg PostgreSQL driver");
PyModule_AddIntConstant(module, "__libpq_version__", PG_VERSION_NUM);
PyModule_AddIntMacro(module, REPLICATION_PHYSICAL);

View File

@ -87,6 +87,7 @@ typedef unsigned long Py_uhash_t;
#ifndef PyNumber_Int
#define PyNumber_Int PyNumber_Long
#endif
#endif /* PY_MAJOR_VERSION > 2 */
#if PY_MAJOR_VERSION < 3
@ -104,6 +105,10 @@ typedef unsigned long Py_uhash_t;
#define Bytes_ConcatAndDel PyString_ConcatAndDel
#define _Bytes_Resize _PyString_Resize
#define PyDateTime_DELTA_GET_DAYS(o) (((PyDateTime_Delta*)o)->days)
#define PyDateTime_DELTA_GET_SECONDS(o) (((PyDateTime_Delta*)o)->seconds)
#define PyDateTime_DELTA_GET_MICROSECONDS(o) (((PyDateTime_Delta*)o)->microseconds)
#else
#define Bytes_Type PyBytes_Type

View File

@ -1,6 +1,7 @@
/* solaris_support.c - emulate functions missing on Solaris
*
* Copyright (C) 2017 My Karlsson <mk@acc.umu.se>
* Copyright (c) 2018, Joyent, Inc.
*
* This file is part of psycopg.
*
@ -28,7 +29,8 @@
#include "psycopg/solaris_support.h"
#if defined(__sun) && defined(__SVR4)
/* timeradd is missing on Solaris */
/* timeradd is missing on Solaris 10 */
#ifndef timeradd
void
timeradd(struct timeval *a, struct timeval *b, struct timeval *c)
{
@ -51,4 +53,5 @@ timersub(struct timeval *a, struct timeval *b, struct timeval *c)
c->tv_sec -= 1;
}
}
#endif /* timeradd */
#endif /* defined(__sun) && defined(__SVR4) */

View File

@ -1,6 +1,7 @@
/* solaris_support.h - definitions for solaris_support.c
*
* Copyright (C) 2017 My Karlsson <mk@acc.umu.se>
* Copyright (c) 2018, Joyent, Inc.
*
* This file is part of psycopg.
*
@ -30,8 +31,10 @@
#if defined(__sun) && defined(__SVR4)
#include <sys/time.h>
#ifndef timeradd
extern HIDDEN void timeradd(struct timeval *a, struct timeval *b, struct timeval *c);
extern HIDDEN void timersub(struct timeval *a, struct timeval *b, struct timeval *c);
#endif
#endif
#endif /* !defined(PSYCOPG_SOLARIS_SUPPORT_H) */

View File

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

View File

@ -168,11 +168,11 @@ psycopg_ensure_bytes(PyObject *obj)
PyObject *rv = NULL;
if (!obj) { return NULL; }
if (PyUnicode_CheckExact(obj)) {
if (PyUnicode_Check(obj)) {
rv = PyUnicode_AsUTF8String(obj);
Py_DECREF(obj);
}
else if (Bytes_CheckExact(obj)) {
else if (Bytes_Check(obj)) {
rv = obj;
}
else {
@ -282,7 +282,7 @@ exit:
/* Make a connection string out of a string and a dictionary of arguments.
*
* Helper to call psycopg2.extensions.make_dns()
* Helper to call psycopg2.extensions.make_dsn()
*/
PyObject *
psycopg_make_dsn(PyObject *dsn, PyObject *kwargs)

View File

@ -9,7 +9,7 @@ To invalidate the cache, update this file and check it into git.
Currently used modules built in the cache:
OpenSSL
Version: 1.0.2m
Version: 1.0.2n
PostgreSQL
Version: 10.1

View File

@ -56,15 +56,15 @@ fi
# Unsupported postgres versions that we still support
# Images built by https://github.com/psycopg/psycopg2-wheels/tree/build-dinosaurs
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.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
# Postgres built from master

View File

@ -39,6 +39,7 @@ except ImportError:
from distutils.command.build_ext import build_ext
from distutils.sysconfig import get_python_inc
from distutils.ccompiler import get_default_compiler
from distutils.errors import CompileError
from distutils.util import get_platform
try:
@ -89,15 +90,23 @@ class PostgresConfig:
if not self.pg_config_exe:
self.pg_config_exe = self.autodetect_pg_config_path()
if self.pg_config_exe is None:
sys.stderr.write("""\
sys.stderr.write("""
Error: pg_config executable not found.
Please add the directory containing pg_config to the PATH
or specify the full executable path with the option:
pg_config is required to build psycopg2 from source. Please add the directory
containing pg_config to the $PATH or specify the full executable path with the
option:
python setup.py build_ext --pg-config /path/to/pg_config build ...
or with the pg_config option in 'setup.cfg'.
If you prefer to avoid building psycopg2 from source, please install the PyPI
'psycopg2-binary' package instead.
For further information please check the 'doc/src/install.rst' file (also at
<http://initd.org/psycopg/docs/install.html>).
""")
sys.exit(1)
@ -271,8 +280,37 @@ class psycopg_build_ext(build_ext):
else:
return build_ext.get_export_symbols(self, extension)
built_files = 0
def build_extension(self, extension):
build_ext.build_extension(self, extension)
# Count files compiled to print the binary blurb only if the first fails
compile_orig = getattr(self.compiler, '_compile', None)
if compile_orig is not None:
def _compile(*args, **kwargs):
rv = compile_orig(*args, **kwargs)
psycopg_build_ext.built_files += 1
return rv
self.compiler._compile = _compile
try:
build_ext.build_extension(self, extension)
psycopg_build_ext.built_files += 1
except CompileError:
if self.built_files == 0:
sys.stderr.write("""
It appears you are missing some prerequisite to build the package from source.
You may install a binary package by installing 'psycopg2-binary' from PyPI.
If you want to install psycopg2 from source, please install the packages
required for the build and try again.
For further information please check the 'doc/src/install.rst' file (also at
<http://initd.org/psycopg/docs/install.html>).
""")
raise
sysVer = sys.version_info[:2]
# For Python versions that use MSVC compiler 2008, re-insert the
@ -543,10 +581,7 @@ if version_flags:
else:
PSYCOPG_VERSION_EX = PSYCOPG_VERSION
if not PLATFORM_IS_WINDOWS:
define_macros.append(('PSYCOPG_VERSION', '"' + PSYCOPG_VERSION_EX + '"'))
else:
define_macros.append(('PSYCOPG_VERSION', '\\"' + PSYCOPG_VERSION_EX + '\\"'))
define_macros.append(('PSYCOPG_VERSION', PSYCOPG_VERSION_EX))
if parser.has_option('build_ext', 'have_ssl'):
have_ssl = int(parser.get('build_ext', 'have_ssl'))

View File

@ -37,7 +37,9 @@ from psycopg2 import extensions as ext
from .testutils import (
unittest, decorate_all_tests, skip_if_no_superuser,
skip_before_postgres, skip_after_postgres, skip_before_libpq,
ConnectingTestCase, skip_if_tpc_disabled, skip_if_windows, slow)
ConnectingTestCase, skip_if_tpc_disabled, skip_if_windows, slow,
libpq_version
)
from .testconfig import dsn, dbname
@ -246,6 +248,13 @@ class ConnectionTests(ConnectingTestCase):
else:
del os.environ['PGCLIENTENCODING']
def test_connect_no_string(self):
class MyString(str):
pass
conn = psycopg2.connect(MyString(dsn))
conn.close()
def test_weakref(self):
from weakref import ref
import gc
@ -400,6 +409,13 @@ class ParseDsnTestCase(ConnectingTestCase):
self.assertRaises(TypeError, ext.parse_dsn, None)
self.assertRaises(TypeError, ext.parse_dsn, 42)
def test_str_subclass(self):
class MyString(str):
pass
res = ext.parse_dsn(MyString("dbname=test"))
self.assertEqual(res, {'dbname': 'test'})
class MakeDsnTestCase(ConnectingTestCase):
def test_empty_arguments(self):
@ -1381,6 +1397,102 @@ class TransactionControlTests(ConnectingTestCase):
cur.execute("SHOW default_transaction_read_only;")
self.assertEqual(cur.fetchone()[0], 'off')
def test_idempotence_check(self):
self.conn.autocommit = False
self.conn.readonly = True
self.conn.autocommit = True
self.conn.readonly = True
cur = self.conn.cursor()
cur.execute("SHOW transaction_read_only")
self.assertEqual(cur.fetchone()[0], 'on')
class TestEncryptPassword(ConnectingTestCase):
@skip_before_postgres(10)
def test_encrypt_password_post_9_6(self):
cur = self.conn.cursor()
cur.execute("SHOW password_encryption;")
server_encryption_algorithm = cur.fetchone()[0]
# MD5 algorithm
self.assertEqual(
ext.encrypt_password('psycopg2', 'ashesh', self.conn, 'md5'),
'md594839d658c28a357126f105b9cb14cfc'
)
# keywords
self.assertEqual(
ext.encrypt_password(
password='psycopg2', user='ashesh',
scope=self.conn, algorithm='md5'),
'md594839d658c28a357126f105b9cb14cfc'
)
if libpq_version() < 100000:
self.assertRaises(
psycopg2.NotSupportedError,
ext.encrypt_password, 'psycopg2', 'ashesh', self.conn,
'scram-sha-256'
)
else:
enc_password = ext.encrypt_password(
'psycopg2', 'ashesh', self.conn
)
if server_encryption_algorithm == 'md5':
self.assertEqual(
enc_password, 'md594839d658c28a357126f105b9cb14cfc'
)
elif server_encryption_algorithm == 'scram-sha-256':
self.assertEqual(enc_password[:14], 'SCRAM-SHA-256$')
self.assertEqual(
ext.encrypt_password(
'psycopg2', 'ashesh', self.conn, 'scram-sha-256'
)[:14], 'SCRAM-SHA-256$'
)
self.assertRaises(psycopg2.ProgrammingError,
ext.encrypt_password, 'psycopg2', 'ashesh', self.conn, 'abc')
@skip_after_postgres(10)
def test_encrypt_password_pre_10(self):
self.assertEqual(
ext.encrypt_password('psycopg2', 'ashesh', self.conn),
'md594839d658c28a357126f105b9cb14cfc'
)
self.assertRaises(psycopg2.ProgrammingError,
ext.encrypt_password, 'psycopg2', 'ashesh', self.conn, 'abc')
def test_encrypt_md5(self):
self.assertEqual(
ext.encrypt_password('psycopg2', 'ashesh', algorithm='md5'),
'md594839d658c28a357126f105b9cb14cfc'
)
def test_encrypt_scram(self):
if libpq_version() >= 100000:
self.assert_(
ext.encrypt_password(
'psycopg2', 'ashesh', self.conn, 'scram-sha-256')
.startswith('SCRAM-SHA-256$'))
else:
self.assertRaises(psycopg2.NotSupportedError,
ext.encrypt_password,
password='psycopg2', user='ashesh',
scope=self.conn, algorithm='scram-sha-256')
def test_bad_types(self):
self.assertRaises(TypeError, ext.encrypt_password)
self.assertRaises(TypeError, ext.encrypt_password,
'password', 42, self.conn, 'md5')
self.assertRaises(TypeError, ext.encrypt_password,
42, 'user', self.conn, 'md5')
self.assertRaises(TypeError, ext.encrypt_password,
42, 'user', 'wat', 'abc')
self.assertRaises(TypeError, ext.encrypt_password,
'password', 'user', 'wat', 42)
class AutocommitTests(ConnectingTestCase):
def test_closed(self):
@ -1539,9 +1651,13 @@ import os
import sys
import time
import signal
import warnings
import threading
import psycopg2
# ignore wheel deprecation warning
with warnings.catch_warnings():
warnings.simplefilter('ignore')
import psycopg2
def handle_sigabort(sig, frame):
sys.exit(1)

View File

@ -119,6 +119,12 @@ class CursorTests(ConnectingTestCase):
nref2 = sys.getrefcount(foo)
self.assertEqual(nref1, nref2)
def test_modify_closed(self):
cur = self.conn.cursor()
cur.close()
sql = cur.mogrify("select %s", (10,))
self.assertEqual(sql, b"select 10")
def test_bad_placeholder(self):
cur = self.conn.cursor()
self.assertRaises(psycopg2.ProgrammingError,
@ -430,6 +436,11 @@ class CursorTests(ConnectingTestCase):
self.assertEqual([(2,), (3,), (4,)], cur2.fetchmany(3))
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)
def test_scroll(self):
cur = self.conn.cursor()

View File

@ -438,6 +438,14 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
r = cur.fetchone()[0]
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.
if not hasattr(psycopg2.extensions, 'PYDATETIME'):
@ -639,7 +647,8 @@ class FromTicksTestCase(unittest.TestCase):
def test_date_value_error_sec_59_99(self):
from datetime import date
s = psycopg2.DateFromTicks(1273173119.99992)
self.assertEqual(s.adapted, date(2010, 5, 6))
# The returned date is local
self.assert_(s.adapted in [date(2010, 5, 6), date(2010, 5, 7)])
def test_time_value_error_sec_59_99(self):
from datetime import time

View File

@ -19,12 +19,11 @@ from datetime import timedelta
import psycopg2
import psycopg2.extras
import unittest
from .testutils import ConnectingTestCase, skip_before_postgres
from .testutils import ConnectingTestCase, skip_before_postgres, \
skip_before_python, skip_from_python
class ExtrasDictCursorTests(ConnectingTestCase):
"""Test if DictCursor extension class works."""
class _DictCursorBase(ConnectingTestCase):
def setUp(self):
ConnectingTestCase.setUp(self)
curs = self.conn.cursor()
@ -32,6 +31,30 @@ class ExtrasDictCursorTests(ConnectingTestCase):
curs.execute("INSERT INTO ExtrasDictCursorTests VALUES ('bar')")
self.conn.commit()
def _testIterRowNumber(self, curs):
# Only checking for dataset < itersize:
# see CursorTests.test_iter_named_cursor_rownumber
curs.itersize = 20
curs.execute("""select * from generate_series(1,10)""")
for i, r in enumerate(curs):
self.assertEqual(i + 1, curs.rownumber)
def _testNamedCursorNotGreedy(self, curs):
curs.itersize = 2
curs.execute("""select clock_timestamp() as ts from generate_series(1,3)""")
recs = []
for t in curs:
time.sleep(0.01)
recs.append(t)
# check that the dataset was not fetched in a single gulp
self.assert_(recs[1]['ts'] - recs[0]['ts'] < timedelta(seconds=0.005))
self.assert_(recs[2]['ts'] - recs[1]['ts'] > timedelta(seconds=0.0099))
class ExtrasDictCursorTests(_DictCursorBase):
"""Test if DictCursor extension class works."""
def testDictConnCursorArgs(self):
self.conn.close()
self.conn = self.connect(connection_factory=psycopg2.extras.DictConnection)
@ -81,35 +104,6 @@ class ExtrasDictCursorTests(ConnectingTestCase):
self.failUnless(row[0] == 'bar')
return row
def testDictCursorWithPlainCursorRealFetchOne(self):
self._testWithPlainCursorReal(lambda curs: curs.fetchone())
def testDictCursorWithPlainCursorRealFetchMany(self):
self._testWithPlainCursorReal(lambda curs: curs.fetchmany(100)[0])
def testDictCursorWithPlainCursorRealFetchManyNoarg(self):
self._testWithPlainCursorReal(lambda curs: curs.fetchmany()[0])
def testDictCursorWithPlainCursorRealFetchAll(self):
self._testWithPlainCursorReal(lambda curs: curs.fetchall()[0])
def testDictCursorWithPlainCursorRealIter(self):
def getter(curs):
for row in curs:
return row
self._testWithPlainCursorReal(getter)
@skip_before_postgres(8, 0)
def testDictCursorWithPlainCursorRealIterRowNumber(self):
curs = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
self._testIterRowNumber(curs)
def _testWithPlainCursorReal(self, getter):
curs = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
curs.execute("SELECT * FROM ExtrasDictCursorTests")
row = getter(curs)
self.failUnless(row['foo'] == 'bar')
def testDictCursorWithNamedCursorFetchOne(self):
self._testWithNamedCursor(lambda curs: curs.fetchone())
@ -145,6 +139,94 @@ class ExtrasDictCursorTests(ConnectingTestCase):
self.failUnless(row['foo'] == 'bar')
self.failUnless(row[0] == 'bar')
def testPickleDictRow(self):
import pickle
curs = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
curs.execute("select 10 as a, 20 as b")
r = curs.fetchone()
d = pickle.dumps(r)
r1 = pickle.loads(d)
self.assertEqual(r, r1)
self.assertEqual(r[0], r1[0])
self.assertEqual(r[1], r1[1])
self.assertEqual(r['a'], r1['a'])
self.assertEqual(r['b'], r1['b'])
self.assertEqual(r._index, r1._index)
@skip_from_python(3)
def test_iter_methods_2(self):
curs = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
curs.execute("select 10 as a, 20 as b")
r = curs.fetchone()
self.assert_(isinstance(r.keys(), list))
self.assertEqual(len(r.keys()), 2)
self.assert_(isinstance(r.values(), tuple)) # sic?
self.assertEqual(len(r.values()), 2)
self.assert_(isinstance(r.items(), list))
self.assertEqual(len(r.items()), 2)
self.assert_(not isinstance(r.iterkeys(), list))
self.assertEqual(len(list(r.iterkeys())), 2)
self.assert_(not isinstance(r.itervalues(), list))
self.assertEqual(len(list(r.itervalues())), 2)
self.assert_(not isinstance(r.iteritems(), list))
self.assertEqual(len(list(r.iteritems())), 2)
@skip_before_python(3)
def test_iter_methods_3(self):
curs = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
curs.execute("select 10 as a, 20 as b")
r = curs.fetchone()
self.assert_(not isinstance(r.keys(), list))
self.assertEqual(len(list(r.keys())), 2)
self.assert_(not isinstance(r.values(), list))
self.assertEqual(len(list(r.values())), 2)
self.assert_(not isinstance(r.items(), list))
self.assertEqual(len(list(r.items())), 2)
class ExtrasDictCursorRealTests(_DictCursorBase):
def testDictCursorWithPlainCursorRealFetchOne(self):
self._testWithPlainCursorReal(lambda curs: curs.fetchone())
def testDictCursorWithPlainCursorRealFetchMany(self):
self._testWithPlainCursorReal(lambda curs: curs.fetchmany(100)[0])
def testDictCursorWithPlainCursorRealFetchManyNoarg(self):
self._testWithPlainCursorReal(lambda curs: curs.fetchmany()[0])
def testDictCursorWithPlainCursorRealFetchAll(self):
self._testWithPlainCursorReal(lambda curs: curs.fetchall()[0])
def testDictCursorWithPlainCursorRealIter(self):
def getter(curs):
for row in curs:
return row
self._testWithPlainCursorReal(getter)
@skip_before_postgres(8, 0)
def testDictCursorWithPlainCursorRealIterRowNumber(self):
curs = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
self._testIterRowNumber(curs)
def _testWithPlainCursorReal(self, getter):
curs = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
curs.execute("SELECT * FROM ExtrasDictCursorTests")
row = getter(curs)
self.failUnless(row['foo'] == 'bar')
def testPickleRealDictRow(self):
import pickle
curs = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
curs.execute("select 10 as a, 20 as b")
r = curs.fetchone()
d = pickle.dumps(r)
r1 = pickle.loads(d)
self.assertEqual(r, r1)
self.assertEqual(r['a'], r1['a'])
self.assertEqual(r['b'], r1['b'])
self.assertEqual(r._column_mapping, r1._column_mapping)
def testDictCursorRealWithNamedCursorFetchOne(self):
self._testWithNamedCursorReal(lambda curs: curs.fetchone())
@ -180,51 +262,36 @@ class ExtrasDictCursorTests(ConnectingTestCase):
row = getter(curs)
self.failUnless(row['foo'] == 'bar')
def _testNamedCursorNotGreedy(self, curs):
curs.itersize = 2
curs.execute("""select clock_timestamp() as ts from generate_series(1,3)""")
recs = []
for t in curs:
time.sleep(0.01)
recs.append(t)
# check that the dataset was not fetched in a single gulp
self.assert_(recs[1]['ts'] - recs[0]['ts'] < timedelta(seconds=0.005))
self.assert_(recs[2]['ts'] - recs[1]['ts'] > timedelta(seconds=0.0099))
def _testIterRowNumber(self, curs):
# Only checking for dataset < itersize:
# see CursorTests.test_iter_named_cursor_rownumber
curs.itersize = 20
curs.execute("""select * from generate_series(1,10)""")
for i, r in enumerate(curs):
self.assertEqual(i + 1, curs.rownumber)
def testPickleDictRow(self):
import pickle
curs = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
curs.execute("select 10 as a, 20 as b")
r = curs.fetchone()
d = pickle.dumps(r)
r1 = pickle.loads(d)
self.assertEqual(r, r1)
self.assertEqual(r[0], r1[0])
self.assertEqual(r[1], r1[1])
self.assertEqual(r['a'], r1['a'])
self.assertEqual(r['b'], r1['b'])
self.assertEqual(r._index, r1._index)
def testPickleRealDictRow(self):
import pickle
@skip_from_python(3)
def test_iter_methods_2(self):
curs = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
curs.execute("select 10 as a, 20 as b")
r = curs.fetchone()
d = pickle.dumps(r)
r1 = pickle.loads(d)
self.assertEqual(r, r1)
self.assertEqual(r['a'], r1['a'])
self.assertEqual(r['b'], r1['b'])
self.assertEqual(r._column_mapping, r1._column_mapping)
self.assert_(isinstance(r.keys(), list))
self.assertEqual(len(r.keys()), 2)
self.assert_(isinstance(r.values(), list))
self.assertEqual(len(r.values()), 2)
self.assert_(isinstance(r.items(), list))
self.assertEqual(len(r.items()), 2)
self.assert_(not isinstance(r.iterkeys(), list))
self.assertEqual(len(list(r.iterkeys())), 2)
self.assert_(not isinstance(r.itervalues(), list))
self.assertEqual(len(list(r.itervalues())), 2)
self.assert_(not isinstance(r.iteritems(), list))
self.assertEqual(len(list(r.iteritems())), 2)
@skip_before_python(3)
def test_iter_methods_3(self):
curs = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
curs.execute("select 10 as a, 20 as b")
r = curs.fetchone()
self.assert_(not isinstance(r.keys(), list))
self.assertEqual(len(list(r.keys())), 2)
self.assert_(not isinstance(r.values(), list))
self.assertEqual(len(list(r.values())), 2)
self.assert_(not isinstance(r.items(), list))
self.assertEqual(len(list(r.items())), 2)
class NamedTupleCursorTest(ConnectingTestCase):
@ -349,6 +416,22 @@ class NamedTupleCursorTest(ConnectingTestCase):
curs.execute("update nttest set s = s")
self.assertRaises(psycopg2.ProgrammingError, curs.fetchall)
def test_bad_col_names(self):
curs = self.conn.cursor()
curs.execute('select 1 as "foo.bar_baz", 2 as "?column?", 3 as "3"')
rv = curs.fetchone()
self.assertEqual(rv.foo_bar_baz, 1)
self.assertEqual(rv.f_column_, 2)
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):
# Instrument the class to verify it gets called the minimum number of times.
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)", ([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)", ([[None, None]],))
# curs.execute("insert into na (intaa) values (%s)", ([[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)", ([[42, 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 (intaa) values (%s)", ([[None, 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)", ([[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)
def testTypeRoundtripBuffer(self):

View File

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

View File

@ -26,7 +26,7 @@ import psycopg2
import psycopg2.extensions as ext
import unittest
from .testutils import ConnectingTestCase
from .testutils import ConnectingTestCase, skip_before_postgres
class WithTestCase(ConnectingTestCase):
@ -215,6 +215,11 @@ class WithCursorTestCase(WithTestCase):
else:
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():
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]
envlist = py27
envlist = py{27,34,35,36}
[testenv]
commands = make check
whitelist_externals = make
[flake8]
max-line-length = 85