mirror of
https://github.com/psycopg/psycopg2.git
synced 2025-08-05 04:40:09 +03:00
Compare commits
82 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1690fbace1 | ||
|
c5411f67c2 | ||
|
a5970a21a8 | ||
|
98a9203827 | ||
|
9886a7210e | ||
|
f4b57822b0 | ||
|
9d4e3a06b7 | ||
|
88af6e790c | ||
|
5e7a4983c6 | ||
|
230d9d6b1a | ||
|
e3ba21039c | ||
|
61764ea581 | ||
|
c585478dd1 | ||
|
2b52469a0b | ||
|
bbe6414ad3 | ||
|
ece7fb43b5 | ||
|
f335c46a54 | ||
|
e062404d5c | ||
|
600c744fba | ||
|
556f25153f | ||
|
8dff6a38c7 | ||
|
05f23df238 | ||
|
5f3be39e0a | ||
|
4d0b449cbf | ||
|
89f2ab9f05 | ||
|
e79949f76e | ||
|
077e1f0e8d | ||
|
53c1607644 | ||
|
ac14957fbb | ||
|
2714c9c638 | ||
|
072b6ef98b | ||
|
5aa7588f56 | ||
|
0fa44ea943 | ||
|
fd58f9105e | ||
|
ae87645c43 | ||
|
40997d901b | ||
|
1f737c4d65 | ||
|
a355d48aeb | ||
|
b6d1fbdfd1 | ||
|
cbeea0f98f | ||
|
88593602de | ||
|
ef09ebe62a | ||
|
2833d4f5ff | ||
|
c20503544f | ||
|
ff4c52f435 | ||
|
db9082b6e4 | ||
|
78d6dca65d | ||
|
c076fc3a26 | ||
|
347a64b979 | ||
|
a09efaf5a1 | ||
|
1714bd9273 | ||
|
ca90fb2791 | ||
|
9fd5def484 | ||
|
4a6b31f56a | ||
|
abf1f28c44 | ||
|
3e31fb359e | ||
|
e707c3f657 | ||
|
b3c07fddb8 | ||
|
c29dd8fffa | ||
|
1aed1c6ecd | ||
|
6d2bd882ad | ||
|
9021c90c07 | ||
|
72c7374d8e | ||
|
c4ffc0d940 | ||
|
fb487ff244 | ||
|
526c9ea763 | ||
|
57a4342946 | ||
|
6192649d25 | ||
|
9ee15f9845 | ||
|
60fe90d531 | ||
|
76ac3ec34e | ||
|
a70a541e0b | ||
|
8739484b61 | ||
|
b77a8033d3 | ||
|
00946ae7f4 | ||
|
862f47040c | ||
|
6a62b4fa1d | ||
|
b2734c2cbd | ||
|
a24597d0e0 | ||
|
44ba7df67f | ||
|
9ddc224c69 | ||
|
7b78354d58 |
|
@ -2,9 +2,10 @@ recursive-include psycopg *.c *.h *.manifest
|
|||
recursive-include lib *.py
|
||||
recursive-include tests *.py
|
||||
recursive-include examples *.py somehackers.jpg whereareyou.jpg
|
||||
recursive-include doc README SUCCESS COPYING.LESSER pep-0249.txt
|
||||
include doc/README.rst doc/SUCCESS doc/COPYING.LESSER doc/pep-0249.txt
|
||||
include doc/Makefile doc/requirements.txt
|
||||
recursive-include doc/src *.rst *.py *.css Makefile
|
||||
recursive-include scripts *.py *.sh
|
||||
include scripts/maketypes.sh scripts/buildtypes.py
|
||||
include AUTHORS README.rst INSTALL LICENSE NEWS
|
||||
include PKG-INFO MANIFEST.in MANIFEST setup.py setup.cfg Makefile
|
||||
include MANIFEST.in setup.py setup.cfg Makefile
|
||||
|
|
13
Makefile
13
Makefile
|
@ -92,24 +92,19 @@ $(PACKAGE)/tests/%.py: tests/%.py
|
|||
$(PYTHON) setup.py build_py $(BUILD_OPT)
|
||||
touch $@
|
||||
|
||||
$(SDIST): MANIFEST $(SOURCE)
|
||||
$(SDIST): $(SOURCE)
|
||||
$(PYTHON) setup.py sdist $(SDIST_OPT)
|
||||
|
||||
MANIFEST: MANIFEST.in $(SOURCE)
|
||||
# Run twice as MANIFEST.in includes MANIFEST
|
||||
$(PYTHON) setup.py sdist --manifest-only
|
||||
$(PYTHON) setup.py sdist --manifest-only
|
||||
|
||||
# docs depend on the build as it partly use introspection.
|
||||
doc/html/genindex.html: $(PLATLIB) $(PURELIB) $(SOURCE_DOC)
|
||||
PYTHONPATH=:$(BUILD_DIR):$$PYTHONPATH $(MAKE) -C doc html
|
||||
$(MAKE) -C doc html
|
||||
|
||||
doc/psycopg2.txt: $(PLATLIB) $(PURELIB) $(SOURCE_DOC)
|
||||
PYTHONPATH=$(BUILD_DIR):$$PYTHONPATH $(MAKE) -C doc text
|
||||
$(MAKE) -C doc text
|
||||
|
||||
doc/docs.zip: doc/html/genindex.html
|
||||
(cd doc/html && zip -r ../docs.zip *)
|
||||
|
||||
clean:
|
||||
rm -rf build MANIFEST
|
||||
rm -rf build
|
||||
$(MAKE) -C doc clean
|
||||
|
|
36
NEWS
36
NEWS
|
@ -1,6 +1,40 @@
|
|||
Current release
|
||||
---------------
|
||||
|
||||
What's new in psycopg 2.6.2
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- Fixed inconsistent state in externally closed connections
|
||||
(:tickets:`#263, #311, #443`).
|
||||
- Report the server response status on errors (such as :ticket:`#281`).
|
||||
- Raise `!NotSupportedError` on unhandled server response status
|
||||
(:ticket:`#352`).
|
||||
- Allow overriding string adapter encoding with no connection (:ticket:`#331`).
|
||||
- The `~psycopg2.extras.wait_select` callback allows interrupting a
|
||||
long-running query in an interactive shell using :kbd:`Ctrl-C`
|
||||
(:ticket:`#333`).
|
||||
- Fixed `!PersistentConnectionPool` on Python 3 (:ticket:`#348`).
|
||||
- Fixed segfault on `repr()` of an unitialized connection (:ticket:`#361`).
|
||||
- Allow adapting bytes using `~psycopg2.extensions.QuotedString` on Python 3
|
||||
(:ticket:`#365`).
|
||||
- Added support for setuptools/wheel (:ticket:`#370`).
|
||||
- Fix build on Windows with Python 3.5, VS 2015 (:ticket:`#380`).
|
||||
- Fixed `!errorcodes.lookup` initialization thread-safety (:ticket:`#382`).
|
||||
- Fixed `!read()` exception propagation in copy_from (:ticket:`#412`).
|
||||
- Fixed possible NULL TZ decref (:ticket:`#424`).
|
||||
- `~psycopg2.errorcodes` map updated to PostgreSQL 9.5.
|
||||
|
||||
|
||||
What's new in psycopg 2.6.1
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- Lists consisting of only `None` are escaped correctly (:ticket:`#285`).
|
||||
- Fixed deadlock in multithread programs using OpenSSL (:ticket:`#290`).
|
||||
- Correctly unlock the connection after error in flush (:ticket:`#294`).
|
||||
- Fixed `!MinTimeLoggingCursor.callproc()` (:ticket:`#309`).
|
||||
- Added support for MSVC 2015 compiler (:ticket:`#350`).
|
||||
|
||||
|
||||
What's new in psycopg 2.6
|
||||
-------------------------
|
||||
|
||||
|
@ -13,7 +47,7 @@ New features:
|
|||
|
||||
Bug fixes:
|
||||
|
||||
- Json apapter's `!str()` returns the adapted content instead of the `!repr()`
|
||||
- Json adapter's `!str()` returns the adapted content instead of the `!repr()`
|
||||
(:ticket:`#191`).
|
||||
|
||||
|
||||
|
|
23
doc/Makefile
23
doc/Makefile
|
@ -7,22 +7,33 @@ check: doctest
|
|||
# The environment is currently required to build the documentation.
|
||||
# It is not clean by 'make clean'
|
||||
|
||||
help:
|
||||
$(MAKE) SPHINXBUILD=$$(pwd)/env/bin/sphinx-build -C src $@
|
||||
PYTHON := python$(PYTHON_VERSION)
|
||||
PYTHON_VERSION ?= $(shell $(PYTHON) -c 'import sys; print ("%d.%d" % sys.version_info[:2])')
|
||||
|
||||
SPHOPTS=PYTHONPATH=$$(pwd)/../build/lib.$(PYTHON_VERSION)/ SPHINXBUILD=$$(pwd)/env/bin/sphinx-build
|
||||
|
||||
html:
|
||||
$(MAKE) SPHINXBUILD=$$(pwd)/env/bin/sphinx-build -C src $@
|
||||
$(MAKE) PYTHON=$(PYTHON) -C .. package
|
||||
$(MAKE) $(SPHOPTS) -C src $@
|
||||
cp -r src/_build/html .
|
||||
|
||||
text:
|
||||
$(MAKE) SPHINXBUILD=$$(pwd)/env/bin/sphinx-build -C src $@
|
||||
$(MAKE) PYTHON=$(PYTHON) -C .. package
|
||||
$(MAKE) $(SPHOPTS) -C src $@
|
||||
cd src && tools/stitch_text.py index.rst _build/text > ../psycopg2.txt
|
||||
|
||||
doctest:
|
||||
$(MAKE) SPHINXBUILD=$$(pwd)/env/bin/sphinx-build -C src $@
|
||||
$(MAKE) PYTHON=$(PYTHON) -C .. package
|
||||
$(MAKE) $(SPHOPTS) -C src $@
|
||||
|
||||
upload:
|
||||
# this command requires ssh configured to the proper target
|
||||
tar czf - -C html . | ssh psycoweb tar xzvf - -C docs/current
|
||||
# this command requires a .pypirc with the right privileges
|
||||
python src/tools/pypi_docs_upload.py psycopg2 $$(pwd)/html
|
||||
|
||||
clean:
|
||||
$(MAKE) SPHINXBUILD=$$(pwd)/env/bin/sphinx-build -C src $@
|
||||
$(MAKE) $(SPHOPTS) -C src $@
|
||||
rm -rf html psycopg2.txt
|
||||
|
||||
env: requirements.txt
|
||||
|
|
42
doc/README
42
doc/README
|
@ -1,42 +0,0 @@
|
|||
How to build psycopg documentation
|
||||
----------------------------------
|
||||
|
||||
- Install Sphinx, maybe in a virtualenv. Tested with Sphinx 0.6.4::
|
||||
|
||||
~$ virtualenv pd
|
||||
New python executable in pd/bin/python
|
||||
Installing setuptools............done.
|
||||
~$ cd pd
|
||||
~/pd$ source bin/activate
|
||||
(pd)~/pd$
|
||||
|
||||
- Install Sphinx in the env::
|
||||
|
||||
(pd)~/pd$ easy_install sphinx
|
||||
Searching for sphinx
|
||||
Reading http://pypi.python.org/simple/sphinx/
|
||||
Reading http://sphinx.pocoo.org/
|
||||
Best match: Sphinx 0.6.4
|
||||
...
|
||||
Finished processing dependencies for sphinx
|
||||
|
||||
- Build psycopg2 and ensure the package can be imported (it will be used for
|
||||
reading the version number, autodocs etc.)::
|
||||
|
||||
(pd)~/pd/psycopg2$ python setup.py build
|
||||
(pd)~/pd/psycopg2$ python setup.py install
|
||||
running install
|
||||
...
|
||||
creating ~/pd/lib/python2.6/site-packages/psycopg2
|
||||
...
|
||||
|
||||
- Move to the ``doc`` dir and run ``make`` from there::
|
||||
|
||||
(pd)~/pd/psycopg2$ cd doc/
|
||||
(pd)~/pd/psycopg2/doc$ make
|
||||
Running Sphinx v0.6.4
|
||||
...
|
||||
|
||||
You should have the rendered documentation in ``./html`` and the text file
|
||||
``psycopg2.txt`` now.
|
||||
|
26
doc/README.rst
Normal file
26
doc/README.rst
Normal file
|
@ -0,0 +1,26 @@
|
|||
How to build psycopg documentation
|
||||
----------------------------------
|
||||
|
||||
Building the documentation usually requires building the library too for
|
||||
introspection, so you will need the same prerequisites_. The only extra
|
||||
prerequisite is virtualenv_: the packages needed to build the docs will be
|
||||
installed when building the env.
|
||||
|
||||
.. _prerequisites: http://initd.org/psycopg/docs/install.html#install-from-source
|
||||
.. _virtualenv: https://virtualenv.pypa.io/en/latest/
|
||||
|
||||
Build the env once with::
|
||||
|
||||
make env
|
||||
|
||||
Then you can build the documentation with::
|
||||
|
||||
make
|
||||
|
||||
Or the single targets::
|
||||
|
||||
make html
|
||||
make text
|
||||
|
||||
You should find the rendered documentation in the ``html`` dir and the text
|
||||
file ``psycopg2.txt``.
|
|
@ -47,7 +47,7 @@ it is the class where query building, execution and result type-casting into
|
|||
Python variables happens.
|
||||
|
||||
The `~psycopg2.extras` module contains several examples of :ref:`connection
|
||||
and cursor sublcasses <cursor-subclasses>`.
|
||||
and cursor subclasses <cursor-subclasses>`.
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -270,7 +270,7 @@ wasting resources.
|
|||
|
||||
A simple application could poll the connection from time to time to check if
|
||||
something new has arrived. A better strategy is to use some I/O completion
|
||||
function such as :py:func:`~select.select` to sleep until awaken from the kernel when there is
|
||||
function such as :py:func:`~select.select` to sleep until awakened by the kernel when there is
|
||||
some data to read on the connection, thereby using no CPU unless there is
|
||||
something to read::
|
||||
|
||||
|
@ -291,7 +291,7 @@ something to read::
|
|||
else:
|
||||
conn.poll()
|
||||
while conn.notifies:
|
||||
notify = conn.notifies.pop()
|
||||
notify = conn.notifies.pop(0)
|
||||
print "Got NOTIFY:", notify.pid, notify.channel, notify.payload
|
||||
|
||||
Running the script and executing a command such as :sql:`NOTIFY test, 'hello'`
|
||||
|
|
|
@ -42,9 +42,7 @@ master_doc = 'index'
|
|||
|
||||
# General information about the project.
|
||||
project = u'Psycopg'
|
||||
from datetime import date
|
||||
year = date.today().year
|
||||
copyright = u'2001-%s, Federico Di Gregorio, Daniele Varrazzo' % year
|
||||
copyright = u'2001-2016, Federico Di Gregorio, Daniele Varrazzo'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
|
|
@ -351,17 +351,14 @@ The ``connection`` class
|
|||
.. method:: set_session(isolation_level=None, readonly=None, deferrable=None, autocommit=None)
|
||||
|
||||
Set one or more parameters for the next transactions or statements in
|
||||
the current session. See |SET TRANSACTION|_ for further details.
|
||||
|
||||
.. |SET TRANSACTION| replace:: :sql:`SET TRANSACTION`
|
||||
.. _SET TRANSACTION: http://www.postgresql.org/docs/current/static/sql-set-transaction.html
|
||||
the current session.
|
||||
|
||||
:param isolation_level: set the `isolation level`_ for the next
|
||||
transactions/statements. The value can be one of the
|
||||
:ref:`constants <isolation-level-constants>` defined in the
|
||||
`~psycopg2.extensions` module or one of the literal values
|
||||
``READ UNCOMMITTED``, ``READ COMMITTED``, ``REPEATABLE READ``,
|
||||
``SERIALIZABLE``.
|
||||
transactions/statements. The value can be one of the literal
|
||||
values ``READ UNCOMMITTED``, ``READ COMMITTED``, ``REPEATABLE
|
||||
READ``, ``SERIALIZABLE`` or the equivalent :ref:`constant
|
||||
<isolation-level-constants>` defined in the `~psycopg2.extensions`
|
||||
module.
|
||||
:param readonly: if `!True`, set the connection to read only;
|
||||
read/write if `!False`.
|
||||
:param deferrable: if `!True`, set the connection to deferrable;
|
||||
|
@ -370,19 +367,14 @@ The ``connection`` class
|
|||
PostgreSQL session setting but an alias for setting the
|
||||
`autocommit` attribute.
|
||||
|
||||
Parameter passed as `!None` (the default for all) will not be changed.
|
||||
The parameters *isolation_level*, *readonly* and *deferrable* also
|
||||
accept the string ``DEFAULT`` as a value: the effect is to reset the
|
||||
parameter to the server default.
|
||||
|
||||
.. _isolation level:
|
||||
http://www.postgresql.org/docs/current/static/transaction-iso.html
|
||||
|
||||
The function must be invoked with no transaction in progress. At every
|
||||
function invocation, only the specified parameters are changed.
|
||||
|
||||
The default for the values are defined by the server configuration:
|
||||
see values for |default_transaction_isolation|__,
|
||||
Arguments set to `!None` (the default for all) will not be changed.
|
||||
The parameters *isolation_level*, *readonly* and *deferrable* also
|
||||
accept the string ``DEFAULT`` as a value: the effect is to reset the
|
||||
parameter to the server default. Defaults are defined by the server
|
||||
configuration: see values for |default_transaction_isolation|__,
|
||||
|default_transaction_read_only|__, |default_transaction_deferrable|__.
|
||||
|
||||
.. |default_transaction_isolation| replace:: :sql:`default_transaction_isolation`
|
||||
|
@ -392,12 +384,20 @@ The ``connection`` class
|
|||
.. |default_transaction_deferrable| replace:: :sql:`default_transaction_deferrable`
|
||||
.. __: http://www.postgresql.org/docs/current/static/runtime-config-client.html#GUC-DEFAULT-TRANSACTION-DEFERRABLE
|
||||
|
||||
The function must be invoked with no transaction in progress.
|
||||
|
||||
.. note::
|
||||
|
||||
There is currently no builtin method to read the current value for
|
||||
the parameters: use :sql:`SHOW default_transaction_...` to read
|
||||
the values from the backend.
|
||||
|
||||
.. seealso:: |SET TRANSACTION|_ for further details about the behaviour
|
||||
of the transaction parameters in the server.
|
||||
|
||||
.. |SET TRANSACTION| replace:: :sql:`SET TRANSACTION`
|
||||
.. _SET TRANSACTION: http://www.postgresql.org/docs/current/static/sql-set-transaction.html
|
||||
|
||||
.. versionadded:: 2.4.2
|
||||
|
||||
|
||||
|
@ -419,8 +419,8 @@ The ``connection`` class
|
|||
|
||||
By default, any query execution, including a simple :sql:`SELECT`
|
||||
will start a transaction: for long-running programs, if no further
|
||||
action is taken, the session will remain "idle in transaction", a
|
||||
condition non desiderable for several reasons (locks are held by
|
||||
action is taken, the session will remain "idle in transaction", an
|
||||
undesirable condition for several reasons (locks are held by
|
||||
the session, tables bloat...). For long lived scripts, either
|
||||
ensure to terminate a transaction as soon as possible or use an
|
||||
autocommit connection.
|
||||
|
|
|
@ -494,6 +494,9 @@ The ``cursor`` class
|
|||
|
||||
.. rubric:: COPY-related methods
|
||||
|
||||
Efficiently copy data from file-like objects to the database and back. See
|
||||
:ref:`copy` for an overview.
|
||||
|
||||
.. extension::
|
||||
|
||||
The :sql:`COPY` command is a PostgreSQL extension to the SQL standard.
|
||||
|
@ -502,7 +505,7 @@ The ``cursor`` class
|
|||
.. method:: copy_from(file, table, sep='\\t', null='\\\\N', size=8192, columns=None)
|
||||
|
||||
Read data *from* the file-like object *file* appending them to
|
||||
the table named *table*. See :ref:`copy` for an overview.
|
||||
the table named *table*.
|
||||
|
||||
:param file: file-like object to read data from. It must have both
|
||||
`!read()` and `!readline()` methods.
|
||||
|
@ -524,6 +527,13 @@ The ``cursor`` class
|
|||
>>> cur.fetchall()
|
||||
[(6, 42, 'foo'), (7, 74, 'bar')]
|
||||
|
||||
.. note:: the name of the table is not quoted: if the table name
|
||||
contains uppercase letters or special characters it must be quoted
|
||||
with double quotes::
|
||||
|
||||
cur.copy_from(f, '"TABLE"')
|
||||
|
||||
|
||||
.. versionchanged:: 2.0.6
|
||||
added the *columns* parameter.
|
||||
|
||||
|
@ -553,6 +563,12 @@ The ``cursor`` class
|
|||
2|\N|dada
|
||||
...
|
||||
|
||||
.. note:: the name of the table is not quoted: if the table name
|
||||
contains uppercase letters or special characters it must be quoted
|
||||
with double quotes::
|
||||
|
||||
cur.copy_to(f, '"TABLE"')
|
||||
|
||||
.. versionchanged:: 2.0.6
|
||||
added the *columns* parameter.
|
||||
|
||||
|
|
|
@ -611,3 +611,6 @@ Coroutine support
|
|||
|
||||
.. autofunction:: wait_select(conn)
|
||||
|
||||
.. versionchanged:: 2.6.2
|
||||
allow to cancel a query using :kbd:`Ctrl-C`, see
|
||||
:ref:`the FAQ <faq-interrupt-query>` for an example.
|
||||
|
|
|
@ -223,6 +223,37 @@ What are the advantages or disadvantages of using named cursors?
|
|||
little memory on the client and to skip or discard parts of the result set.
|
||||
|
||||
|
||||
.. _faq-interrupt-query:
|
||||
.. cssclass:: faq
|
||||
|
||||
How do I interrupt a long-running query in an interactive shell?
|
||||
Normally the interactive shell becomes unresponsive to :kbd:`Ctrl-C` when
|
||||
running a query. Using a connection in green mode allows Python to
|
||||
receive and handle the interrupt, although it may leave the connection
|
||||
broken, if the async callback doesn't handle the `!KeyboardInterrupt`
|
||||
correctly.
|
||||
|
||||
Starting from psycopg 2.6.2, the `~psycopg2.extras.wait_select` callback
|
||||
can handle a :kbd:`Ctrl-C` correctly. For previous versions, you can use
|
||||
`this implementation`__.
|
||||
|
||||
.. __: http://initd.org/psycopg/articles/2014/07/20/cancelling-postgresql-statements-python/
|
||||
|
||||
.. code-block:: pycon
|
||||
|
||||
>>> psycopg2.extensions.set_wait_callback(psycopg2.extensions.wait_select)
|
||||
>>> cnn = psycopg2.connect('')
|
||||
>>> cur = cnn.cursor()
|
||||
>>> cur.execute("select pg_sleep(10)")
|
||||
^C
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
QueryCanceledError: canceling statement due to user request
|
||||
|
||||
>>> cnn.rollback()
|
||||
>>> # You can use the connection and cursor again from here
|
||||
|
||||
|
||||
.. _faq-compile:
|
||||
|
||||
Problems compiling and deploying psycopg2
|
||||
|
|
|
@ -18,7 +18,7 @@ The current `!psycopg2` implementation supports:
|
|||
NOTE: keep consistent with setup.py and the /features/ page.
|
||||
|
||||
- Python 2 versions from 2.5 to 2.7
|
||||
- Python 3 versions from 3.1 to 3.4
|
||||
- Python 3 versions from 3.1 to 3.5
|
||||
- PostgreSQL versions from 7.4 to 9.4
|
||||
|
||||
.. _PostgreSQL: http://www.postgresql.org/
|
||||
|
@ -51,6 +51,16 @@ extension packages, *above all if you are a Windows or a Mac OS user*, please
|
|||
use a pre-compiled package and go straight to the :ref:`module usage <usage>`
|
||||
avoid bothering with the gory details.
|
||||
|
||||
.. note::
|
||||
|
||||
Regardless of the way `!psycopg2` is installed, at runtime it will need to
|
||||
use the libpq_ library. `!psycopg2` relies on the host OS to find the
|
||||
library file (usually ``libpq.so`` or ``libpq.dll``): if the library is
|
||||
installed in a standard location there is usually no problem; if the
|
||||
library is in a non-standard location you will have to tell somehow
|
||||
psycopg how to find it, which is OS-dependent (for instance setting a
|
||||
suitable :envvar:`LD_LIBRARY_PATH` on Linux).
|
||||
|
||||
|
||||
|
||||
.. _install-from-package:
|
||||
|
@ -95,7 +105,17 @@ Install from a package
|
|||
pair: Install; Windows
|
||||
|
||||
**Microsoft Windows**
|
||||
Jason Erickson maintains a packaged `Windows port of Psycopg`__ with
|
||||
There are two options to install a precompiled `psycopg2` package under windows:
|
||||
|
||||
**Option 1:** Using `pip`__ (Included in python 2.7.9+ and python 3.4+)
|
||||
and a binary wheel package. Launch windows' command prompt (`cmd.exe`)
|
||||
and execute the following command::
|
||||
|
||||
pip install psycopg2
|
||||
|
||||
.. __: https://pip.pypa.io/en/stable/installing/
|
||||
|
||||
**Option 2:** Jason Erickson maintains a packaged `Windows port of Psycopg`__ with
|
||||
installation executable. Download. Double click. Done.
|
||||
|
||||
.. __: http://www.stickpeople.com/projects/python/win-psycopg/
|
||||
|
|
166
doc/src/tools/pypi_docs_upload.py
Executable file
166
doc/src/tools/pypi_docs_upload.py
Executable file
|
@ -0,0 +1,166 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Standalone script to upload a project docs on PyPI
|
||||
|
||||
Hacked together from the following distutils extension, avaliable from
|
||||
https://bitbucket.org/jezdez/sphinx-pypi-upload/overview (ver. 0.2.1)
|
||||
|
||||
sphinx_pypi_upload
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
setuptools command for uploading Sphinx documentation to PyPI
|
||||
|
||||
:author: Jannis Leidel
|
||||
:contact: jannis@leidel.info
|
||||
:copyright: Copyright 2009, Jannis Leidel.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
import zipfile
|
||||
import httplib
|
||||
import base64
|
||||
import urlparse
|
||||
import tempfile
|
||||
import cStringIO as StringIO
|
||||
from ConfigParser import ConfigParser
|
||||
|
||||
from distutils import log
|
||||
from distutils.command.upload import upload
|
||||
from distutils.errors import DistutilsOptionError
|
||||
|
||||
class UploadDoc(object):
|
||||
"""Distutils command to upload Sphinx documentation."""
|
||||
def __init__(self, name, upload_dir, repository=None):
|
||||
self.name = name
|
||||
self.upload_dir = upload_dir
|
||||
|
||||
p = ConfigParser()
|
||||
p.read(os.path.expanduser('~/.pypirc'))
|
||||
self.username = p.get('pypi', 'username')
|
||||
self.password = p.get('pypi', 'password')
|
||||
|
||||
self.show_response = False
|
||||
self.repository = repository or upload.DEFAULT_REPOSITORY
|
||||
|
||||
def create_zipfile(self):
|
||||
# name = self.distribution.metadata.get_name()
|
||||
name = self.name
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
tmp_file = os.path.join(tmp_dir, "%s.zip" % name)
|
||||
zip_file = zipfile.ZipFile(tmp_file, "w")
|
||||
for root, dirs, files in os.walk(self.upload_dir):
|
||||
if not files:
|
||||
raise DistutilsOptionError, \
|
||||
"no files found in upload directory '%s'" % self.upload_dir
|
||||
for name in files:
|
||||
full = os.path.join(root, name)
|
||||
relative = root[len(self.upload_dir):].lstrip(os.path.sep)
|
||||
dest = os.path.join(relative, name)
|
||||
zip_file.write(full, dest)
|
||||
zip_file.close()
|
||||
return tmp_file
|
||||
|
||||
def upload_file(self, filename):
|
||||
content = open(filename,'rb').read()
|
||||
# meta = self.distribution.metadata
|
||||
data = {
|
||||
':action': 'doc_upload',
|
||||
'name': self.name, # meta.get_name(),
|
||||
'content': (os.path.basename(filename),content),
|
||||
}
|
||||
# set up the authentication
|
||||
auth = "Basic " + base64.encodestring(self.username + ":" + self.password).strip()
|
||||
|
||||
# Build up the MIME payload for the POST data
|
||||
boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
|
||||
sep_boundary = '\n--' + boundary
|
||||
end_boundary = sep_boundary + '--'
|
||||
body = StringIO.StringIO()
|
||||
for key, value in data.items():
|
||||
# handle multiple entries for the same name
|
||||
if type(value) != type([]):
|
||||
value = [value]
|
||||
for value in value:
|
||||
if type(value) is tuple:
|
||||
fn = ';filename="%s"' % value[0]
|
||||
value = value[1]
|
||||
else:
|
||||
fn = ""
|
||||
value = str(value)
|
||||
body.write(sep_boundary)
|
||||
body.write('\nContent-Disposition: form-data; name="%s"'%key)
|
||||
body.write(fn)
|
||||
body.write("\n\n")
|
||||
body.write(value)
|
||||
if value and value[-1] == '\r':
|
||||
body.write('\n') # write an extra newline (lurve Macs)
|
||||
body.write(end_boundary)
|
||||
body.write("\n")
|
||||
body = body.getvalue()
|
||||
|
||||
self.announce("Submitting documentation to %s" % (self.repository), log.INFO)
|
||||
|
||||
# build the Request
|
||||
# We can't use urllib2 since we need to send the Basic
|
||||
# auth right with the first request
|
||||
schema, netloc, url, params, query, fragments = \
|
||||
urlparse.urlparse(self.repository)
|
||||
assert not params and not query and not fragments
|
||||
if schema == 'http':
|
||||
http = httplib.HTTPConnection(netloc)
|
||||
elif schema == 'https':
|
||||
http = httplib.HTTPSConnection(netloc)
|
||||
else:
|
||||
raise AssertionError, "unsupported schema "+schema
|
||||
|
||||
data = ''
|
||||
loglevel = log.INFO
|
||||
try:
|
||||
http.connect()
|
||||
http.putrequest("POST", url)
|
||||
http.putheader('Content-type',
|
||||
'multipart/form-data; boundary=%s'%boundary)
|
||||
http.putheader('Content-length', str(len(body)))
|
||||
http.putheader('Authorization', auth)
|
||||
http.endheaders()
|
||||
http.send(body)
|
||||
except socket.error, e:
|
||||
self.announce(str(e), log.ERROR)
|
||||
return
|
||||
|
||||
response = http.getresponse()
|
||||
if response.status == 200:
|
||||
self.announce('Server response (%s): %s' % (response.status, response.reason),
|
||||
log.INFO)
|
||||
elif response.status == 301:
|
||||
location = response.getheader('Location')
|
||||
if location is None:
|
||||
location = 'http://packages.python.org/%s/' % self.name # meta.get_name()
|
||||
self.announce('Upload successful. Visit %s' % location,
|
||||
log.INFO)
|
||||
else:
|
||||
self.announce('Upload failed (%s): %s' % (response.status, response.reason),
|
||||
log.ERROR)
|
||||
if self.show_response:
|
||||
print '-'*75, response.read(), '-'*75
|
||||
|
||||
def run(self):
|
||||
zip_file = self.create_zipfile()
|
||||
self.upload_file(zip_file)
|
||||
os.remove(zip_file)
|
||||
|
||||
def announce(self, msg, *args, **kwargs):
|
||||
print msg
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 3:
|
||||
print >>sys.stderr, "usage: %s PROJECT UPLOAD_DIR" % sys.argv[0]
|
||||
sys.exit(2)
|
||||
|
||||
project, upload_dir = sys.argv[1:]
|
||||
up = UploadDoc(project, upload_dir=upload_dir)
|
||||
up.run()
|
||||
|
|
@ -679,7 +679,7 @@ older versions).
|
|||
|
||||
By default even a simple :sql:`SELECT` will start a transaction: in
|
||||
long-running programs, if no further action is taken, the session will
|
||||
remain "idle in transaction", a condition non desiderable for several
|
||||
remain "idle in transaction", an undesirable condition for several
|
||||
reasons (locks are held by the session, tables bloat...). For long lived
|
||||
scripts, either make sure to terminate a transaction as soon as possible or
|
||||
use an autocommit connection.
|
||||
|
@ -705,13 +705,28 @@ managers* and can be used with the ``with`` statement::
|
|||
|
||||
When a connection exits the ``with`` block, if no exception has been raised by
|
||||
the block, the transaction is committed. In case of exception the transaction
|
||||
is rolled back. In no case the connection is closed: a connection can be used
|
||||
in more than a ``with`` statement and each ``with`` block is effectively
|
||||
wrapped in a transaction.
|
||||
is rolled back.
|
||||
|
||||
When a cursor exits the ``with`` block it is closed, releasing any resource
|
||||
eventually associated with it. The state of the transaction is not affected.
|
||||
|
||||
Note that, unlike file objects or other resources, exiting the connection's
|
||||
``with`` block *doesn't close the connection* but only the transaction
|
||||
associated with it: a connection can be used in more than a ``with`` statement
|
||||
and each ``with`` block is effectively wrapped in a separate transaction::
|
||||
|
||||
conn = psycopg2.connect(DSN)
|
||||
|
||||
with conn:
|
||||
with conn.cursor() as curs:
|
||||
curs.execute(SQL1)
|
||||
|
||||
with conn:
|
||||
with conn.cursor() as curs:
|
||||
curs.execute(SQL2)
|
||||
|
||||
conn.close()
|
||||
|
||||
|
||||
|
||||
.. index::
|
||||
|
@ -849,11 +864,19 @@ Using COPY TO and COPY FROM
|
|||
|
||||
Psycopg `cursor` objects provide an interface to the efficient
|
||||
PostgreSQL |COPY|__ command to move data from files to tables and back.
|
||||
|
||||
Currently no adaptation is provided between Python and PostgreSQL types on
|
||||
|COPY|: the file can be any Python file-like object but its format must be in
|
||||
the format accepted by `PostgreSQL COPY command`__ (data fromat, escaped
|
||||
characters, etc).
|
||||
|
||||
.. __: COPY_
|
||||
|
||||
The methods exposed are:
|
||||
|
||||
`~cursor.copy_from()`
|
||||
Reads data *from* a file-like object appending them to a database table
|
||||
(:sql:`COPY table FROM file` syntax). The source file must have both
|
||||
(:sql:`COPY table FROM file` syntax). The source file must provide both
|
||||
`!read()` and `!readline()` method.
|
||||
|
||||
`~cursor.copy_to()`
|
||||
|
|
|
@ -38,11 +38,17 @@ def lookup(code, _cache={}):
|
|||
return _cache[code]
|
||||
|
||||
# Generate the lookup map at first usage.
|
||||
tmp = {}
|
||||
for k, v in globals().iteritems():
|
||||
if isinstance(v, str) and len(v) in (2, 5):
|
||||
_cache[v] = k
|
||||
tmp[v] = k
|
||||
|
||||
return lookup(code)
|
||||
assert tmp
|
||||
|
||||
# Atomic update, to avoid race condition on import (bug #382)
|
||||
_cache.update(tmp)
|
||||
|
||||
return _cache[code]
|
||||
|
||||
|
||||
# autogenerated data: do not edit below this point.
|
||||
|
@ -193,6 +199,8 @@ INVALID_ESCAPE_SEQUENCE = '22025'
|
|||
STRING_DATA_LENGTH_MISMATCH = '22026'
|
||||
TRIM_ERROR = '22027'
|
||||
ARRAY_SUBSCRIPT_ERROR = '2202E'
|
||||
INVALID_TABLESAMPLE_REPEAT = '2202G'
|
||||
INVALID_TABLESAMPLE_ARGUMENT = '2202H'
|
||||
FLOATING_POINT_EXCEPTION = '22P01'
|
||||
INVALID_TEXT_REPRESENTATION = '22P02'
|
||||
INVALID_BINARY_REPRESENTATION = '22P03'
|
||||
|
@ -265,6 +273,7 @@ INVALID_SQLSTATE_RETURNED = '39001'
|
|||
NULL_VALUE_NOT_ALLOWED = '39004'
|
||||
TRIGGER_PROTOCOL_VIOLATED = '39P01'
|
||||
SRF_PROTOCOL_VIOLATED = '39P02'
|
||||
EVENT_TRIGGER_PROTOCOL_VIOLATED = '39P03'
|
||||
|
||||
# Class 3B - Savepoint Exception
|
||||
SAVEPOINT_EXCEPTION = '3B000'
|
||||
|
@ -402,6 +411,7 @@ PLPGSQL_ERROR = 'P0000'
|
|||
RAISE_EXCEPTION = 'P0001'
|
||||
NO_DATA_FOUND = 'P0002'
|
||||
TOO_MANY_ROWS = 'P0003'
|
||||
ASSERT_FAILURE = 'P0004'
|
||||
|
||||
# Class XX - Internal Error
|
||||
INTERNAL_ERROR = 'XX000'
|
||||
|
|
|
@ -434,7 +434,7 @@ class MinTimeLoggingCursor(LoggingCursor):
|
|||
|
||||
def callproc(self, procname, vars=None):
|
||||
self.timestamp = _time.time()
|
||||
return LoggingCursor.execute(self, procname, vars)
|
||||
return LoggingCursor.callproc(self, procname, vars)
|
||||
|
||||
|
||||
# a dbtype and adapter for Python UUID type
|
||||
|
@ -575,6 +575,7 @@ def wait_select(conn):
|
|||
from psycopg2.extensions import POLL_OK, POLL_READ, POLL_WRITE
|
||||
|
||||
while 1:
|
||||
try:
|
||||
state = conn.poll()
|
||||
if state == POLL_OK:
|
||||
break
|
||||
|
@ -584,6 +585,10 @@ def wait_select(conn):
|
|||
select.select([], [conn.fileno()], [])
|
||||
else:
|
||||
raise conn.OperationalError("bad state from poll: %s" % state)
|
||||
except KeyboardInterrupt:
|
||||
conn.cancel()
|
||||
# the loop will be broken by a server error
|
||||
continue
|
||||
|
||||
|
||||
def _solve_conn_curs(conn_or_curs):
|
||||
|
|
|
@ -204,8 +204,8 @@ class PersistentConnectionPool(AbstractConnectionPool):
|
|||
|
||||
# we we'll need the thread module, to determine thread ids, so we
|
||||
# import it here and copy it in an instance variable
|
||||
import thread
|
||||
self.__thread = thread
|
||||
import thread as _thread # work around for 2to3 bug - see ticket #348
|
||||
self.__thread = _thread
|
||||
|
||||
def getconn(self):
|
||||
"""Generate thread id and return a connection."""
|
||||
|
|
|
@ -28,7 +28,7 @@ old code while porting to psycopg 2. Import it as follows::
|
|||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
# License for more details.
|
||||
|
||||
import _psycopg as _2psycopg
|
||||
import psycopg2._psycopg as _2psycopg
|
||||
from psycopg2.extensions import cursor as _2cursor
|
||||
from psycopg2.extensions import connection as _2connection
|
||||
|
||||
|
|
|
@ -451,7 +451,7 @@ psyco_TimestampFromTicks(PyObject *self, PyObject *args)
|
|||
tz);
|
||||
|
||||
exit:
|
||||
Py_DECREF(tz);
|
||||
Py_XDECREF(tz);
|
||||
Py_XDECREF(m);
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -39,6 +39,14 @@ list_quote(listObject *self)
|
|||
/* adapt the list by calling adapt() recursively and then wrapping
|
||||
everything into "ARRAY[]" */
|
||||
PyObject *tmp = NULL, *str = NULL, *joined = NULL, *res = NULL;
|
||||
|
||||
/* list consisting of only NULL don't work with the ARRAY[] construct
|
||||
* so we use the {NULL,...} syntax. Note however that list of lists where
|
||||
* some element is a list of only null still fails: for that we should use
|
||||
* the '{...}' syntax uniformly but we cannot do it in the current
|
||||
* infrastructure. TODO in psycopg3 */
|
||||
int all_nulls = 1;
|
||||
|
||||
Py_ssize_t i, len;
|
||||
|
||||
len = PyList_GET_SIZE(self->wrapped);
|
||||
|
@ -60,6 +68,7 @@ list_quote(listObject *self)
|
|||
quoted = microprotocol_getquoted(wrapped,
|
||||
(connectionObject*)self->connection);
|
||||
if (quoted == NULL) goto error;
|
||||
all_nulls = 0;
|
||||
}
|
||||
|
||||
/* here we don't loose a refcnt: SET_ITEM does not change the
|
||||
|
@ -74,7 +83,12 @@ list_quote(listObject *self)
|
|||
joined = PyObject_CallMethod(str, "join", "(O)", tmp);
|
||||
if (joined == NULL) goto error;
|
||||
|
||||
/* PG doesn't like ARRAY[NULL..] */
|
||||
if (!all_nulls) {
|
||||
res = Bytes_FromFormat("ARRAY[%s]", Bytes_AsString(joined));
|
||||
} else {
|
||||
res = Bytes_FromFormat("'{%s}'", Bytes_AsString(joined));
|
||||
}
|
||||
|
||||
error:
|
||||
Py_XDECREF(tmp);
|
||||
|
|
|
@ -36,44 +36,56 @@ static const char *default_encoding = "latin1";
|
|||
|
||||
/* qstring_quote - do the quote process on plain and unicode strings */
|
||||
|
||||
const char *
|
||||
_qstring_get_encoding(qstringObject *self)
|
||||
{
|
||||
/* if the wrapped object is an unicode object we can encode it to match
|
||||
conn->encoding but if the encoding is not specified we don't know what
|
||||
to do and we raise an exception */
|
||||
if (self->conn) {
|
||||
return self->conn->codec;
|
||||
}
|
||||
else {
|
||||
return self->encoding ? self->encoding : default_encoding;
|
||||
}
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
qstring_quote(qstringObject *self)
|
||||
{
|
||||
PyObject *str = NULL;
|
||||
char *s, *buffer = NULL;
|
||||
Py_ssize_t len, qlen;
|
||||
const char *encoding = default_encoding;
|
||||
const char *encoding;
|
||||
PyObject *rv = NULL;
|
||||
|
||||
/* if the wrapped object is an unicode object we can encode it to match
|
||||
conn->encoding but if the encoding is not specified we don't know what
|
||||
to do and we raise an exception */
|
||||
if (self->conn) {
|
||||
encoding = self->conn->codec;
|
||||
}
|
||||
|
||||
encoding = _qstring_get_encoding(self);
|
||||
Dprintf("qstring_quote: encoding to %s", encoding);
|
||||
|
||||
if (PyUnicode_Check(self->wrapped) && encoding) {
|
||||
if (PyUnicode_Check(self->wrapped)) {
|
||||
if (encoding) {
|
||||
str = PyUnicode_AsEncodedString(self->wrapped, encoding, NULL);
|
||||
Dprintf("qstring_quote: got encoded object at %p", str);
|
||||
if (str == NULL) goto exit;
|
||||
}
|
||||
else {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"missing encoding to encode unicode object");
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
/* if the wrapped object is a simple string, we don't know how to
|
||||
/* if the wrapped object is a binary string, we don't know how to
|
||||
(re)encode it, so we pass it as-is */
|
||||
else if (PyString_Check(self->wrapped)) {
|
||||
else if (Bytes_Check(self->wrapped)) {
|
||||
str = self->wrapped;
|
||||
/* INCREF to make it ref-wise identical to unicode one */
|
||||
Py_INCREF(str);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* if the wrapped object is not a string, this is an error */
|
||||
else {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"can't quote non-string object (or missing encoding)");
|
||||
PyErr_SetString(PyExc_TypeError, "can't quote non-string object");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
|
@ -150,13 +162,32 @@ qstring_conform(qstringObject *self, PyObject *args)
|
|||
static PyObject *
|
||||
qstring_get_encoding(qstringObject *self)
|
||||
{
|
||||
const char *encoding = default_encoding;
|
||||
|
||||
if (self->conn) {
|
||||
encoding = self->conn->codec;
|
||||
const char *encoding;
|
||||
encoding = _qstring_get_encoding(self);
|
||||
return Text_FromUTF8(encoding);
|
||||
}
|
||||
|
||||
return Text_FromUTF8(encoding);
|
||||
static int
|
||||
qstring_set_encoding(qstringObject *self, PyObject *pyenc)
|
||||
{
|
||||
int rv = -1;
|
||||
const char *tmp;
|
||||
char *cenc;
|
||||
|
||||
/* get a C copy of the encoding (which may come from unicode) */
|
||||
Py_INCREF(pyenc);
|
||||
if (!(pyenc = psycopg_ensure_bytes(pyenc))) { goto exit; }
|
||||
if (!(tmp = Bytes_AsString(pyenc))) { goto exit; }
|
||||
if (0 > psycopg_strdup(&cenc, tmp, 0)) { goto exit; }
|
||||
|
||||
Dprintf("qstring_set_encoding: encoding set to %s", cenc);
|
||||
PyMem_Free((void *)self->encoding);
|
||||
self->encoding = cenc;
|
||||
rv = 0;
|
||||
|
||||
exit:
|
||||
Py_XDECREF(pyenc);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/** the QuotedString object **/
|
||||
|
@ -183,7 +214,7 @@ static PyMethodDef qstringObject_methods[] = {
|
|||
static PyGetSetDef qstringObject_getsets[] = {
|
||||
{ "encoding",
|
||||
(getter)qstring_get_encoding,
|
||||
(setter)NULL,
|
||||
(setter)qstring_set_encoding,
|
||||
"current encoding of the adapter" },
|
||||
{NULL}
|
||||
};
|
||||
|
@ -216,6 +247,7 @@ qstring_dealloc(PyObject* obj)
|
|||
Py_CLEAR(self->wrapped);
|
||||
Py_CLEAR(self->buffer);
|
||||
Py_CLEAR(self->conn);
|
||||
PyMem_Free((void *)self->encoding);
|
||||
|
||||
Dprintf("qstring_dealloc: deleted qstring object at %p, refcnt = "
|
||||
FORMAT_CODE_PY_SSIZE_T,
|
||||
|
|
|
@ -39,6 +39,9 @@ typedef struct {
|
|||
PyObject *buffer;
|
||||
|
||||
connectionObject *conn;
|
||||
|
||||
const char *encoding;
|
||||
|
||||
} qstringObject;
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
@ -129,28 +129,33 @@ static int pthread_mutex_init(pthread_mutex_t *mutex, void* fake)
|
|||
/* remove the inline keyword, since it doesn't work unless C++ file */
|
||||
#define inline
|
||||
|
||||
/* Hmmm, MSVC doesn't have a isnan/isinf function, but has _isnan function */
|
||||
/* Hmmm, MSVC <2015 doesn't have a isnan/isinf function, but has _isnan function */
|
||||
#if defined (_MSC_VER)
|
||||
#if !defined(isnan)
|
||||
#define isnan(x) (_isnan(x))
|
||||
/* The following line was hacked together from simliar code by Bjorn Reese
|
||||
* in libxml2 code */
|
||||
#define isinf(x) ((_fpclass(x) == _FPCLASS_PINF) ? 1 \
|
||||
: ((_fpclass(x) == _FPCLASS_NINF) ? -1 : 0))
|
||||
|
||||
#endif
|
||||
#define strcasecmp(x, y) lstrcmpi(x, y)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* what's this, we have no round function either? */
|
||||
#if (defined(__FreeBSD__) && __FreeBSD_version < 503000) \
|
||||
|| (defined(_WIN32) && !defined(__GNUC__)) \
|
||||
|| (defined(sun) || defined(__sun__)) \
|
||||
&& (defined(__SunOS_5_8) || defined(__SunOS_5_9))
|
||||
/* what's this, we have no round function either? */
|
||||
|
||||
/* round has been added in the standard library with MSVC 2015 */
|
||||
#if _MSC_VER < 1900
|
||||
static double round(double num)
|
||||
{
|
||||
return (num >= 0) ? floor(num + 0.5) : ceil(num - 0.5);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* resolve missing isinf() function for Solaris */
|
||||
#if defined (__SVR4) && defined (__sun)
|
||||
|
|
|
@ -840,6 +840,10 @@ psyco_conn_get_exception(PyObject *self, void *closure)
|
|||
return exception;
|
||||
}
|
||||
|
||||
|
||||
#define psyco_conn_poll_doc \
|
||||
"poll() -> int -- Advance the connection or query process without blocking."
|
||||
|
||||
static PyObject *
|
||||
psyco_conn_poll(connectionObject *self)
|
||||
{
|
||||
|
@ -980,7 +984,7 @@ static struct PyMethodDef connectionObject_methods[] = {
|
|||
{"reset", (PyCFunction)psyco_conn_reset,
|
||||
METH_NOARGS, psyco_conn_reset_doc},
|
||||
{"poll", (PyCFunction)psyco_conn_poll,
|
||||
METH_NOARGS, psyco_conn_lobject_doc},
|
||||
METH_NOARGS, psyco_conn_poll_doc},
|
||||
{"fileno", (PyCFunction)psyco_conn_fileno,
|
||||
METH_NOARGS, psyco_conn_fileno_doc},
|
||||
{"isexecuting", (PyCFunction)psyco_conn_isexecuting,
|
||||
|
@ -1168,7 +1172,7 @@ connection_repr(connectionObject *self)
|
|||
{
|
||||
return PyString_FromFormat(
|
||||
"<connection object at %p; dsn: '%s', closed: %ld>",
|
||||
self, self->dsn, self->closed);
|
||||
self, (self->dsn ? self->dsn : "<unintialized>"), self->closed);
|
||||
}
|
||||
|
||||
static int
|
||||
|
|
|
@ -335,7 +335,7 @@ _psyco_curs_merge_query_args(cursorObject *self,
|
|||
PyErr_Fetch(&err, &arg, &trace);
|
||||
|
||||
if (err && PyErr_GivenExceptionMatches(err, PyExc_TypeError)) {
|
||||
Dprintf("psyco_curs_execute: TypeError exception catched");
|
||||
Dprintf("psyco_curs_execute: TypeError exception caught");
|
||||
PyErr_NormalizeException(&err, &arg, &trace);
|
||||
|
||||
if (PyObject_HasAttrString(arg, "args")) {
|
||||
|
@ -1546,7 +1546,7 @@ psyco_curs_copy_expert(cursorObject *self, PyObject *args, PyObject *kwargs)
|
|||
if (sql == NULL) { goto exit; }
|
||||
|
||||
/* This validation of file is rather weak, in that it doesn't enforce the
|
||||
assocation between "COPY FROM" -> "read" and "COPY TO" -> "write".
|
||||
association between "COPY FROM" -> "read" and "COPY TO" -> "write".
|
||||
However, the error handling in _pq_copy_[in|out] must be able to handle
|
||||
the case where the attempt to call file.read|write fails, so no harm
|
||||
done. */
|
||||
|
|
|
@ -60,8 +60,8 @@ RAISES_NEG HIDDEN int lobject_export(lobjectObject *self, const char *filename);
|
|||
RAISES_NEG HIDDEN Py_ssize_t lobject_read(lobjectObject *self, char *buf, size_t len);
|
||||
RAISES_NEG HIDDEN Py_ssize_t lobject_write(lobjectObject *self, const char *buf,
|
||||
size_t len);
|
||||
RAISES_NEG HIDDEN long lobject_seek(lobjectObject *self, long pos, int whence);
|
||||
RAISES_NEG HIDDEN long lobject_tell(lobjectObject *self);
|
||||
RAISES_NEG HIDDEN Py_ssize_t lobject_seek(lobjectObject *self, Py_ssize_t pos, int whence);
|
||||
RAISES_NEG HIDDEN Py_ssize_t lobject_tell(lobjectObject *self);
|
||||
RAISES_NEG HIDDEN int lobject_truncate(lobjectObject *self, size_t len);
|
||||
RAISES_NEG HIDDEN int lobject_close(lobjectObject *self);
|
||||
|
||||
|
|
|
@ -376,12 +376,12 @@ lobject_read(lobjectObject *self, char *buf, size_t len)
|
|||
|
||||
/* lobject_seek - move the current position in the lo */
|
||||
|
||||
RAISES_NEG long
|
||||
lobject_seek(lobjectObject *self, long pos, int whence)
|
||||
RAISES_NEG Py_ssize_t
|
||||
lobject_seek(lobjectObject *self, Py_ssize_t pos, int whence)
|
||||
{
|
||||
PGresult *pgres = NULL;
|
||||
char *error = NULL;
|
||||
long where;
|
||||
Py_ssize_t where;
|
||||
|
||||
Dprintf("lobject_seek: fd = %d, pos = %ld, whence = %d",
|
||||
self->fd, pos, whence);
|
||||
|
@ -391,12 +391,12 @@ lobject_seek(lobjectObject *self, long pos, int whence)
|
|||
|
||||
#ifdef HAVE_LO64
|
||||
if (self->conn->server_version < 90300) {
|
||||
where = (long)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
|
||||
where = (Py_ssize_t)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
|
||||
} else {
|
||||
where = lo_lseek64(self->conn->pgconn, self->fd, pos, whence);
|
||||
where = (Py_ssize_t)lo_lseek64(self->conn->pgconn, self->fd, pos, whence);
|
||||
}
|
||||
#else
|
||||
where = (long)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
|
||||
where = (Py_ssize_t)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
|
||||
#endif
|
||||
Dprintf("lobject_seek: where = %ld", where);
|
||||
if (where < 0)
|
||||
|
@ -412,12 +412,12 @@ lobject_seek(lobjectObject *self, long pos, int whence)
|
|||
|
||||
/* lobject_tell - tell the current position in the lo */
|
||||
|
||||
RAISES_NEG long
|
||||
RAISES_NEG Py_ssize_t
|
||||
lobject_tell(lobjectObject *self)
|
||||
{
|
||||
PGresult *pgres = NULL;
|
||||
char *error = NULL;
|
||||
long where;
|
||||
Py_ssize_t where;
|
||||
|
||||
Dprintf("lobject_tell: fd = %d", self->fd);
|
||||
|
||||
|
@ -426,12 +426,12 @@ lobject_tell(lobjectObject *self)
|
|||
|
||||
#ifdef HAVE_LO64
|
||||
if (self->conn->server_version < 90300) {
|
||||
where = (long)lo_tell(self->conn->pgconn, self->fd);
|
||||
where = (Py_ssize_t)lo_tell(self->conn->pgconn, self->fd);
|
||||
} else {
|
||||
where = lo_tell64(self->conn->pgconn, self->fd);
|
||||
where = (Py_ssize_t)lo_tell64(self->conn->pgconn, self->fd);
|
||||
}
|
||||
#else
|
||||
where = (long)lo_tell(self->conn->pgconn, self->fd);
|
||||
where = (Py_ssize_t)lo_tell(self->conn->pgconn, self->fd);
|
||||
#endif
|
||||
Dprintf("lobject_tell: where = %ld", where);
|
||||
if (where < 0)
|
||||
|
|
|
@ -105,7 +105,7 @@ psyco_lobj_write(lobjectObject *self, PyObject *args)
|
|||
goto exit;
|
||||
}
|
||||
|
||||
rv = PyInt_FromLong((long)res);
|
||||
rv = PyInt_FromSsize_t((Py_ssize_t)res);
|
||||
|
||||
exit:
|
||||
Py_XDECREF(data);
|
||||
|
@ -121,7 +121,7 @@ static PyObject *
|
|||
psyco_lobj_read(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *res;
|
||||
long where, end;
|
||||
Py_ssize_t where, end;
|
||||
Py_ssize_t size = -1;
|
||||
char *buffer;
|
||||
|
||||
|
@ -165,10 +165,10 @@ psyco_lobj_read(lobjectObject *self, PyObject *args)
|
|||
static PyObject *
|
||||
psyco_lobj_seek(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
long offset, pos=0;
|
||||
Py_ssize_t offset, pos=0;
|
||||
int whence=0;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "l|i", &offset, &whence))
|
||||
if (!PyArg_ParseTuple(args, "n|i", &offset, &whence))
|
||||
return NULL;
|
||||
|
||||
EXC_IF_LOBJ_CLOSED(self);
|
||||
|
@ -187,8 +187,8 @@ psyco_lobj_seek(lobjectObject *self, PyObject *args)
|
|||
#else
|
||||
if (offset < INT_MIN || offset > INT_MAX) {
|
||||
PyErr_Format(InterfaceError,
|
||||
"offset out of range (%ld): this psycopg version was not built "
|
||||
"with lobject 64 API support",
|
||||
"offset out of range (" FORMAT_CODE_PY_SSIZE_T "): "
|
||||
"this psycopg version was not built with lobject 64 API support",
|
||||
offset);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ psyco_lobj_seek(lobjectObject *self, PyObject *args)
|
|||
if ((pos = lobject_seek(self, offset, whence)) < 0)
|
||||
return NULL;
|
||||
|
||||
return PyLong_FromLong(pos);
|
||||
return PyInt_FromSsize_t(pos);
|
||||
}
|
||||
|
||||
/* tell method - tell current position in the lobject */
|
||||
|
@ -208,7 +208,7 @@ psyco_lobj_seek(lobjectObject *self, PyObject *args)
|
|||
static PyObject *
|
||||
psyco_lobj_tell(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
long pos;
|
||||
Py_ssize_t pos;
|
||||
|
||||
EXC_IF_LOBJ_CLOSED(self);
|
||||
EXC_IF_LOBJ_LEVEL0(self);
|
||||
|
@ -217,7 +217,7 @@ psyco_lobj_tell(lobjectObject *self, PyObject *args)
|
|||
if ((pos = lobject_tell(self)) < 0)
|
||||
return NULL;
|
||||
|
||||
return PyLong_FromLong(pos);
|
||||
return PyInt_FromSsize_t(pos);
|
||||
}
|
||||
|
||||
/* unlink method - unlink (destroy) the lobject */
|
||||
|
@ -274,9 +274,9 @@ psyco_lobj_get_closed(lobjectObject *self, void *closure)
|
|||
static PyObject *
|
||||
psyco_lobj_truncate(lobjectObject *self, PyObject *args)
|
||||
{
|
||||
long len = 0;
|
||||
Py_ssize_t len = 0;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "|l", &len))
|
||||
if (!PyArg_ParseTuple(args, "|n", &len))
|
||||
return NULL;
|
||||
|
||||
EXC_IF_LOBJ_CLOSED(self);
|
||||
|
@ -286,16 +286,16 @@ psyco_lobj_truncate(lobjectObject *self, PyObject *args)
|
|||
#ifdef HAVE_LO64
|
||||
if (len > INT_MAX && self->conn->server_version < 90300) {
|
||||
PyErr_Format(NotSupportedError,
|
||||
"len out of range (%ld): server version %d "
|
||||
"does not support the lobject 64 API",
|
||||
"len out of range (" FORMAT_CODE_PY_SSIZE_T "): "
|
||||
"server version %d does not support the lobject 64 API",
|
||||
len, self->conn->server_version);
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
if (len > INT_MAX) {
|
||||
PyErr_Format(InterfaceError,
|
||||
"len out of range (%ld): this psycopg version was not built "
|
||||
"with lobject 64 API support",
|
||||
"len out of range (" FORMAT_CODE_PY_SSIZE_T "): "
|
||||
"this psycopg version was not built with lobject 64 API support",
|
||||
len);
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -161,14 +161,16 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult **pgres)
|
|||
|
||||
if (conn == NULL) {
|
||||
PyErr_SetString(DatabaseError,
|
||||
"psycopg went psycotic and raised a null error");
|
||||
"psycopg went psychotic and raised a null error");
|
||||
return;
|
||||
}
|
||||
|
||||
/* if the connection has somehow beed broken, we mark the connection
|
||||
/* if the connection has somehow been broken, we mark the connection
|
||||
object as closed but requiring cleanup */
|
||||
if (conn->pgconn != NULL && PQstatus(conn->pgconn) == CONNECTION_BAD)
|
||||
if (conn->pgconn != NULL && PQstatus(conn->pgconn) == CONNECTION_BAD) {
|
||||
conn->closed = 2;
|
||||
exc = OperationalError;
|
||||
}
|
||||
|
||||
if (pgres == NULL && curs != NULL)
|
||||
pgres = &curs->pgres;
|
||||
|
@ -190,8 +192,10 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult **pgres)
|
|||
raise and a meaningful message is better than an empty one.
|
||||
Note: it can happen without it being our error: see ticket #82 */
|
||||
if (err == NULL || err[0] == '\0') {
|
||||
PyErr_SetString(DatabaseError,
|
||||
"error with no message from the libpq");
|
||||
PyErr_Format(DatabaseError,
|
||||
"error with status %s and no message from the libpq",
|
||||
PQresStatus(pgres == NULL ?
|
||||
PQstatus(conn->pgconn) : PQresultStatus(*pgres)));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -200,9 +204,9 @@ pq_raise(connectionObject *conn, cursorObject *curs, PGresult **pgres)
|
|||
if (code != NULL) {
|
||||
exc = exception_from_sqlstate(code);
|
||||
}
|
||||
else {
|
||||
/* Fallback if there is no exception code (reported happening e.g.
|
||||
* when the connection is closed). */
|
||||
else if (exc == NULL) {
|
||||
/* Fallback if there is no exception code (unless we already
|
||||
determined that the connection was closed). */
|
||||
exc = DatabaseError;
|
||||
}
|
||||
|
||||
|
@ -905,7 +909,7 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result, int
|
|||
PyErr_SetString(OperationalError, PQerrorMessage(curs->conn->pgconn));
|
||||
return -1;
|
||||
}
|
||||
Dprintf("curs_execute: pg connection at %p OK", curs->conn->pgconn);
|
||||
Dprintf("pq_execute: pg connection at %p OK", curs->conn->pgconn);
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
pthread_mutex_lock(&(curs->conn->lock));
|
||||
|
@ -930,8 +934,11 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result, int
|
|||
Py_UNBLOCK_THREADS;
|
||||
}
|
||||
|
||||
/* dont let pgres = NULL go to pq_fetch() */
|
||||
/* don't let pgres = NULL go to pq_fetch() */
|
||||
if (curs->pgres == NULL) {
|
||||
if (CONNECTION_BAD == PQstatus(curs->conn->pgconn)) {
|
||||
curs->conn->closed = 2;
|
||||
}
|
||||
pthread_mutex_unlock(&(curs->conn->lock));
|
||||
Py_BLOCK_THREADS;
|
||||
if (!PyErr_Occurred()) {
|
||||
|
@ -959,6 +966,9 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result, int
|
|||
|
||||
CLEARPGRES(curs->pgres);
|
||||
if (PQsendQuery(curs->conn->pgconn, query) == 0) {
|
||||
if (CONNECTION_BAD == PQstatus(curs->conn->pgconn)) {
|
||||
curs->conn->closed = 2;
|
||||
}
|
||||
pthread_mutex_unlock(&(curs->conn->lock));
|
||||
Py_BLOCK_THREADS;
|
||||
PyErr_SetString(OperationalError,
|
||||
|
@ -980,6 +990,10 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result, int
|
|||
}
|
||||
else {
|
||||
/* there was an error */
|
||||
pthread_mutex_unlock(&(curs->conn->lock));
|
||||
Py_BLOCK_THREADS;
|
||||
PyErr_SetString(OperationalError,
|
||||
PQerrorMessage(curs->conn->pgconn));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
@ -1387,7 +1401,11 @@ _pq_copy_in_v3(cursorObject *curs)
|
|||
Py_DECREF(str);
|
||||
}
|
||||
}
|
||||
PyErr_Restore(t, ex, tb);
|
||||
/* Clear the Py exception: it will be re-raised from the libpq */
|
||||
Py_XDECREF(t);
|
||||
Py_XDECREF(ex);
|
||||
Py_XDECREF(tb);
|
||||
PyErr_Clear();
|
||||
}
|
||||
res = PQputCopyEnd(curs->conn->pgconn, buf);
|
||||
}
|
||||
|
@ -1591,11 +1609,26 @@ pq_fetch(cursorObject *curs, int no_result)
|
|||
ex = -1;
|
||||
break;
|
||||
|
||||
default:
|
||||
Dprintf("pq_fetch: uh-oh, something FAILED: pgconn = %p", curs->conn);
|
||||
case PGRES_BAD_RESPONSE:
|
||||
case PGRES_NONFATAL_ERROR:
|
||||
case PGRES_FATAL_ERROR:
|
||||
Dprintf("pq_fetch: uh-oh, something FAILED: status = %d pgconn = %p",
|
||||
pgstatus, curs->conn);
|
||||
pq_raise(curs->conn, curs, NULL);
|
||||
ex = -1;
|
||||
break;
|
||||
|
||||
default:
|
||||
/* PGRES_COPY_BOTH, PGRES_SINGLE_TUPLE, future statuses */
|
||||
Dprintf("pq_fetch: got unsupported result: status = %d pgconn = %p",
|
||||
pgstatus, curs->conn);
|
||||
PyErr_Format(NotSupportedError,
|
||||
"got server response with unsupported status %s",
|
||||
PQresStatus(curs->pgres == NULL ?
|
||||
PQstatus(curs->conn->pgconn) : PQresultStatus(curs->pgres)));
|
||||
CLEARPGRES(curs->pgres);
|
||||
ex = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* error checking, close the connection if necessary (some critical errors
|
||||
|
|
|
@ -176,6 +176,29 @@ psyco_register_type(PyObject *self, PyObject *args)
|
|||
}
|
||||
|
||||
|
||||
|
||||
/* Make sure libcrypto thread callbacks are set up. */
|
||||
static void
|
||||
psyco_libcrypto_threads_init(void)
|
||||
{
|
||||
PyObject *m;
|
||||
|
||||
/* importing the ssl module sets up Python's libcrypto callbacks */
|
||||
if ((m = PyImport_ImportModule("ssl"))) {
|
||||
/* disable libcrypto setup in libpq, so it won't stomp on the callbacks
|
||||
that have already been set up */
|
||||
#if PG_VERSION_HEX >= 0x080400
|
||||
PQinitOpenSSL(1, 0);
|
||||
#endif
|
||||
Py_DECREF(m);
|
||||
}
|
||||
else {
|
||||
/* might mean that Python has been compiled without OpenSSL support,
|
||||
fall back to relying on libpq's libcrypto locking */
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize the default adapters map
|
||||
*
|
||||
* Return 0 on success, else -1 and set an exception.
|
||||
|
@ -814,6 +837,9 @@ INIT_MODULE(_psycopg)(void)
|
|||
Py_TYPE(&lobjectType) = &PyType_Type;
|
||||
if (PyType_Ready(&lobjectType) == -1) goto exit;
|
||||
|
||||
/* initialize libcrypto threading callbacks */
|
||||
psyco_libcrypto_threads_init();
|
||||
|
||||
/* import mx.DateTime module, if necessary */
|
||||
#ifdef HAVE_MXDATETIME
|
||||
Py_TYPE(&mxdatetimeType) = &PyType_Type;
|
||||
|
|
|
@ -33,7 +33,7 @@ def main():
|
|||
file_start = read_base_file(filename)
|
||||
# If you add a version to the list fix the docs (errorcodes.rst, err.rst)
|
||||
classes, errors = fetch_errors(
|
||||
['8.1', '8.2', '8.3', '8.4', '9.0', '9.1', '9.2', '9.3', '9.4'])
|
||||
['8.1', '8.2', '8.3', '8.4', '9.0', '9.1', '9.2', '9.3', '9.4', '9.5'])
|
||||
|
||||
f = open(filename, "w")
|
||||
for line in file_start:
|
||||
|
|
11
setup.py
11
setup.py
|
@ -41,6 +41,7 @@ Programming Language :: Python :: 3.1
|
|||
Programming Language :: Python :: 3.2
|
||||
Programming Language :: Python :: 3.3
|
||||
Programming Language :: Python :: 3.4
|
||||
Programming Language :: Python :: 3.5
|
||||
Programming Language :: C
|
||||
Programming Language :: SQL
|
||||
Topic :: Database
|
||||
|
@ -57,6 +58,9 @@ import os
|
|||
import sys
|
||||
import re
|
||||
import subprocess
|
||||
try:
|
||||
from setuptools import setup, Extension
|
||||
except ImportError:
|
||||
from distutils.core import setup, Extension
|
||||
from distutils.command.build_ext import build_ext
|
||||
from distutils.sysconfig import get_python_inc
|
||||
|
@ -86,7 +90,7 @@ except ImportError:
|
|||
# Take a look at http://www.python.org/dev/peps/pep-0386/
|
||||
# for a consistent versioning pattern.
|
||||
|
||||
PSYCOPG_VERSION = '2.6'
|
||||
PSYCOPG_VERSION = '2.6.2'
|
||||
|
||||
version_flags = ['dt', 'dec']
|
||||
|
||||
|
@ -301,6 +305,10 @@ class psycopg_build_ext(build_ext):
|
|||
except AttributeError:
|
||||
ext_path = os.path.join(self.build_lib,
|
||||
'psycopg2', '_psycopg.pyd')
|
||||
# Make sure spawn() will work if compile() was never
|
||||
# called. https://github.com/psycopg/psycopg2/issues/380
|
||||
if not self.compiler.initialized:
|
||||
self.compiler.initialize()
|
||||
self.compiler.spawn(
|
||||
['mt.exe', '-nologo', '-manifest',
|
||||
os.path.join('psycopg', manifest),
|
||||
|
@ -343,6 +351,7 @@ class psycopg_build_ext(build_ext):
|
|||
self.libraries.append("advapi32")
|
||||
if self.compiler_is_msvc():
|
||||
# MSVC requires an explicit "libpq"
|
||||
if "pq" in self.libraries:
|
||||
self.libraries.remove("pq")
|
||||
self.libraries.append("secur32")
|
||||
self.libraries.append("libpq")
|
||||
|
|
|
@ -34,6 +34,7 @@ import test_connection
|
|||
import test_copy
|
||||
import test_cursor
|
||||
import test_dates
|
||||
import test_errcodes
|
||||
import test_extras_dictcursor
|
||||
import test_green
|
||||
import test_lobject
|
||||
|
@ -71,6 +72,7 @@ def test_suite():
|
|||
suite.addTest(test_copy.test_suite())
|
||||
suite.addTest(test_cursor.test_suite())
|
||||
suite.addTest(test_dates.test_suite())
|
||||
suite.addTest(test_errcodes.test_suite())
|
||||
suite.addTest(test_extras_dictcursor.test_suite())
|
||||
suite.addTest(test_green.test_suite())
|
||||
suite.addTest(test_lobject.test_suite())
|
||||
|
|
|
@ -62,7 +62,7 @@ import sys
|
|||
# - Reversed the polarity of buggy test in test_description
|
||||
# - Test exception hierarchy correctly
|
||||
# - self.populate is now self._populate(), so if a driver stub
|
||||
# overrides self.ddl1 this change propogates
|
||||
# overrides self.ddl1 this change propagates
|
||||
# - VARCHAR columns now have a width, which will hopefully make the
|
||||
# DDL even more portible (this will be reversed if it causes more problems)
|
||||
# - cursor.rowcount being checked after various execute and fetchXXX methods
|
||||
|
@ -804,7 +804,7 @@ class DatabaseAPI20Test(unittest.TestCase):
|
|||
con.close()
|
||||
|
||||
def test_setoutputsize(self):
|
||||
# Real test for setoutputsize is driver dependant
|
||||
# Real test for setoutputsize is driver dependent
|
||||
raise NotImplementedError('Driver needed to override this test')
|
||||
|
||||
def test_None(self):
|
||||
|
|
|
@ -26,6 +26,7 @@ import os
|
|||
import time
|
||||
import threading
|
||||
from operator import attrgetter
|
||||
from StringIO import StringIO
|
||||
|
||||
import psycopg2
|
||||
import psycopg2.errorcodes
|
||||
|
@ -70,16 +71,8 @@ class ConnectionTests(ConnectingTestCase):
|
|||
# ticket #148
|
||||
conn = self.conn
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
cur.execute("select pg_terminate_backend(pg_backend_pid())")
|
||||
except psycopg2.OperationalError, e:
|
||||
if e.pgcode != psycopg2.errorcodes.ADMIN_SHUTDOWN:
|
||||
raise
|
||||
except psycopg2.DatabaseError, e:
|
||||
# curiously when disconnected in green mode we get a DatabaseError
|
||||
# without pgcode.
|
||||
if e.pgcode is not None:
|
||||
raise
|
||||
self.assertRaises(psycopg2.OperationalError,
|
||||
cur.execute, "select pg_terminate_backend(pg_backend_pid())")
|
||||
|
||||
self.assertEqual(conn.closed, 2)
|
||||
conn.close()
|
||||
|
@ -1067,6 +1060,17 @@ class AutocommitTests(ConnectingTestCase):
|
|||
self.assertEqual(cur.fetchone()[0], 'on')
|
||||
|
||||
|
||||
class ReplicationTest(ConnectingTestCase):
|
||||
@skip_before_postgres(9, 0)
|
||||
def test_replication_not_supported(self):
|
||||
conn = self.repl_connect()
|
||||
if conn is None: return
|
||||
cur = conn.cursor()
|
||||
f = StringIO()
|
||||
self.assertRaises(psycopg2.NotSupportedError,
|
||||
cur.copy_expert, "START_REPLICATION 0/0", f)
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||
|
||||
|
|
|
@ -22,13 +22,18 @@
|
|||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
# License for more details.
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
import time
|
||||
import pickle
|
||||
import psycopg2
|
||||
import psycopg2.extensions
|
||||
import psycopg2.extras
|
||||
from psycopg2.extensions import b
|
||||
from testutils import unittest, ConnectingTestCase, skip_before_postgres
|
||||
from testutils import skip_if_no_namedtuple, skip_if_no_getrefcount
|
||||
from testutils import skip_if_no_superuser, skip_if_windows
|
||||
|
||||
|
||||
class CursorTests(ConnectingTestCase):
|
||||
|
||||
|
@ -48,8 +53,10 @@ class CursorTests(ConnectingTestCase):
|
|||
conn = self.conn
|
||||
cur = conn.cursor()
|
||||
cur.execute("create temp table test_exc (data int);")
|
||||
|
||||
def buggygen():
|
||||
yield 1 // 0
|
||||
|
||||
self.assertRaises(ZeroDivisionError,
|
||||
cur.executemany, "insert into test_exc values (%s)", buggygen())
|
||||
cur.close()
|
||||
|
@ -194,16 +201,16 @@ class CursorTests(ConnectingTestCase):
|
|||
|
||||
self._create_withhold_table()
|
||||
curs = self.conn.cursor("W")
|
||||
self.assertEqual(curs.withhold, False);
|
||||
self.assertEqual(curs.withhold, False)
|
||||
curs.withhold = True
|
||||
self.assertEqual(curs.withhold, True);
|
||||
self.assertEqual(curs.withhold, True)
|
||||
curs.execute("select data from withhold order by data")
|
||||
self.conn.commit()
|
||||
self.assertEqual(curs.fetchall(), [(10,), (20,), (30,)])
|
||||
curs.close()
|
||||
|
||||
curs = self.conn.cursor("W", withhold=True)
|
||||
self.assertEqual(curs.withhold, True);
|
||||
self.assertEqual(curs.withhold, True)
|
||||
curs.execute("select data from withhold order by data")
|
||||
self.conn.commit()
|
||||
self.assertEqual(curs.fetchall(), [(10,), (20,), (30,)])
|
||||
|
@ -271,12 +278,12 @@ class CursorTests(ConnectingTestCase):
|
|||
for t in range(2):
|
||||
if not t:
|
||||
curs = self.conn.cursor("S")
|
||||
self.assertEqual(curs.scrollable, None);
|
||||
self.assertEqual(curs.scrollable, None)
|
||||
curs.scrollable = True
|
||||
else:
|
||||
curs = self.conn.cursor("S", scrollable=True)
|
||||
|
||||
self.assertEqual(curs.scrollable, True);
|
||||
self.assertEqual(curs.scrollable, True)
|
||||
curs.itersize = 10
|
||||
|
||||
# complex enough to make postgres cursors declare without
|
||||
|
@ -490,6 +497,56 @@ class CursorTests(ConnectingTestCase):
|
|||
cur = self.conn.cursor()
|
||||
self.assertRaises(TypeError, cur.callproc, 'lower', 42)
|
||||
|
||||
@skip_if_no_superuser
|
||||
@skip_if_windows
|
||||
@skip_before_postgres(8, 4)
|
||||
def test_external_close_sync(self):
|
||||
# If a "victim" connection is closed by a "control" connection
|
||||
# behind psycopg2's back, psycopg2 always handles it correctly:
|
||||
# raise OperationalError, set conn.closed to 2. This reproduces
|
||||
# issue #443, a race between control_conn closing victim_conn and
|
||||
# psycopg2 noticing.
|
||||
control_conn = self.conn
|
||||
connect_func = self.connect
|
||||
wait_func = lambda conn: None
|
||||
self._test_external_close(control_conn, connect_func, wait_func)
|
||||
|
||||
@skip_if_no_superuser
|
||||
@skip_if_windows
|
||||
@skip_before_postgres(8, 4)
|
||||
def test_external_close_async(self):
|
||||
# Issue #443 is in the async code too. Since the fix is duplicated,
|
||||
# so is the test.
|
||||
control_conn = self.conn
|
||||
connect_func = lambda: self.connect(async=True)
|
||||
wait_func = psycopg2.extras.wait_select
|
||||
self._test_external_close(control_conn, connect_func, wait_func)
|
||||
|
||||
def _test_external_close(self, control_conn, connect_func, wait_func):
|
||||
# The short sleep before using victim_conn the second time makes it
|
||||
# much more likely to lose the race and see the bug. Repeating the
|
||||
# test several times makes it even more likely.
|
||||
for i in range(10):
|
||||
victim_conn = connect_func()
|
||||
wait_func(victim_conn)
|
||||
|
||||
with victim_conn.cursor() as cur:
|
||||
cur.execute('select pg_backend_pid()')
|
||||
wait_func(victim_conn)
|
||||
pid1 = cur.fetchall()[0][0]
|
||||
|
||||
with control_conn.cursor() as cur:
|
||||
cur.execute('select pg_terminate_backend(%s)', (pid1,))
|
||||
|
||||
def f():
|
||||
with victim_conn.cursor() as cur:
|
||||
cur.execute('select 1')
|
||||
wait_func(victim_conn)
|
||||
|
||||
time.sleep(0.001)
|
||||
self.assertRaises(psycopg2.OperationalError, f)
|
||||
self.assertEqual(victim_conn.closed, 2)
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||
|
|
65
tests/test_errcodes.py
Executable file
65
tests/test_errcodes.py
Executable file
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# test_errcodes.py - unit test for psycopg2.errcodes module
|
||||
#
|
||||
# Copyright (C) 2015 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
||||
#
|
||||
# psycopg2 is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# In addition, as a special exception, the copyright holders give
|
||||
# permission to link this program with the OpenSSL library (or with
|
||||
# modified versions of OpenSSL that use the same license as OpenSSL),
|
||||
# and distribute linked combinations including the two.
|
||||
#
|
||||
# You must obey the GNU Lesser General Public License in all respects for
|
||||
# all of the code used other than OpenSSL.
|
||||
#
|
||||
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
# License for more details.
|
||||
|
||||
from testutils import unittest, ConnectingTestCase
|
||||
|
||||
try:
|
||||
reload
|
||||
except NameError:
|
||||
from imp import reload
|
||||
|
||||
from threading import Thread
|
||||
from psycopg2 import errorcodes
|
||||
|
||||
class ErrocodeTests(ConnectingTestCase):
|
||||
def test_lookup_threadsafe(self):
|
||||
|
||||
# Increase if it does not fail with KeyError
|
||||
MAX_CYCLES = 2000
|
||||
|
||||
errs = []
|
||||
def f(pg_code='40001'):
|
||||
try:
|
||||
errorcodes.lookup(pg_code)
|
||||
except Exception, e:
|
||||
errs.append(e)
|
||||
|
||||
for __ in xrange(MAX_CYCLES):
|
||||
reload(errorcodes)
|
||||
(t1, t2) = (Thread(target=f), Thread(target=f))
|
||||
(t1.start(), t2.start())
|
||||
(t1.join(), t2.join())
|
||||
|
||||
if errs:
|
||||
self.fail(
|
||||
"raised %s errors in %s cycles (first is %s %s)" % (
|
||||
len(errs), MAX_CYCLES,
|
||||
errs[0].__class__.__name__, errs[0]))
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -23,12 +23,14 @@
|
|||
# License for more details.
|
||||
|
||||
import sys
|
||||
import testutils
|
||||
from testutils import unittest, ConnectingTestCase
|
||||
|
||||
import psycopg2
|
||||
import psycopg2.extensions
|
||||
from psycopg2.extensions import b
|
||||
|
||||
|
||||
class QuotingTestCase(ConnectingTestCase):
|
||||
r"""Checks the correct quoting of strings and binary objects.
|
||||
|
||||
|
@ -156,7 +158,7 @@ class QuotingTestCase(ConnectingTestCase):
|
|||
|
||||
|
||||
class TestQuotedString(ConnectingTestCase):
|
||||
def test_encoding(self):
|
||||
def test_encoding_from_conn(self):
|
||||
q = psycopg2.extensions.QuotedString('hi')
|
||||
self.assertEqual(q.encoding, 'latin1')
|
||||
|
||||
|
@ -164,10 +166,61 @@ class TestQuotedString(ConnectingTestCase):
|
|||
q.prepare(self.conn)
|
||||
self.assertEqual(q.encoding, 'utf_8')
|
||||
|
||||
def test_encoding_default(self):
|
||||
from psycopg2.extensions import adapt
|
||||
a = adapt("hello")
|
||||
self.assertEqual(a.encoding, 'latin1')
|
||||
self.assertEqual(a.getquoted(), b("'hello'"))
|
||||
|
||||
# NOTE: we can't really test an encoding different from utf8, because
|
||||
# when encoding without connection the libpq will use parameters from
|
||||
# a previous one, so what would happens depends jn the tests run order.
|
||||
# egrave = u'\xe8'
|
||||
# self.assertEqual(adapt(egrave).getquoted(), "'\xe8'")
|
||||
|
||||
def test_encoding_error(self):
|
||||
from psycopg2.extensions import adapt
|
||||
snowman = u"\u2603"
|
||||
a = adapt(snowman)
|
||||
self.assertRaises(UnicodeEncodeError, a.getquoted)
|
||||
|
||||
def test_set_encoding(self):
|
||||
# Note: this works-ish mostly in case when the standard db connection
|
||||
# we test with is utf8, otherwise the encoding chosen by PQescapeString
|
||||
# may give bad results.
|
||||
from psycopg2.extensions import adapt
|
||||
snowman = u"\u2603"
|
||||
a = adapt(snowman)
|
||||
a.encoding = 'utf8'
|
||||
self.assertEqual(a.encoding, 'utf8')
|
||||
q = a.getquoted()
|
||||
self.assert_(q in (b("'\xe2\x98\x83'"), b("E'\xe2\x98\x83'")), q)
|
||||
|
||||
def test_connection_wins_anyway(self):
|
||||
from psycopg2.extensions import adapt
|
||||
snowman = u"\u2603"
|
||||
a = adapt(snowman)
|
||||
a.encoding = 'latin9'
|
||||
|
||||
self.conn.set_client_encoding('utf8')
|
||||
a.prepare(self.conn)
|
||||
|
||||
self.assertEqual(a.encoding, 'utf_8')
|
||||
q = a.getquoted()
|
||||
self.assert_(q in (b("'\xe2\x98\x83'"), b("E'\xe2\x98\x83'")), q)
|
||||
|
||||
@testutils.skip_before_python(3)
|
||||
def test_adapt_bytes(self):
|
||||
snowman = u"\u2603"
|
||||
self.conn.set_client_encoding('utf8')
|
||||
a = psycopg2.extensions.QuotedString(snowman.encode('utf8'))
|
||||
a.prepare(self.conn)
|
||||
q = a.getquoted()
|
||||
self.assert_(q in (b("'\xe2\x98\x83'"), b("E'\xe2\x98\x83'")), q)
|
||||
|
||||
|
||||
def test_suite():
|
||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
|
|
@ -192,6 +192,41 @@ class TypesBasicTests(ConnectingTestCase):
|
|||
self.assertRaises(psycopg2.DataError,
|
||||
psycopg2.extensions.STRINGARRAY, b(s), curs)
|
||||
|
||||
@testutils.skip_before_postgres(8, 2)
|
||||
def testArrayOfNulls(self):
|
||||
curs = self.conn.cursor()
|
||||
curs.execute("""
|
||||
create table na (
|
||||
texta text[],
|
||||
inta int[],
|
||||
boola boolean[],
|
||||
|
||||
textaa text[][],
|
||||
intaa int[][],
|
||||
boolaa boolean[][]
|
||||
)""")
|
||||
|
||||
curs.execute("insert into na (texta) values (%s)", ([None],))
|
||||
curs.execute("insert into na (texta) values (%s)", (['a', None],))
|
||||
curs.execute("insert into na (texta) values (%s)", ([None, None],))
|
||||
curs.execute("insert into na (inta) values (%s)", ([None],))
|
||||
curs.execute("insert into na (inta) values (%s)", ([42, None],))
|
||||
curs.execute("insert into na (inta) values (%s)", ([None, None],))
|
||||
curs.execute("insert into na (boola) values (%s)", ([None],))
|
||||
curs.execute("insert into na (boola) values (%s)", ([True, None],))
|
||||
curs.execute("insert into na (boola) values (%s)", ([None, None],))
|
||||
|
||||
# TODO: array of array of nulls are not supported yet
|
||||
# curs.execute("insert into na (textaa) values (%s)", ([[None]],))
|
||||
curs.execute("insert into na (textaa) values (%s)", ([['a', None]],))
|
||||
# curs.execute("insert into na (textaa) values (%s)", ([[None, None]],))
|
||||
# curs.execute("insert into na (intaa) values (%s)", ([[None]],))
|
||||
curs.execute("insert into na (intaa) values (%s)", ([[42, None]],))
|
||||
# curs.execute("insert into na (intaa) values (%s)", ([[None, None]],))
|
||||
# curs.execute("insert into na (boolaa) values (%s)", ([[None]],))
|
||||
curs.execute("insert into na (boolaa) values (%s)", ([[True, None]],))
|
||||
# curs.execute("insert into na (boolaa) values (%s)", ([[None, None]],))
|
||||
|
||||
@testutils.skip_from_python(3)
|
||||
def testTypeRoundtripBuffer(self):
|
||||
o1 = buffer("".join(map(chr, range(256))))
|
||||
|
@ -345,7 +380,6 @@ class AdaptSubclassTest(unittest.TestCase):
|
|||
finally:
|
||||
del psycopg2.extensions.adapters[A, psycopg2.extensions.ISQLQuote]
|
||||
|
||||
|
||||
@testutils.skip_before_python(3)
|
||||
def test_adapt_subtype_3(self):
|
||||
from psycopg2.extensions import adapt, register_adapter, AsIs
|
||||
|
@ -445,6 +479,7 @@ class ByteaParserTest(unittest.TestCase):
|
|||
|
||||
self.assertEqual(rv, tgt)
|
||||
|
||||
|
||||
def skip_if_cant_cast(f):
|
||||
@wraps(f)
|
||||
def skip_if_cant_cast_(self, *args, **kwargs):
|
||||
|
@ -464,4 +499,3 @@ def test_suite():
|
|||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ dbhost = os.environ.get('PSYCOPG2_TESTDB_HOST', None)
|
|||
dbport = os.environ.get('PSYCOPG2_TESTDB_PORT', None)
|
||||
dbuser = os.environ.get('PSYCOPG2_TESTDB_USER', None)
|
||||
dbpass = os.environ.get('PSYCOPG2_TESTDB_PASSWORD', None)
|
||||
repl_dsn = os.environ.get('PSYCOPG2_TEST_REPL_DSN',
|
||||
"dbname=psycopg2_test replication=1")
|
||||
|
||||
# Check if we want to test psycopg's green path.
|
||||
green = os.environ.get('PSYCOPG2_TEST_GREEN', None)
|
||||
|
@ -32,5 +34,3 @@ if dbuser is not None:
|
|||
dsn += ' user=%s' % dbuser
|
||||
if dbpass is not None:
|
||||
dsn += ' password=%s' % dbpass
|
||||
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ import os
|
|||
import platform
|
||||
import sys
|
||||
from functools import wraps
|
||||
from testconfig import dsn
|
||||
from testconfig import dsn, repl_dsn
|
||||
|
||||
try:
|
||||
import unittest2
|
||||
|
@ -65,7 +65,8 @@ else:
|
|||
|
||||
unittest.TestCase.skipTest = skipTest
|
||||
|
||||
# Silence warnings caused by the stubborness of the Python unittest maintainers
|
||||
# Silence warnings caused by the stubbornness of the Python unittest
|
||||
# maintainers
|
||||
# http://bugs.python.org/issue9424
|
||||
if not hasattr(unittest.TestCase, 'assert_') \
|
||||
or unittest.TestCase.assert_ is not unittest.TestCase.assertTrue:
|
||||
|
@ -99,14 +100,38 @@ class ConnectingTestCase(unittest.TestCase):
|
|||
self._conns
|
||||
except AttributeError, e:
|
||||
raise AttributeError(
|
||||
"%s (did you remember calling ConnectingTestCase.setUp()?)"
|
||||
"%s (did you forget to call ConnectingTestCase.setUp()?)"
|
||||
% e)
|
||||
|
||||
if 'dsn' in kwargs:
|
||||
conninfo = kwargs.pop('dsn')
|
||||
else:
|
||||
conninfo = dsn
|
||||
import psycopg2
|
||||
conn = psycopg2.connect(dsn, **kwargs)
|
||||
conn = psycopg2.connect(conninfo, **kwargs)
|
||||
self._conns.append(conn)
|
||||
return conn
|
||||
|
||||
def repl_connect(self, **kwargs):
|
||||
"""Return a connection set up for replication
|
||||
|
||||
The connection is on "PSYCOPG2_TEST_REPL_DSN" unless overridden by
|
||||
a *dsn* kwarg.
|
||||
|
||||
Should raise a skip test if not available, but guard for None on
|
||||
old Python versions.
|
||||
"""
|
||||
if 'dsn' not in kwargs:
|
||||
kwargs['dsn'] = repl_dsn
|
||||
import psycopg2
|
||||
try:
|
||||
conn = self.connect(**kwargs)
|
||||
except psycopg2.OperationalError, e:
|
||||
return self.skipTest("replication db not configured: %s" % e)
|
||||
|
||||
conn.autocommit = True
|
||||
return conn
|
||||
|
||||
def _get_conn(self):
|
||||
if not hasattr(self, '_the_conn'):
|
||||
self._the_conn = self.connect()
|
||||
|
@ -350,4 +375,3 @@ class py3_raises_typeerror(object):
|
|||
if sys.version_info[0] >= 3:
|
||||
assert type is TypeError
|
||||
return True
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user