mirror of
https://github.com/psycopg/psycopg2.git
synced 2025-02-07 21:00:33 +03:00
Merge branch 'master' into drop-2to3
This commit is contained in:
commit
e8a831dda2
|
@ -13,43 +13,17 @@ environment:
|
||||||
matrix:
|
matrix:
|
||||||
# For Python versions available on Appveyor, see
|
# For Python versions available on Appveyor, see
|
||||||
# http://www.appveyor.com/docs/installed-software#python
|
# 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)
|
OPENSSL_VERSION: "1_0_2n"
|
||||||
# Py 3.4 = VS Ver. 10.0 (VS 2010)
|
POSTGRES_VERSION: "10_1"
|
||||||
# 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
|
|
||||||
|
|
||||||
PSYCOPG2_TESTDB: psycopg2_test
|
PSYCOPG2_TESTDB: psycopg2_test
|
||||||
PSYCOPG2_TESTDB_USER: postgres
|
PSYCOPG2_TESTDB_USER: postgres
|
||||||
|
@ -77,7 +51,22 @@ cache:
|
||||||
init:
|
init:
|
||||||
# Uncomment next line to get RDP access during the build.
|
# 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'))
|
#- 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 Python to the path
|
||||||
- SET PATH=%PYTHON%;%PYTHON%\Scripts;C:\Program Files\Git\mingw64\bin;%PATH%
|
- SET PATH=%PYTHON%;%PYTHON%\Scripts;C:\Program Files\Git\mingw64\bin;%PATH%
|
||||||
|
|
||||||
|
@ -153,8 +142,8 @@ install:
|
||||||
}
|
}
|
||||||
# Download OpenSSL source
|
# Download OpenSSL source
|
||||||
- CD C:\Others
|
- CD C:\Others
|
||||||
- IF NOT EXIST OpenSSL_1_0_2m.zip (
|
- IF NOT EXIST OpenSSL_%OPENSSL_VERSION%.zip (
|
||||||
curl -fsSL -o OpenSSL_1_0_2m.zip https://github.com/openssl/openssl/archive/OpenSSL_1_0_2m.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
|
# To use OpenSSL >= 1.1.0, both libpq and psycopg build environments have
|
||||||
|
@ -166,15 +155,15 @@ install:
|
||||||
# - nmake build_libs install_dev
|
# - nmake build_libs install_dev
|
||||||
- IF NOT EXIST %OPENSSLTOP%\lib\ssleay32.lib (
|
- IF NOT EXIST %OPENSSLTOP%\lib\ssleay32.lib (
|
||||||
CD %BUILD_DIR% &&
|
CD %BUILD_DIR% &&
|
||||||
7z x C:\Others\OpenSSL_1_0_2m.zip &&
|
7z x C:\Others\OpenSSL_%OPENSSL_VERSION%.zip &&
|
||||||
CD openssl-OpenSSL_1_0_2m &&
|
CD openssl-OpenSSL_%OPENSSL_VERSION% &&
|
||||||
perl Configure %TARGET% no-asm no-shared no-zlib --prefix=%OPENSSLTOP% --openssldir=%OPENSSLTOP% &&
|
perl Configure %TARGET% no-asm no-shared no-zlib --prefix=%OPENSSLTOP% --openssldir=%OPENSSLTOP% &&
|
||||||
CALL ms\%DO% &&
|
CALL ms\%DO% &&
|
||||||
nmake -f ms\nt.mak init headers lib &&
|
nmake -f ms\nt.mak init headers lib &&
|
||||||
COPY inc32\openssl\*.h %OPENSSLTOP%\include\openssl &&
|
COPY inc32\openssl\*.h %OPENSSLTOP%\include\openssl &&
|
||||||
COPY out32\*.lib %OPENSSLTOP%\lib &&
|
COPY out32\*.lib %OPENSSLTOP%\lib &&
|
||||||
CD %BASE_DIR% &&
|
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
|
# Setup directories for building PostgreSQL librarires
|
||||||
|
@ -188,8 +177,8 @@ install:
|
||||||
|
|
||||||
# Download PostgreSQL source
|
# Download PostgreSQL source
|
||||||
- CD C:\Others
|
- CD C:\Others
|
||||||
- IF NOT EXIST postgres-REL_10_1.zip (
|
- IF NOT EXIST postgres-REL_%POSTGRES_VERSION%.zip (
|
||||||
curl -fsSL -o postgres-REL_10_1.zip https://github.com/postgres/postgres/archive/REL_10_1.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)
|
# Setup build config file (config.pl)
|
||||||
|
@ -200,11 +189,11 @@ install:
|
||||||
# Prepare local include directory for building from
|
# Prepare local include directory for building from
|
||||||
# Build pg_config in place
|
# Build pg_config in place
|
||||||
# NOTE: Cannot set and use the same variable inside an IF
|
# 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 (
|
- IF NOT EXIST %PGTOP%\lib\libpq.lib (
|
||||||
CD %BUILD_DIR% &&
|
CD %BUILD_DIR% &&
|
||||||
7z x C:\Others\postgres-REL_10_1.zip &&
|
7z x C:\Others\postgres-REL_%POSTGRES_VERSION%.zip &&
|
||||||
CD postgres-REL_10_1\src\tools\msvc &&
|
CD postgres-REL_%POSTGRES_VERSION%\src\tools\msvc &&
|
||||||
ECHO $config-^>{ldap} = 0; > config.pl &&
|
ECHO $config-^>{ldap} = 0; > config.pl &&
|
||||||
ECHO $config-^>{openssl} = "%OPENSSLTOP:\=\\%"; >> config.pl &&
|
ECHO $config-^>{openssl} = "%OPENSSLTOP:\=\\%"; >> config.pl &&
|
||||||
ECHO.>> config.pl &&
|
ECHO.>> config.pl &&
|
||||||
|
|
|
@ -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
10
LICENSE
|
@ -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
|
||||||
|
|
34
NEWS
34
NEWS
|
@ -4,6 +4,10 @@ Current release
|
||||||
What's new in psycopg 2.8
|
What's new in psycopg 2.8
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
New features:
|
||||||
|
|
||||||
|
- Added `~psycopg2.extensions.encrypt_password()` function (:ticket:`#576`).
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
|
|
||||||
- Dropped support for Python 2.6, 3.2, 3.3.
|
- Dropped support for Python 2.6, 3.2, 3.3.
|
||||||
|
@ -15,17 +19,41 @@ Other changes:
|
||||||
install``.
|
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
|
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`).
|
- 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 `~psycopg2.extras.MinTimeLoggingCursor` on Python 3 (:ticket:`#609`).
|
||||||
- Fixed parsing of array of points as floats (:ticket:`#613`).
|
- Fixed parsing of array of points as floats (:ticket:`#613`).
|
||||||
- Fixed `~psycopg2.__libpq_version__` building with libpq >= 10.1
|
- Fixed `~psycopg2.__libpq_version__` building with libpq >= 10.1
|
||||||
(:ticket:`632`).
|
(:ticket:`632`).
|
||||||
- Fixed `~cursor.rowcount` after `~cursor.executemany()` with :sql:`RETURNING` statements
|
- Fixed `~cursor.rowcount` after `~cursor.executemany()` with :sql:`RETURNING`
|
||||||
(:ticket:`633`).
|
statements (:ticket:`633`).
|
||||||
- Wheel packages compiled against PostgreSQL 10.1 libpq and OpenSSL 1.0.2m.
|
- 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
|
What's new in psycopg 2.7.3.2
|
||||||
|
|
35
README.rst
35
README.rst
|
@ -25,29 +25,40 @@ Documentation is included in the ``doc`` directory and is `available online`__.
|
||||||
|
|
||||||
.. __: http://initd.org/psycopg/docs/
|
.. __: http://initd.org/psycopg/docs/
|
||||||
|
|
||||||
|
For any other resource (source code repository, bug tracker, mailing list)
|
||||||
|
please check the `project homepage`__.
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
If your ``pip`` version supports wheel_ packages it should be possible to
|
Building Psycopg requires a few prerequisites (a C compiler, some development
|
||||||
install a binary version of Psycopg including all the dependencies from PyPI_.
|
packages): please check the install_ and the faq_ documents in the ``doc`` dir
|
||||||
Just run::
|
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
|
$ pip install psycopg2
|
||||||
|
|
||||||
If you want to build Psycopg from source you will need some prerequisites (a C
|
or using ``setup.py`` if you have downloaded the source package locally::
|
||||||
compiler, development packages): please check the install_ and the faq_
|
|
||||||
documents in the ``doc`` dir for the details.
|
|
||||||
|
|
||||||
.. _wheel: http://pythonwheels.com/
|
$ python setup.py build
|
||||||
.. _PyPI: https://pypi.python.org/pypi/psycopg2
|
$ 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
|
.. _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
|
||||||
|
|
||||||
For any other resource (source code repository, bug tracker, mailing list)
|
|
||||||
please check the `project homepage`__.
|
|
||||||
|
|
||||||
.. __: http://initd.org/psycopg/
|
.. __: http://initd.org/psycopg/
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 $@
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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/
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -41,11 +41,6 @@ The ``connection`` class
|
||||||
previously only valid PostgreSQL identifiers were accepted as
|
previously only valid PostgreSQL identifiers were accepted as
|
||||||
cursor name.
|
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
|
The *cursor_factory* argument can be used to create non-standard
|
||||||
cursors. The class returned must be a subclass of
|
cursors. The class returned must be a subclass of
|
||||||
`psycopg2.extensions.cursor`. See :ref:`subclassing-cursor` for
|
`psycopg2.extensions.cursor`. See :ref:`subclassing-cursor` for
|
||||||
|
|
|
@ -555,6 +555,38 @@ Other functions
|
||||||
.. __: http://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQESCAPEIDENTIFIER
|
.. __: 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::
|
.. index::
|
||||||
pair: Isolation level; Constants
|
pair: Isolation level; Constants
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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
|
there is an experimental `porting of Psycopg for Ctypes`__, but it is not as
|
||||||
mature as the C implementation yet.
|
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/
|
.. _PostgreSQL: http://www.postgresql.org/
|
||||||
.. _Python: http://www.python.org/
|
.. _Python: http://www.python.org/
|
||||||
.. _libpq: http://www.postgresql.org/docs/current/static/libpq.html
|
.. _libpq: http://www.postgresql.org/docs/current/static/libpq.html
|
||||||
|
@ -32,94 +22,20 @@ The current `!psycopg2` implementation supports:
|
||||||
|
|
||||||
|
|
||||||
.. index::
|
.. index::
|
||||||
single: Install; from PyPI
|
single: Prerequisites
|
||||||
single: Install; wheel
|
|
||||||
single: Wheel
|
|
||||||
|
|
||||||
Binary install from PyPI
|
Prerequisites
|
||||||
------------------------
|
-------------
|
||||||
|
|
||||||
`!psycopg2` is `available on PyPI`__ in the form of wheel_ packages for the
|
The current `!psycopg2` implementation supports:
|
||||||
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:
|
|
||||||
|
|
||||||
.. code-block:: console
|
..
|
||||||
|
NOTE: keep consistent with setup.py and the /features/ page.
|
||||||
|
|
||||||
$ pip install psycopg2
|
- Python version 2.7
|
||||||
|
- Python 3 versions from 3.4 to 3.6
|
||||||
Make sure to use an up-to-date version of :program:`pip` (you can upgrade it
|
- PostgreSQL server versions from 7.4 to 10
|
||||||
using something like ``pip install -U pip``)
|
- PostgreSQL client library version from 9.1
|
||||||
|
|
||||||
.. __: 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/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,8 +44,10 @@ page`__ or from PyPI_.
|
||||||
Build prerequisites
|
Build prerequisites
|
||||||
^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
These notes illustrate how to compile Psycopg on Linux. If you want to compile
|
The build prerequisites are to be met in order to install Psycopg from source
|
||||||
Psycopg on other platforms you may have to adjust some details accordingly.
|
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:
|
||||||
|
@ -161,6 +79,12 @@ it from sources you will need:
|
||||||
|
|
||||||
Once everything is in place it's just a matter of running the standard:
|
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
|
.. code-block:: console
|
||||||
|
|
||||||
$ python setup.py build
|
$ 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::
|
.. index::
|
||||||
single: setup.py
|
single: setup.py
|
||||||
single: setup.cfg
|
single: setup.cfg
|
||||||
|
|
||||||
Non-standard builds
|
Non-standard builds
|
||||||
^^^^^^^^^^^^^^^^^^^
|
-------------------
|
||||||
|
|
||||||
If you have less standard requirements such as:
|
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
|
- Edit the ``setup.cfg`` file adding the ``PSYCOPG_DEBUG`` flag to the
|
||||||
``define`` option.
|
``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:
|
- 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
|
- 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
7
doc/src/license.rst
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.. index::
|
||||||
|
single: License
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. include:: ../../LICENSE
|
|
@ -1,3 +1,7 @@
|
||||||
|
.. index::
|
||||||
|
single: Release notes
|
||||||
|
single: News
|
||||||
|
|
||||||
Release notes
|
Release notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -24,13 +24,18 @@ directly in the client application.
|
||||||
|
|
||||||
.. method:: getconn(key=None)
|
.. 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)
|
.. method:: putconn(conn, key=None, close=False)
|
||||||
|
|
||||||
Put away a connection.
|
Put away a connection.
|
||||||
|
|
||||||
If *close* is `!True`, discard the connection from the pool.
|
If *close* is `!True`, discard the connection from the pool.
|
||||||
|
*key* should be used consistently with `getconn()`.
|
||||||
|
|
||||||
.. method:: closeall
|
.. method:: closeall
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) != 3:
|
if len(sys.argv) != 3:
|
||||||
sys.stderr.write("usage: %s index.rst text-dir\n")
|
sys.stderr.write("usage: %s index.rst text-dir\n")
|
||||||
|
@ -17,20 +18,20 @@ def main():
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def iter_file_base(fn):
|
def iter_file_base(fn):
|
||||||
f = open(fn)
|
f = open(fn)
|
||||||
have_line = iter(f).__next__
|
|
||||||
|
|
||||||
while not have_line().startswith('.. toctree'):
|
while not next(f).startswith('.. toctree'):
|
||||||
pass
|
pass
|
||||||
while have_line().strip().startswith(':'):
|
while next(f).strip().startswith(':'):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
yield os.path.splitext(os.path.basename(fn))[0]
|
yield os.path.splitext(os.path.basename(fn))[0]
|
||||||
|
|
||||||
n = 0
|
n = 0
|
||||||
while True:
|
while True:
|
||||||
line = have_line()
|
line = next(f)
|
||||||
if line.isspace():
|
if line.isspace():
|
||||||
continue
|
continue
|
||||||
if line.startswith(".."):
|
if line.startswith(".."):
|
||||||
|
@ -44,6 +45,7 @@ def iter_file_base(fn):
|
||||||
# maybe format changed?
|
# maybe format changed?
|
||||||
raise Exception("Not enough files found. Format change in index.rst?")
|
raise Exception("Not enough files found. Format change in index.rst?")
|
||||||
|
|
||||||
|
|
||||||
def emit(basename, txt_dir):
|
def emit(basename, txt_dir):
|
||||||
f = open(os.path.join(txt_dir, basename + ".txt"))
|
f = open(os.path.join(txt_dir, basename + ".txt"))
|
||||||
for line in f:
|
for line in f:
|
||||||
|
|
|
@ -63,7 +63,7 @@ from psycopg2._psycopg import ( # noqa
|
||||||
string_types, binary_types, new_type, new_array_type, register_type,
|
string_types, binary_types, new_type, new_array_type, register_type,
|
||||||
ISQLQuote, Notify, Diagnostics, Column,
|
ISQLQuote, Notify, Diagnostics, Column,
|
||||||
QueryCanceledError, TransactionRollbackError,
|
QueryCanceledError, TransactionRollbackError,
|
||||||
set_wait_callback, get_wait_callback, )
|
set_wait_callback, get_wait_callback, encrypt_password, )
|
||||||
|
|
||||||
|
|
||||||
"""Isolation level values."""
|
"""Isolation level values."""
|
||||||
|
|
|
@ -344,7 +344,20 @@ class NamedTupleCursor(_cursor):
|
||||||
return
|
return
|
||||||
|
|
||||||
def _make_nt(self):
|
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):
|
class LoggingConnection(_connection):
|
||||||
|
|
|
@ -100,7 +100,7 @@ _pydatetime_string_delta(pydatetimeObject *self)
|
||||||
|
|
||||||
char buffer[8];
|
char buffer[8];
|
||||||
int i;
|
int i;
|
||||||
int a = obj->microseconds;
|
int a = PyDateTime_DELTA_GET_MICROSECONDS(obj);
|
||||||
|
|
||||||
for (i=0; i < 6 ; i++) {
|
for (i=0; i < 6 ; i++) {
|
||||||
buffer[5-i] = '0' + (a % 10);
|
buffer[5-i] = '0' + (a % 10);
|
||||||
|
@ -109,7 +109,9 @@ _pydatetime_string_delta(pydatetimeObject *self)
|
||||||
buffer[6] = '\0';
|
buffer[6] = '\0';
|
||||||
|
|
||||||
return Bytes_FromFormat("'%d days %d.%s seconds'::interval",
|
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 *
|
static PyObject *
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
all_nulls = 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/* here we don't loose a refcnt: SET_ITEM does not change the
|
bufsize += Bytes_GET_SIZE(qs[i]) + 1; /* this, and a comma */
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
/* Return a new "string" from a char* from the database.
|
||||||
*
|
*
|
||||||
* On Py2 just get a string, on Py3 decode it in the connection codec.
|
* 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;
|
int rv = -1;
|
||||||
PGresult *pgres = NULL;
|
PGresult *pgres = NULL;
|
||||||
char *error = 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,
|
PyErr_SetString(ProgrammingError,
|
||||||
"the 'deferrable' setting is only available"
|
"the 'deferrable' setting is only available"
|
||||||
" from PostgreSQL 9.1");
|
" from PostgreSQL 9.1");
|
||||||
|
@ -1209,24 +1214,24 @@ conn_set_session(connectionObject *self, int autocommit,
|
||||||
Py_BEGIN_ALLOW_THREADS;
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
pthread_mutex_lock(&self->lock);
|
pthread_mutex_lock(&self->lock);
|
||||||
|
|
||||||
if (autocommit) {
|
if (want_autocommit) {
|
||||||
/* we are in autocommit state, so no BEGIN will be issued:
|
/* we are or are going in autocommit state, so no BEGIN will be issued:
|
||||||
* configure the session with the characteristics requested */
|
* configure the session with the characteristics requested */
|
||||||
if (isolevel != self->isolevel) {
|
if (isolevel != SRV_STATE_UNCHANGED) {
|
||||||
if (0 > pq_set_guc_locked(self,
|
if (0 > pq_set_guc_locked(self,
|
||||||
"default_transaction_isolation", srv_isolevels[isolevel],
|
"default_transaction_isolation", srv_isolevels[isolevel],
|
||||||
&pgres, &error, &_save)) {
|
&pgres, &error, &_save)) {
|
||||||
goto endlock;
|
goto endlock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (readonly != self->readonly) {
|
if (readonly != SRV_STATE_UNCHANGED) {
|
||||||
if (0 > pq_set_guc_locked(self,
|
if (0 > pq_set_guc_locked(self,
|
||||||
"default_transaction_read_only", srv_state_guc[readonly],
|
"default_transaction_read_only", srv_state_guc[readonly],
|
||||||
&pgres, &error, &_save)) {
|
&pgres, &error, &_save)) {
|
||||||
goto endlock;
|
goto endlock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (deferrable != self->deferrable) {
|
if (deferrable != SRV_STATE_UNCHANGED) {
|
||||||
if (0 > pq_set_guc_locked(self,
|
if (0 > pq_set_guc_locked(self,
|
||||||
"default_transaction_deferrable", srv_state_guc[deferrable],
|
"default_transaction_deferrable", srv_state_guc[deferrable],
|
||||||
&pgres, &error, &_save)) {
|
&pgres, &error, &_save)) {
|
||||||
|
@ -1251,7 +1256,7 @@ conn_set_session(connectionObject *self, int autocommit,
|
||||||
goto endlock;
|
goto endlock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (self->deferrable != STATE_DEFAULT) {
|
if (self->server_version >= 90100 && self->deferrable != STATE_DEFAULT) {
|
||||||
if (0 > pq_set_guc_locked(self,
|
if (0 > pq_set_guc_locked(self,
|
||||||
"default_transaction_deferrable", "default",
|
"default_transaction_deferrable", "default",
|
||||||
&pgres, &error, &_save)) {
|
&pgres, &error, &_save)) {
|
||||||
|
@ -1260,10 +1265,18 @@ conn_set_session(connectionObject *self, int autocommit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (autocommit != SRV_STATE_UNCHANGED) {
|
||||||
self->autocommit = autocommit;
|
self->autocommit = autocommit;
|
||||||
|
}
|
||||||
|
if (isolevel != SRV_STATE_UNCHANGED) {
|
||||||
self->isolevel = isolevel;
|
self->isolevel = isolevel;
|
||||||
|
}
|
||||||
|
if (readonly != SRV_STATE_UNCHANGED) {
|
||||||
self->readonly = readonly;
|
self->readonly = readonly;
|
||||||
|
}
|
||||||
|
if (deferrable != SRV_STATE_UNCHANGED) {
|
||||||
self->deferrable = deferrable;
|
self->deferrable = deferrable;
|
||||||
|
}
|
||||||
rv = 0;
|
rv = 0;
|
||||||
|
|
||||||
endlock:
|
endlock:
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
extern HIDDEN const char *srv_isolevels[];
|
extern HIDDEN const char *srv_isolevels[];
|
||||||
extern HIDDEN const char *srv_readonly[];
|
extern HIDDEN const char *srv_readonly[];
|
||||||
extern HIDDEN const char *srv_deferrable[];
|
extern HIDDEN const char *srv_deferrable[];
|
||||||
|
extern HIDDEN const int SRV_STATE_UNCHANGED;
|
||||||
|
|
||||||
/** DBAPI methods **/
|
/** DBAPI methods **/
|
||||||
|
|
||||||
|
@ -561,10 +562,10 @@ psyco_conn_set_session(connectionObject *self, PyObject *args, PyObject *kwargs)
|
||||||
PyObject *deferrable = Py_None;
|
PyObject *deferrable = Py_None;
|
||||||
PyObject *autocommit = Py_None;
|
PyObject *autocommit = Py_None;
|
||||||
|
|
||||||
int c_isolevel = self->isolevel;
|
int c_isolevel = SRV_STATE_UNCHANGED;
|
||||||
int c_readonly = self->readonly;
|
int c_readonly = SRV_STATE_UNCHANGED;
|
||||||
int c_deferrable = self->deferrable;
|
int c_deferrable = SRV_STATE_UNCHANGED;
|
||||||
int c_autocommit = self->autocommit;
|
int c_autocommit = SRV_STATE_UNCHANGED;
|
||||||
|
|
||||||
static char *kwlist[] =
|
static char *kwlist[] =
|
||||||
{"isolation_level", "readonly", "deferrable", "autocommit", NULL};
|
{"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 (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
|
||||||
if (-1 == (value = PyObject_IsTrue(pyvalue))) { return -1; }
|
if (-1 == (value = PyObject_IsTrue(pyvalue))) { return -1; }
|
||||||
if (0 > conn_set_session(self, value,
|
if (0 > conn_set_session(self, value,
|
||||||
self->isolevel, self->readonly, self->deferrable)) {
|
SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
|
||||||
return -1;
|
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 (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
|
||||||
if (0 > (value = _psyco_conn_parse_isolevel(pyvalue))) { return -1; }
|
if (0 > (value = _psyco_conn_parse_isolevel(pyvalue))) { return -1; }
|
||||||
if (0 > conn_set_session(self, self->autocommit,
|
if (0 > conn_set_session(self, SRV_STATE_UNCHANGED,
|
||||||
value, self->readonly, self->deferrable)) {
|
value, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -715,13 +716,13 @@ psyco_conn_set_isolation_level(connectionObject *self, PyObject *args)
|
||||||
|
|
||||||
if (level == 0) {
|
if (level == 0) {
|
||||||
if (0 > conn_set_session(self, 1,
|
if (0 > conn_set_session(self, 1,
|
||||||
self->isolevel, self->readonly, self->deferrable)) {
|
SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (0 > conn_set_session(self, 0,
|
if (0 > conn_set_session(self, 0,
|
||||||
level, self->readonly, self->deferrable)) {
|
level, SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED)) {
|
||||||
return NULL;
|
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 (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
|
||||||
if (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; }
|
if (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; }
|
||||||
if (0 > conn_set_session(self, self->autocommit,
|
if (0 > conn_set_session(self, SRV_STATE_UNCHANGED,
|
||||||
self->isolevel, value, self->deferrable)) {
|
SRV_STATE_UNCHANGED, value, SRV_STATE_UNCHANGED)) {
|
||||||
return -1;
|
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 (!_psyco_set_session_check_setter_wrapper(self)) { return -1; }
|
||||||
if (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; }
|
if (0 > (value = _psyco_conn_parse_onoff(pyvalue))) { return -1; }
|
||||||
if (0 > conn_set_session(self, self->autocommit,
|
if (0 > conn_set_session(self, SRV_STATE_UNCHANGED,
|
||||||
self->isolevel, self->readonly, value)) {
|
SRV_STATE_UNCHANGED, SRV_STATE_UNCHANGED, value)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
Dprintf("skipping named curs close because tx status %d",
|
||||||
|
(int)status);
|
||||||
|
goto close;
|
||||||
|
}
|
||||||
|
|
||||||
EXC_IF_NO_MARK(self);
|
EXC_IF_NO_MARK(self);
|
||||||
PyOS_snprintf(buffer, 127, "CLOSE %s", self->qname);
|
PyOS_snprintf(buffer, 127, "CLOSE %s", self->qname);
|
||||||
if (pq_execute(self, buffer, 0, 0, 1) == -1) return NULL;
|
if (pq_execute(self, buffer, 0, 0, 1) == -1) return NULL;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
Dprintf("skipping named curs close because tx status %d",
|
|
||||||
(int)status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
@ -592,8 +598,6 @@ psyco_curs_mogrify(cursorObject *self, PyObject *args, PyObject *kwargs)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
EXC_IF_CURS_CLOSED(self);
|
|
||||||
|
|
||||||
return _psyco_curs_mogrify(self, operation, vars);
|
return _psyco_curs_mogrify(self, operation, vars);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,10 @@ HIDDEN PyObject *psyco_null = NULL;
|
||||||
/* The type of the cursor.description items */
|
/* The type of the cursor.description items */
|
||||||
HIDDEN PyObject *psyco_DescriptionType = NULL;
|
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 **/
|
/** connect module-level function **/
|
||||||
#define psyco_connect_doc \
|
#define psyco_connect_doc \
|
||||||
"_connect(dsn, [connection_factory], [async]) -- New database connection.\n\n"
|
"_connect(dsn, [connection_factory], [async]) -- New database connection.\n\n"
|
||||||
|
@ -403,6 +407,105 @@ psyco_libpq_version(PyObject *self)
|
||||||
#endif
|
#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
|
/* psyco_encodings_fill
|
||||||
|
|
||||||
Fill the module's postgresql<->python encoding table */
|
Fill the module's postgresql<->python encoding table */
|
||||||
|
@ -852,6 +955,8 @@ static PyMethodDef psycopgMethods[] = {
|
||||||
METH_O, psyco_set_wait_callback_doc},
|
METH_O, psyco_set_wait_callback_doc},
|
||||||
{"get_wait_callback", (PyCFunction)psyco_get_wait_callback,
|
{"get_wait_callback", (PyCFunction)psyco_get_wait_callback,
|
||||||
METH_NOARGS, psyco_get_wait_callback_doc},
|
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 */
|
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
@ -885,7 +990,7 @@ INIT_MODULE(_psycopg)(void)
|
||||||
psycopg_debug_enabled = 1;
|
psycopg_debug_enabled = 1;
|
||||||
#endif
|
#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 */
|
/* initialize all the new types and then the module */
|
||||||
Py_TYPE(&connectionType) = &PyType_Type;
|
Py_TYPE(&connectionType) = &PyType_Type;
|
||||||
|
@ -1017,7 +1122,7 @@ INIT_MODULE(_psycopg)(void)
|
||||||
if (!(psyco_DescriptionType = psyco_make_description_type())) { goto exit; }
|
if (!(psyco_DescriptionType = psyco_make_description_type())) { goto exit; }
|
||||||
|
|
||||||
/* set some module's parameters */
|
/* 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_AddStringConstant(module, "__doc__", "psycopg PostgreSQL driver");
|
||||||
PyModule_AddIntConstant(module, "__libpq_version__", PG_VERSION_NUM);
|
PyModule_AddIntConstant(module, "__libpq_version__", PG_VERSION_NUM);
|
||||||
PyModule_AddIntMacro(module, REPLICATION_PHYSICAL);
|
PyModule_AddIntMacro(module, REPLICATION_PHYSICAL);
|
||||||
|
|
|
@ -87,6 +87,7 @@ typedef unsigned long Py_uhash_t;
|
||||||
#ifndef PyNumber_Int
|
#ifndef PyNumber_Int
|
||||||
#define PyNumber_Int PyNumber_Long
|
#define PyNumber_Int PyNumber_Long
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* PY_MAJOR_VERSION > 2 */
|
#endif /* PY_MAJOR_VERSION > 2 */
|
||||||
|
|
||||||
#if PY_MAJOR_VERSION < 3
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
@ -104,6 +105,10 @@ typedef unsigned long Py_uhash_t;
|
||||||
#define Bytes_ConcatAndDel PyString_ConcatAndDel
|
#define Bytes_ConcatAndDel PyString_ConcatAndDel
|
||||||
#define _Bytes_Resize _PyString_Resize
|
#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
|
#else
|
||||||
|
|
||||||
#define Bytes_Type PyBytes_Type
|
#define Bytes_Type PyBytes_Type
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* solaris_support.c - emulate functions missing on Solaris
|
/* solaris_support.c - emulate functions missing on Solaris
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017 My Karlsson <mk@acc.umu.se>
|
* Copyright (C) 2017 My Karlsson <mk@acc.umu.se>
|
||||||
|
* Copyright (c) 2018, Joyent, Inc.
|
||||||
*
|
*
|
||||||
* This file is part of psycopg.
|
* This file is part of psycopg.
|
||||||
*
|
*
|
||||||
|
@ -28,7 +29,8 @@
|
||||||
#include "psycopg/solaris_support.h"
|
#include "psycopg/solaris_support.h"
|
||||||
|
|
||||||
#if defined(__sun) && defined(__SVR4)
|
#if defined(__sun) && defined(__SVR4)
|
||||||
/* timeradd is missing on Solaris */
|
/* timeradd is missing on Solaris 10 */
|
||||||
|
#ifndef timeradd
|
||||||
void
|
void
|
||||||
timeradd(struct timeval *a, struct timeval *b, struct timeval *c)
|
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;
|
c->tv_sec -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif /* timeradd */
|
||||||
#endif /* defined(__sun) && defined(__SVR4) */
|
#endif /* defined(__sun) && defined(__SVR4) */
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* solaris_support.h - definitions for solaris_support.c
|
/* solaris_support.h - definitions for solaris_support.c
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017 My Karlsson <mk@acc.umu.se>
|
* Copyright (C) 2017 My Karlsson <mk@acc.umu.se>
|
||||||
|
* Copyright (c) 2018, Joyent, Inc.
|
||||||
*
|
*
|
||||||
* This file is part of psycopg.
|
* This file is part of psycopg.
|
||||||
*
|
*
|
||||||
|
@ -30,8 +31,10 @@
|
||||||
#if defined(__sun) && defined(__SVR4)
|
#if defined(__sun) && defined(__SVR4)
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#ifndef timeradd
|
||||||
extern HIDDEN void timeradd(struct timeval *a, struct timeval *b, struct timeval *c);
|
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);
|
extern HIDDEN void timersub(struct timeval *a, struct timeval *b, struct timeval *c);
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* !defined(PSYCOPG_SOLARIS_SUPPORT_H) */
|
#endif /* !defined(PSYCOPG_SOLARIS_SUPPORT_H) */
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,11 +168,11 @@ psycopg_ensure_bytes(PyObject *obj)
|
||||||
PyObject *rv = NULL;
|
PyObject *rv = NULL;
|
||||||
if (!obj) { return NULL; }
|
if (!obj) { return NULL; }
|
||||||
|
|
||||||
if (PyUnicode_CheckExact(obj)) {
|
if (PyUnicode_Check(obj)) {
|
||||||
rv = PyUnicode_AsUTF8String(obj);
|
rv = PyUnicode_AsUTF8String(obj);
|
||||||
Py_DECREF(obj);
|
Py_DECREF(obj);
|
||||||
}
|
}
|
||||||
else if (Bytes_CheckExact(obj)) {
|
else if (Bytes_Check(obj)) {
|
||||||
rv = obj;
|
rv = obj;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -282,7 +282,7 @@ exit:
|
||||||
|
|
||||||
/* Make a connection string out of a string and a dictionary of arguments.
|
/* 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 *
|
PyObject *
|
||||||
psycopg_make_dsn(PyObject *dsn, PyObject *kwargs)
|
psycopg_make_dsn(PyObject *dsn, PyObject *kwargs)
|
||||||
|
|
|
@ -9,7 +9,7 @@ To invalidate the cache, update this file and check it into git.
|
||||||
Currently used modules built in the cache:
|
Currently used modules built in the cache:
|
||||||
|
|
||||||
OpenSSL
|
OpenSSL
|
||||||
Version: 1.0.2m
|
Version: 1.0.2n
|
||||||
|
|
||||||
PostgreSQL
|
PostgreSQL
|
||||||
Version: 10.1
|
Version: 10.1
|
||||||
|
|
|
@ -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
|
||||||
|
|
49
setup.py
49
setup.py
|
@ -39,6 +39,7 @@ except ImportError:
|
||||||
from distutils.command.build_ext import build_ext
|
from distutils.command.build_ext import build_ext
|
||||||
from distutils.sysconfig import get_python_inc
|
from distutils.sysconfig import get_python_inc
|
||||||
from distutils.ccompiler import get_default_compiler
|
from distutils.ccompiler import get_default_compiler
|
||||||
|
from distutils.errors import CompileError
|
||||||
from distutils.util import get_platform
|
from distutils.util import get_platform
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -89,15 +90,23 @@ class PostgresConfig:
|
||||||
if not self.pg_config_exe:
|
if not self.pg_config_exe:
|
||||||
self.pg_config_exe = self.autodetect_pg_config_path()
|
self.pg_config_exe = self.autodetect_pg_config_path()
|
||||||
if self.pg_config_exe is None:
|
if self.pg_config_exe is None:
|
||||||
sys.stderr.write("""\
|
sys.stderr.write("""
|
||||||
Error: pg_config executable not found.
|
Error: pg_config executable not found.
|
||||||
|
|
||||||
Please add the directory containing pg_config to the PATH
|
pg_config is required to build psycopg2 from source. Please add the directory
|
||||||
or specify the full executable path with the option:
|
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 ...
|
python setup.py build_ext --pg-config /path/to/pg_config build ...
|
||||||
|
|
||||||
or with the pg_config option in 'setup.cfg'.
|
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)
|
sys.exit(1)
|
||||||
|
|
||||||
|
@ -271,8 +280,37 @@ class psycopg_build_ext(build_ext):
|
||||||
else:
|
else:
|
||||||
return build_ext.get_export_symbols(self, extension)
|
return build_ext.get_export_symbols(self, extension)
|
||||||
|
|
||||||
|
built_files = 0
|
||||||
|
|
||||||
def build_extension(self, extension):
|
def 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)
|
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]
|
sysVer = sys.version_info[:2]
|
||||||
|
|
||||||
# For Python versions that use MSVC compiler 2008, re-insert the
|
# For Python versions that use MSVC compiler 2008, re-insert the
|
||||||
|
@ -543,10 +581,7 @@ if version_flags:
|
||||||
else:
|
else:
|
||||||
PSYCOPG_VERSION_EX = PSYCOPG_VERSION
|
PSYCOPG_VERSION_EX = PSYCOPG_VERSION
|
||||||
|
|
||||||
if not PLATFORM_IS_WINDOWS:
|
define_macros.append(('PSYCOPG_VERSION', PSYCOPG_VERSION_EX))
|
||||||
define_macros.append(('PSYCOPG_VERSION', '"' + PSYCOPG_VERSION_EX + '"'))
|
|
||||||
else:
|
|
||||||
define_macros.append(('PSYCOPG_VERSION', '\\"' + PSYCOPG_VERSION_EX + '\\"'))
|
|
||||||
|
|
||||||
if parser.has_option('build_ext', 'have_ssl'):
|
if parser.has_option('build_ext', 'have_ssl'):
|
||||||
have_ssl = int(parser.get('build_ext', 'have_ssl'))
|
have_ssl = int(parser.get('build_ext', 'have_ssl'))
|
||||||
|
|
|
@ -37,7 +37,9 @@ from psycopg2 import extensions as ext
|
||||||
from .testutils import (
|
from .testutils import (
|
||||||
unittest, decorate_all_tests, skip_if_no_superuser,
|
unittest, decorate_all_tests, skip_if_no_superuser,
|
||||||
skip_before_postgres, skip_after_postgres, skip_before_libpq,
|
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
|
from .testconfig import dsn, dbname
|
||||||
|
|
||||||
|
@ -246,6 +248,13 @@ class ConnectionTests(ConnectingTestCase):
|
||||||
else:
|
else:
|
||||||
del os.environ['PGCLIENTENCODING']
|
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):
|
def test_weakref(self):
|
||||||
from weakref import ref
|
from weakref import ref
|
||||||
import gc
|
import gc
|
||||||
|
@ -400,6 +409,13 @@ class ParseDsnTestCase(ConnectingTestCase):
|
||||||
self.assertRaises(TypeError, ext.parse_dsn, None)
|
self.assertRaises(TypeError, ext.parse_dsn, None)
|
||||||
self.assertRaises(TypeError, ext.parse_dsn, 42)
|
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):
|
class MakeDsnTestCase(ConnectingTestCase):
|
||||||
def test_empty_arguments(self):
|
def test_empty_arguments(self):
|
||||||
|
@ -1381,6 +1397,102 @@ class TransactionControlTests(ConnectingTestCase):
|
||||||
cur.execute("SHOW default_transaction_read_only;")
|
cur.execute("SHOW default_transaction_read_only;")
|
||||||
self.assertEqual(cur.fetchone()[0], 'off')
|
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):
|
class AutocommitTests(ConnectingTestCase):
|
||||||
def test_closed(self):
|
def test_closed(self):
|
||||||
|
@ -1539,9 +1651,13 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import signal
|
import signal
|
||||||
|
import warnings
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import psycopg2
|
# ignore wheel deprecation warning
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore')
|
||||||
|
import psycopg2
|
||||||
|
|
||||||
def handle_sigabort(sig, frame):
|
def handle_sigabort(sig, frame):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
|
@ -119,6 +119,12 @@ class CursorTests(ConnectingTestCase):
|
||||||
nref2 = sys.getrefcount(foo)
|
nref2 = sys.getrefcount(foo)
|
||||||
self.assertEqual(nref1, nref2)
|
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):
|
def test_bad_placeholder(self):
|
||||||
cur = self.conn.cursor()
|
cur = self.conn.cursor()
|
||||||
self.assertRaises(psycopg2.ProgrammingError,
|
self.assertRaises(psycopg2.ProgrammingError,
|
||||||
|
@ -430,6 +436,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()
|
||||||
|
|
|
@ -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'):
|
||||||
|
@ -639,7 +647,8 @@ class FromTicksTestCase(unittest.TestCase):
|
||||||
def test_date_value_error_sec_59_99(self):
|
def test_date_value_error_sec_59_99(self):
|
||||||
from datetime import date
|
from datetime import date
|
||||||
s = psycopg2.DateFromTicks(1273173119.99992)
|
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):
|
def test_time_value_error_sec_59_99(self):
|
||||||
from datetime import time
|
from datetime import time
|
||||||
|
|
|
@ -19,12 +19,11 @@ 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, skip_from_python
|
||||||
|
|
||||||
|
|
||||||
class ExtrasDictCursorTests(ConnectingTestCase):
|
class _DictCursorBase(ConnectingTestCase):
|
||||||
"""Test if DictCursor extension class works."""
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
ConnectingTestCase.setUp(self)
|
ConnectingTestCase.setUp(self)
|
||||||
curs = self.conn.cursor()
|
curs = self.conn.cursor()
|
||||||
|
@ -32,6 +31,30 @@ class ExtrasDictCursorTests(ConnectingTestCase):
|
||||||
curs.execute("INSERT INTO ExtrasDictCursorTests VALUES ('bar')")
|
curs.execute("INSERT INTO ExtrasDictCursorTests VALUES ('bar')")
|
||||||
self.conn.commit()
|
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):
|
def testDictConnCursorArgs(self):
|
||||||
self.conn.close()
|
self.conn.close()
|
||||||
self.conn = self.connect(connection_factory=psycopg2.extras.DictConnection)
|
self.conn = self.connect(connection_factory=psycopg2.extras.DictConnection)
|
||||||
|
@ -81,35 +104,6 @@ class ExtrasDictCursorTests(ConnectingTestCase):
|
||||||
self.failUnless(row[0] == 'bar')
|
self.failUnless(row[0] == 'bar')
|
||||||
return row
|
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):
|
def testDictCursorWithNamedCursorFetchOne(self):
|
||||||
self._testWithNamedCursor(lambda curs: curs.fetchone())
|
self._testWithNamedCursor(lambda curs: curs.fetchone())
|
||||||
|
|
||||||
|
@ -145,6 +139,94 @@ class ExtrasDictCursorTests(ConnectingTestCase):
|
||||||
self.failUnless(row['foo'] == 'bar')
|
self.failUnless(row['foo'] == 'bar')
|
||||||
self.failUnless(row[0] == '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):
|
def testDictCursorRealWithNamedCursorFetchOne(self):
|
||||||
self._testWithNamedCursorReal(lambda curs: curs.fetchone())
|
self._testWithNamedCursorReal(lambda curs: curs.fetchone())
|
||||||
|
|
||||||
|
@ -180,51 +262,36 @@ class ExtrasDictCursorTests(ConnectingTestCase):
|
||||||
row = getter(curs)
|
row = getter(curs)
|
||||||
self.failUnless(row['foo'] == 'bar')
|
self.failUnless(row['foo'] == 'bar')
|
||||||
|
|
||||||
def _testNamedCursorNotGreedy(self, curs):
|
@skip_from_python(3)
|
||||||
curs.itersize = 2
|
def test_iter_methods_2(self):
|
||||||
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
|
|
||||||
curs = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
curs = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||||||
curs.execute("select 10 as a, 20 as b")
|
curs.execute("select 10 as a, 20 as b")
|
||||||
r = curs.fetchone()
|
r = curs.fetchone()
|
||||||
d = pickle.dumps(r)
|
self.assert_(isinstance(r.keys(), list))
|
||||||
r1 = pickle.loads(d)
|
self.assertEqual(len(r.keys()), 2)
|
||||||
self.assertEqual(r, r1)
|
self.assert_(isinstance(r.values(), list))
|
||||||
self.assertEqual(r['a'], r1['a'])
|
self.assertEqual(len(r.values()), 2)
|
||||||
self.assertEqual(r['b'], r1['b'])
|
self.assert_(isinstance(r.items(), list))
|
||||||
self.assertEqual(r._column_mapping, r1._column_mapping)
|
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):
|
class NamedTupleCursorTest(ConnectingTestCase):
|
||||||
|
@ -349,6 +416,22 @@ class NamedTupleCursorTest(ConnectingTestCase):
|
||||||
curs.execute("update nttest set s = s")
|
curs.execute("update nttest set s = s")
|
||||||
self.assertRaises(psycopg2.ProgrammingError, curs.fetchall)
|
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):
|
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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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__)
|
||||||
|
|
8
tox.ini
8
tox.ini
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user