Merge master into named parameters branch after 1.5 years.

This commit is contained in:
mrmilosz 2015-11-30 01:02:38 -05:00
commit bc0b1e0195
86 changed files with 2130 additions and 950 deletions

2
.gitignore vendored
View File

@ -9,5 +9,7 @@ build/*
doc/src/_build/*
doc/html/*
doc/psycopg2.txt
scripts/pypi_docs_upload.py
env
.tox
/rel

105
INSTALL
View File

@ -1,103 +1,4 @@
Compiling and installing psycopg
********************************
** Important note: if you plan to use psycopg2 in a multithreaded application,
make sure that your libpq has been compiled with the --with-thread-safety
option. psycopg2 will work correctly even with a non-thread-safe libpq but
libpq will leak memory.
psycopg2 uses distutils for its build process, so most of the process is
executed by the setup.py script. Before building psycopg look at
setup.cfg file and change any settings to follow your system (or taste);
then:
python setup.py build
to build in the local directory; and:
python setup.py install
to install system-wide.
Common errors and build problems
================================
One of the most common errors is trying to build psycopg without the right
development headers for PostgreSQL, Python or both. If you get errors, look
for the following messages and then take the appropriate action:
libpq-fe.h: No such file or directory
PostgreSQL headers are not properly installed on your system or are
installed in a non default path. First make sure they are installed, then
check setup.cfg and make sure pg_config points to a valid pg_config
executable. If you don't have a working pg_config try to play with the
include_dirs variable (and note that a working pg_config is better.)
Running the test suite
======================
The included Makefile allows to run all the tests included in the
distribution. Just use:
make
make check
The tests are run against a database called psycopg2_test on unix socket
and standard port. You can configure a different database to run the test
by setting the environment variables:
- PSYCOPG2_TESTDB
- PSYCOPG2_TESTDB_HOST
- PSYCOPG2_TESTDB_PORT
- PSYCOPG2_TESTDB_USER
The database should be created before running the tests.
The standard Python unittest is used to run the tests. But if unittest2 is
found it will be used instead, with the result of having more informations
about skipped tests.
Building the documentation
==========================
In order to build the documentation included in the distribution, use
make env
make docs
The first command will install all the dependencies (Sphinx, Docutils) in
an 'env' directory in the project tree. The second command will build both
the html format (in the 'doc/html' directory) and in plain text
(doc/psycopg2.txt)
Using setuptools and EasyInstall
================================
If setuptools are installed on your system you can easily create an egg for
psycopg and install it. Download the source distribution (if you're reading
this file you probably already have) and then edit setup.cfg to your taste
and build from the source distribution top-level directory using:
easy_install .
Compiling under Windows with mingw32
====================================
You can compile psycopg under Windows platform with mingw32
(http://www.mingw.org/) compiler. MinGW is also shipped with IDEs such as
Dev-C++ (http://www.bloodshed.net/devcpp.html) and Code::Blocks
(http://www.codeblocks.org). gcc binaries should be in your PATH.
You need a PostgreSQL with include and library files installed. At least v8.0
is required.
First you need to create a libpython2X.a as described in
http://starship.python.net/crew/kernr/mingw32/Notes.html. Then run:
python setup.py build_ext --compiler=mingw32 install
Installation instructions are included in the docs.
Please check the 'doc/src/install.rst' file or online at
<http://initd.org/psycopg/docs/install.html>.

View File

@ -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 HACKING SUCCESS COPYING.LESSER pep-0249.txt
recursive-include doc README SUCCESS COPYING.LESSER pep-0249.txt
recursive-include doc Makefile 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 INSTALL LICENSE NEWS
include AUTHORS README.rst INSTALL LICENSE NEWS
include PKG-INFO MANIFEST.in MANIFEST setup.py setup.cfg Makefile

View File

@ -6,7 +6,7 @@
#
# Build the documentation::
#
# make env
# make env (once)
# make docs
#
# Create a source package::
@ -20,9 +20,6 @@
PYTHON := python$(PYTHON_VERSION)
PYTHON_VERSION ?= $(shell $(PYTHON) -c 'import sys; print ("%d.%d" % sys.version_info[:2])')
BUILD_DIR = $(shell pwd)/build/lib.$(PYTHON_VERSION)
ENV_DIR = $(shell pwd)/env/py-$(PYTHON_VERSION)
ENV_BIN = $(ENV_DIR)/bin
ENV_LIB = $(ENV_DIR)/lib
SOURCE_C := $(wildcard psycopg/*.c psycopg/*.h)
SOURCE_PY := $(wildcard lib/*.py)
@ -46,9 +43,6 @@ endif
VERSION := $(shell grep PSYCOPG_VERSION setup.py | head -1 | sed -e "s/.*'\(.*\)'/\1/")
SDIST := dist/psycopg2-$(VERSION).tar.gz
EASY_INSTALL = PYTHONPATH=$(ENV_LIB) $(ENV_BIN)/easy_install-$(PYTHON_VERSION) -d $(ENV_LIB) -s $(ENV_BIN)
EZ_SETUP = $(ENV_BIN)/ez_setup.py
.PHONY: env check clean
default: package
@ -68,22 +62,8 @@ docs-zip: doc/docs.zip
sdist: $(SDIST)
# The environment is currently required to build the documentation.
# It is not clean by 'make clean'
env: easy_install
mkdir -p $(ENV_BIN)
mkdir -p $(ENV_LIB)
$(EASY_INSTALL) docutils
$(EASY_INSTALL) sphinx
easy_install: ez_setup
PYTHONPATH=$(ENV_LIB) $(PYTHON) $(EZ_SETUP) -d $(ENV_LIB) -s $(ENV_BIN) setuptools
ez_setup:
mkdir -p $(ENV_BIN)
mkdir -p $(ENV_LIB)
wget -O $(EZ_SETUP) http://peak.telecommunity.com/dist/ez_setup.py
env:
$(MAKE) -C doc $@
check:
PYTHONPATH=$(BUILD_DIR):$(PYTHONPATH) $(PYTHON) -c "from psycopg2 import tests; tests.unittest.main(defaultTest='tests.test_suite')" --verbose
@ -122,10 +102,10 @@ MANIFEST: MANIFEST.in $(SOURCE)
# docs depend on the build as it partly use introspection.
doc/html/genindex.html: $(PLATLIB) $(PURELIB) $(SOURCE_DOC)
PYTHONPATH=$(ENV_LIB):$(BUILD_DIR) $(MAKE) SPHINXBUILD=$(ENV_BIN)/sphinx-build -C doc html
$(MAKE) -C doc html
doc/psycopg2.txt: $(PLATLIB) $(PURELIB) $(SOURCE_DOC)
PYTHONPATH=$(ENV_LIB):$(BUILD_DIR) $(MAKE) SPHINXBUILD=$(ENV_BIN)/sphinx-build -C doc text
$(MAKE) -C doc text
doc/docs.zip: doc/html/genindex.html
(cd doc/html && zip -r ../docs.zip *)

78
NEWS
View File

@ -1,15 +1,87 @@
Current release
---------------
What's new in psycopg 2.7
-------------------------
New features:
- Added `~psycopg2.extensions.parse_dsn()` function (:ticket:`#321`).
- Added `~psycopg2.__libpq_version__` and
`~psycopg2.extensions.libpq_version()` to inspect the version of the
``libpq`` library the module was compiled/loaded with
(:tickets:`#35, #323`).
- The attributes `~connection.notices` and `~connection.notifies` can be
customized replacing them with any object exposing an `!append()` method
(:ticket:`#326`).
- Added `~psycopg2.extensions.quote_ident()` function (:ticket:`#359`).
What's new in psycopg 2.6.2
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Report the server response status on errors (such as :ticket:`#281`).
- The `~psycopg2.extras.wait_select` callback allows interrupting a
long-running query in an interactive shell using :kbd:`Ctrl-C`
(:ticket:`#333`).
- Raise `!NotSupportedError` on unhandled server response status
(:ticket:`#352`).
- Fixed `!PersistentConnectionPool` on Python 3 (:ticket:`#348`).
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
-------------------------
New features:
- Added support for large objects larger than 2GB. Many thanks to Blake Rouse
and the MAAS Team for the feature development.
- Python `time` objects with a tzinfo specified and PostgreSQL :sql:`timetz`
data are converted into each other (:ticket:`#272`).
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`).
What's new in psycopg 2.5.5
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Named cursors used as context manager don't swallow the exception on exit
(:ticket:`#262`).
- `cursor.description` can be pickled (:ticket:`#265`).
- Propagate read error messages in COPY FROM (:ticket:`#270`).
- PostgreSQL time 24:00 is converted to Python 00:00 (:ticket:`#278`).
What's new in psycopg 2.5.4
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Added :sql:`jsonb` support for PostgreSQL 9.4 (:ticket:`#226`).
- Fixed segfault if COPY statements are passed to `~cursor.execute()` instead
of using the proper methods (:ticket:`#219`).
- Force conversion of pool arguments to integer to avoid potentially unbounded
pools (:ticket:`#220`).
- Cursors :sql:`WITH HOLD` don't begin a new transaction upon move/fetch/close
(:ticket:`#228`).
- Cursors :sql:`WITH HOLD` can be used in autocommit (:ticket:`#229`).
- `~cursor.callproc()` doesn't silently ignore an argument without a length.
- Fixed memory leak with large objects (:ticket:`#256`).
- Make sure the internal ``_psycopg.so`` module can be imported stand-alone (to
allow modules juggling such as the one described in :ticket:`#201`).
What's new in psycopg 2.5.3
^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -115,7 +187,7 @@ What's new in psycopg 2.4.6
- 'register_hstore()', 'register_composite()', 'tpc_recover()' work with
RealDictConnection and Cursor (:ticket:`#114`).
- Fixed broken pool for Zope and connections re-init across ZSQL methods
in the same request (tickets #123, #125, #142).
in the same request (:tickets:`#123, #125, #142`).
- connect() raises an exception instead of swallowing keyword arguments
when a connection string is specified as well (:ticket:`#131`).
- Discard any result produced by 'executemany()' (:ticket:`#133`).
@ -137,7 +209,7 @@ What's new in psycopg 2.4.5
- Error and its subclasses are picklable, useful for multiprocessing
interaction (:ticket:`#90`).
- Better efficiency and formatting of timezone offset objects thanks
to Menno Smits (tickets #94, #95).
to Menno Smits (:tickets:`#94, #95`).
- Fixed 'rownumber' during iteration on cursor subclasses.
Regression introduced in 2.4.4 (:ticket:`#100`).
- Added support for 'inet' arrays.

38
README
View File

@ -1,38 +0,0 @@
psycopg2 - Python-PostgreSQL Database Adapter
********************************************
psycopg2 is a PostgreSQL database adapter for the Python programming
language. psycopg2 was written with the aim of being very small and fast,
and stable as a rock.
psycopg2 is different from the other database adapter because it was
designed for heavily multi-threaded applications that create and destroy
lots of cursors and make a conspicuous number of concurrent INSERTs or
UPDATEs. psycopg2 also provides full asynchronous operations and support
for coroutine libraries.
psycopg2 can compile and run on Linux, FreeBSD, Solaris, MacOS X and
Windows architecture. It supports Python versions from 2.4 onwards and
PostgreSQL versions from 7.4 onwards.
psycopg2 is free software ("free as in freedom" but I like beer too.)
It is licensed under the GNU Lesser General Public License, version 3 or
later plus an exception to allow OpenSSL (libpq) linking; see LICENSE for
more details.
Documentation
-------------
Start by reading the INSTALL file. More information about psycopg2 extensions
to the DBAPI-2.0 is available in the files located in the doc/ direcory.
Example code can be found in the examples/ directory. If you make any changes
to the code make sure to run the unit tests localed in tests/.
Online documentation can be found at: http://initd.org/psycopg/
If you stumble upon any bugs, please tell us at: http://psycopg.lighthouseapp.com/
Contributors
------------
For a list of contributors to the project, see the AUTHORS file.

46
README.rst Normal file
View File

@ -0,0 +1,46 @@
psycopg2 - Python-PostgreSQL Database Adapter
=============================================
Psycopg is the most popular PostgreSQL database adapter for the Python
programming language. Its main features are the complete implementation of
the Python DB API 2.0 specification and the thread safety (several threads can
share the same connection). It was designed for heavily multi-threaded
applications that create and destroy lots of cursors and make a large number
of concurrent "INSERT"s or "UPDATE"s.
Psycopg 2 is mostly implemented in C as a libpq wrapper, resulting in being
both efficient and secure. It features client-side and server-side cursors,
asynchronous communication and notifications, "COPY TO/COPY FROM" support.
Many Python types are supported out-of-the-box and adapted to matching
PostgreSQL data types; adaptation can be extended and customized thanks to a
flexible objects adaptation system.
Psycopg 2 is both Unicode and Python 3 friendly.
Documentation
-------------
Documentation is included in the 'doc' directory and is `available online`__.
.. __: http://initd.org/psycopg/docs/
Installation
------------
If all the dependencies are met (i.e. you have the Python and libpq
development packages installed in your system) the standard::
python setup.py build
sudo python setup.py install
should work no problem. In case you have any problem check the 'install' and
the 'faq' documents in the docs or online__.
.. __: http://initd.org/psycopg/docs/install.html
For any other resource (source code repository, bug tracker, mailing list)
please check the `project homepage`__.
.. __: http://initd.org/psycopg/

View File

@ -1,43 +0,0 @@
General information
*******************
Some help to people wanting to hack on psycopg. First of all, note that
*every* function in the psycopg module source code is prefixed by one of the
following words:
psyco is used for function directly callable from python (i.e., functions
in the psycopg module itself.) the only notable exception is the
source code for the module itself, that uses "psyco" even for C-only
functions.
conn is used for functions related to connection objects.
curs is used for functions related to cursor objects.
typecast is used for typecasters and utility function related to
typecaster creation and registration.
Pythonic definition of types and functions available from python are defined
in *_type.c files. Internal functions, callable only from C are located in
*_int.c files and extensions to the DBAPI can be found in the *_ext.c files.
Patches
*******
If you submit a patch, please send a diff generated with the "-u" switch.
Also note that I don't like that much cosmetic changes (like renaming
already existing variables) and I will rewrap the patch to 78 columns
anyway, so it is much better if you do that beforehand.
The type system
***************
Simple types, like integers and strings, are converted to python base types
(the conversion functions are in typecast_base.c). Complex types are
converted to ad-hoc types, defined in the typeobj_*.{c,h} files. The
conversion function are in the other typecast_*.c files. typecast.c defines
the basic utility functions (available through the psycopg module) used when
defining new typecasters from C and python.

View File

@ -1,23 +1,41 @@
.PHONY: help clean html text doctest
.PHONY: env help clean html text doctest
docs: html text
check: doctest
help:
cd src && $(MAKE) $@
# The environment is currently required to build the documentation.
# It is not clean by 'make clean'
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:
cd src && $(MAKE) $@
$(MAKE) PYTHON=$(PYTHON) -C .. package
$(MAKE) $(SPHOPTS) -C src $@
cp -r src/_build/html .
text:
cd src && $(MAKE) $@
$(MAKE) PYTHON=$(PYTHON) -C .. package
$(MAKE) $(SPHOPTS) -C src $@
cd src && tools/stitch_text.py index.rst _build/text > ../psycopg2.txt
doctest:
cd src && $(MAKE) $@
$(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:
cd src && $(MAKE) $@
$(MAKE) $(SPHOPTS) -C src $@
rm -rf html psycopg2.txt
env: requirements.txt
virtualenv env
./env/bin/pip install -r requirements.txt

View File

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

3
doc/requirements.txt Normal file
View File

@ -0,0 +1,3 @@
# Packages only needed to build the docs
Pygments>=1.5
Sphinx>=1.2,<=1.3

View File

@ -145,7 +145,9 @@ geometric type:
... self.y = y
>>> def adapt_point(point):
... return AsIs("'(%s, %s)'" % (adapt(point.x), adapt(point.y)))
... x = adapt(point.x).getquoted()
... y = adapt(point.y).getquoted()
... return AsIs("'(%s, %s)'" % (x, y))
>>> register_adapter(Point, adapt_point)
@ -289,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'`
@ -310,6 +312,10 @@ received from a previous version server will have the
Added `~psycopg2.extensions.Notify` object and handling notification
payload.
.. versionchanged:: 2.7
The `~connection.notifies` attribute is writable: it is possible to
replace it with any object exposing an `!append()` method. An useful
example would be to use a `~collections.deque` object.
.. index::

View File

@ -42,7 +42,9 @@ master_doc = 'index'
# General information about the project.
project = u'Psycopg'
copyright = u'2001-2013, Federico Di Gregorio. Documentation by Daniele Varrazzo'
from datetime import date
year = date.today().year
copyright = u'2001-%s, Federico Di Gregorio, Daniele Varrazzo' % year
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@ -66,7 +68,9 @@ intersphinx_mapping = {
}
# Pattern to generate links to the bug tracker
ticket_url = 'http://psycopg.lighthouseapp.com/projects/62710/tickets/%s'
ticket_url = 'https://github.com/psycopg/psycopg2/issues/%s'
ticket_remap_until = 25
ticket_remap_offset = 230
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@ -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.
@ -483,13 +483,21 @@ The ``connection`` class
['NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo"\n',
'NOTICE: CREATE TABLE will create implicit sequence "foo_id_seq" for serial column "foo.id"\n']
.. versionchanged:: 2.7
The `!notices` attribute is writable: the user may replace it
with any Python object exposing an `!append()` method. If
appending raises an exception the notice is silently
dropped.
To avoid a leak in case excessive notices are generated, only the last
50 messages are kept.
50 messages are kept. This check is only in place if the `!notices`
attribute is a list: if any other object is used it will be up to the
user to guard from leakage.
You can configure what messages to receive using `PostgreSQL logging
configuration parameters`__ such as ``log_statement``,
``client_min_messages``, ``log_min_duration_statement`` etc.
.. __: http://www.postgresql.org/docs/current/static/runtime-config-logging.html
@ -506,6 +514,12 @@ The ``connection`` class
the payload was not accessible. To keep backward compatibility,
`!Notify` objects can still be accessed as 2 items tuples.
.. versionchanged:: 2.7
The `!notifies` attribute is writable: the user may replace it
with any Python object exposing an `!append()` method. If
appending raises an exception the notification is silently
dropped.
.. attribute:: cursor_factory

View File

@ -529,6 +529,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.
@ -558,6 +565,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.

View File

@ -49,8 +49,8 @@ An example of the available constants defined in the module:
>>> errorcodes.UNDEFINED_TABLE
'42P01'
Constants representing all the error values documented by PostgreSQL versions
between 8.1 and 9.2 are included in the module.
Constants representing all the error values defined by PostgreSQL versions
between 8.1 and 9.4 are included in the module.
.. autofunction:: lookup(code)

View File

@ -12,6 +12,17 @@
The module contains a few objects and function extending the minimum set of
functionalities defined by the |DBAPI|_.
.. function:: parse_dsn(dsn)
Parse connection string into a dictionary of keywords and values.
Uses libpq's ``PQconninfoParse`` to parse the string according to
accepted format(s) and check for supported keywords.
Example::
>>> psycopg2.extensions.parse_dsn('dbname=test user=postgres password=secret')
{'password': 'secret', 'user': 'postgres', 'dbname': 'test'}
.. class:: connection(dsn, async=False)
@ -40,18 +51,20 @@ functionalities defined by the |DBAPI|_.
The class can be subclassed: see the `connection.lobject()` to know
how to specify a `!lobject` subclass.
.. versionadded:: 2.0.8
.. attribute:: oid
Database OID of the object.
.. attribute:: mode
The mode the database was open. See `connection.lobject()` for a
description of the available modes.
.. method:: read(bytes=-1)
Read a chunk of data from the current file position. If -1 (default)
@ -64,6 +77,7 @@ functionalities defined by the |DBAPI|_.
.. versionchanged:: 2.4
added Unicode support.
.. method:: write(str)
Write a string to the large object. Return the number of bytes
@ -73,42 +87,60 @@ functionalities defined by the |DBAPI|_.
.. versionchanged:: 2.4
added Unicode support.
.. method:: export(file_name)
Export the large object content to the file system.
The method uses the efficient |lo_export|_ libpq function.
.. |lo_export| replace:: `!lo_export()`
.. _lo_export: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-EXPORT
.. method:: seek(offset, whence=0)
Set the lobject current position.
.. versionchanged:: 2.6.0
added support for *offset* > 2GB.
.. method:: tell()
Return the lobject current position.
.. method:: truncate(len=0)
.. versionadded:: 2.2.0
.. versionchanged:: 2.6.0
added support for return value > 2GB.
.. method:: truncate(len=0)
Truncate the lobject to the given size.
The method will only be available if Psycopg has been built against libpq
from PostgreSQL 8.3 or later and can only be used with PostgreSQL servers
running these versions. It uses the |lo_truncate|_ libpq function.
The method will only be available if Psycopg has been built against
libpq from PostgreSQL 8.3 or later and can only be used with
PostgreSQL servers running these versions. It uses the |lo_truncate|_
libpq function.
.. |lo_truncate| replace:: `!lo_truncate()`
.. _lo_truncate: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-TRUNCATE
.. warning::
.. versionadded:: 2.2.0
.. versionchanged:: 2.6.0
added support for *len* > 2GB.
.. warning::
If Psycopg is built with |lo_truncate| support or with the 64 bits API
support (resp. from PostgreSQL versions 8.3 and 9.3) but at runtime an
older version of the dynamic library is found, the ``psycopg2`` module
will fail to import. See :ref:`the lo_truncate FAQ <faq-lo_truncate>`
about the problem.
If Psycopg is built with |lo_truncate| support (i.e. if the
:program:`pg_config` used during setup is version >= 8.3), but at
runtime an older libpq is found, Psycopg will fail to import. See
:ref:`the lo_truncate FAQ <faq-lo_truncate>` about the problem.
.. method:: close()
@ -176,6 +208,31 @@ functionalities defined by the |DBAPI|_.
.. versionadded:: 2.2.0
.. function:: libpq_version()
Return the version number of the ``libpq`` dynamic library loaded as an
integer, in the same format of `~connection.server_version`.
Raise `~psycopg2.NotSupportedError` if the ``psycopg2`` module was
compiled with a ``libpq`` version lesser than 9.1 (which can be detected
by the `~psycopg2.__libpq_version__` constant).
.. seealso:: libpq docs for `PQlibVersion()`__.
.. __: http://www.postgresql.org/docs/current/static/libpq-misc.html#LIBPQ-PQLIBVERSION
.. function:: quote_ident(str, scope)
Return quoted identifier according to PostgreSQL quoting rules.
The *scope* must be a `connection` or a `cursor`, the underlying
connection encoding is used for any necessary character conversion.
Requires libpq >= 9.0.
.. seealso:: libpq docs for `PQescapeIdentifier()`__
.. __: http://www.postgresql.org/docs/current/static/libpq-exec.html#LIBPQ-PQESCAPEIDENTIFIER
.. _sql-adaptation-objects:
@ -189,7 +246,7 @@ deal with Python objects adaptation:
.. function:: adapt(obj)
Return the SQL representation of *obj* as a string. Raise a
Return the SQL representation of *obj* as an `ISQLQuote`. Raise a
`~psycopg2.ProgrammingError` if how to adapt the object is unknown.
In order to allow new objects to be adapted, register a new adapter for it
using the `register_adapter()` function.
@ -203,7 +260,7 @@ deal with Python objects adaptation:
Register a new adapter for the objects of class *class*.
*adapter* should be a function taking a single argument (the object
to adapt) and returning an object conforming the `ISQLQuote`
to adapt) and returning an object conforming to the `ISQLQuote`
protocol (e.g. exposing a `!getquoted()` method). The `AsIs` is
often useful for this task.

View File

@ -160,23 +160,27 @@ JSON_ adaptation
^^^^^^^^^^^^^^^^
.. versionadded:: 2.5
.. versionchanged:: 2.5.4
added |jsonb| support. In previous versions |jsonb| values are returned
as strings. See :ref:`the FAQ <faq-jsonb-adapt>` for a workaround.
Psycopg can adapt Python objects to and from the PostgreSQL |pgjson|_ type.
With PostgreSQL 9.2 adaptation is available out-of-the-box. To use JSON data
with previous database versions (either with the `9.1 json extension`__, but
even if you want to convert text fields to JSON) you can use
`register_json()`.
Psycopg can adapt Python objects to and from the PostgreSQL |pgjson|_ and
|jsonb| types. With PostgreSQL 9.2 and following versions adaptation is
available out-of-the-box. To use JSON data with previous database versions
(either with the `9.1 json extension`__, but even if you want to convert text
fields to JSON) you can use the `register_json()` function.
.. __: http://people.planetpostgresql.org/andrew/index.php?/archives/255-JSON-for-PG-9.2-...-and-now-for-9.1!.html
The Python library used to convert Python objects to JSON depends on the
language version: with Python 2.6 and following the :py:mod:`json` module from
the standard library is used; with previous versions the `simplejson`_ module
is used if available. Note that the last `!simplejson` version supporting
Python 2.4 is the 2.0.9.
The Python library used by default to convert Python objects to JSON and to
parse data from the database depends on the language version: with Python 2.6
and following the :py:mod:`json` module from the standard library is used;
with previous versions the `simplejson`_ module is used if available. Note
that the last `!simplejson` version supporting Python 2.4 is the 2.0.9.
.. _JSON: http://www.json.org/
.. |pgjson| replace:: :sql:`json`
.. |jsonb| replace:: :sql:`jsonb`
.. _pgjson: http://www.postgresql.org/docs/current/static/datatype-json.html
.. _simplejson: http://pypi.python.org/pypi/simplejson/
@ -186,8 +190,8 @@ the `Json` adapter::
curs.execute("insert into mytable (jsondata) values (%s)",
[Json({'a': 100})])
Reading from the database, |pgjson| values will be automatically converted to
Python objects.
Reading from the database, |pgjson| and |jsonb| values will be automatically
converted to Python objects.
.. note::
@ -233,9 +237,11 @@ or you can subclass it overriding the `~Json.dumps()` method::
[MyJson({'a': 100})])
Customizing the conversion from PostgreSQL to Python can be done passing a
custom `!loads()` function to `register_json()` (or `register_default_json()`
for PostgreSQL 9.2). For example, if you want to convert the float values
from :sql:`json` into :py:class:`~decimal.Decimal` you can use::
custom `!loads()` function to `register_json()`. For the builtin data types
(|pgjson| from PostgreSQL 9.2, |jsonb| from PostgreSQL 9.4) use
`register_default_json()` and `register_default_jsonb()`. For example, if you
want to convert the float values from :sql:`json` into
:py:class:`~decimal.Decimal` you can use::
loads = lambda x: json.loads(x, parse_float=Decimal)
psycopg2.extras.register_json(conn, loads=loads)
@ -248,8 +254,15 @@ from :sql:`json` into :py:class:`~decimal.Decimal` you can use::
.. autofunction:: register_json
.. versionchanged:: 2.5.4
added the *name* parameter to enable :sql:`jsonb` support.
.. autofunction:: register_default_json
.. autofunction:: register_default_jsonb
.. versionadded:: 2.5.4
.. index::
@ -598,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.

View File

@ -137,6 +137,20 @@ Psycopg automatically converts PostgreSQL :sql:`json` data into Python objects.
See :ref:`adapt-json` for further details.
.. _faq-jsonb-adapt:
.. cssclass:: faq
Psycopg converts :sql:`json` values into Python objects but :sql:`jsonb` values are returned as strings. Can :sql:`jsonb` be converted automatically?
Automatic conversion of :sql:`jsonb` values is supported from Psycopg
release 2.5.4. For previous versions you can register the :sql:`json`
typecaster on the :sql:`jsonb` oids (which are known and not suppsed to
change in future PostgreSQL versions)::
psycopg2.extras.register_json(oid=3802, array_oid=3807, globally=True)
See :ref:`adapt-json` for further details.
.. _faq-bytea-9.0:
.. cssclass:: faq
@ -209,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
@ -234,13 +279,20 @@ I can't compile `!psycopg2`: the compiler says *error: libpq-fe.h: No such file
.. cssclass:: faq
`!psycopg2` raises `!ImportError` with message *_psycopg.so: undefined symbol: lo_truncate* when imported.
This means that Psycopg has been compiled with |lo_truncate|_ support,
which means that the libpq used at compile time was version >= 8.3, but at
runtime an older libpq library is found. You can use::
This means that Psycopg was compiled with |lo_truncate|_ support (*i.e.*
the libpq used at compile time was version >= 8.3) but at runtime an older
libpq dynamic library is found.
Fast-forward several years, if the message reports *undefined symbol:
lo_truncate64* it means that Psycopg was built with large objects 64 bits
API support (*i.e.* the libpq used at compile time was at least 9.3) but
at runtime an older libpq dynamic library is found.
You can use::
$ ldd /path/to/packages/psycopg2/_psycopg.so | grep libpq
to find what is the version used at runtime.
to find what is the libpq dynamic library used at runtime.
You can avoid the problem by using the same version of the
:program:`pg_config` at install time and the libpq at runtime.

View File

@ -43,9 +43,9 @@ Psycopg 2 is both Unicode and Python 3 friendly.
cursor
advanced
extensions
extras
tz
pool
extras
errorcodes
faq
news

View File

@ -14,9 +14,12 @@ mature as the C implementation yet.
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.3
- PostgreSQL versions from 7.4 to 9.2
- Python 3 versions from 3.1 to 3.4
- PostgreSQL versions from 7.4 to 9.4
.. _PostgreSQL: http://www.postgresql.org/
.. _Python: http://www.python.org/
@ -202,6 +205,33 @@ supported.
.. index::
single: tests
.. _test-suite:
Running the test suite
^^^^^^^^^^^^^^^^^^^^^^
The included ``Makefile`` allows to run all the tests included in the
distribution. Just run::
make
make check
The tests run against a database called ``psycopg2_test`` on UNIX socket and
the standard port. You can configure a different database to run the test by
setting the environment variables:
- :envvar:`PSYCOPG2_TESTDB`
- :envvar:`PSYCOPG2_TESTDB_HOST`
- :envvar:`PSYCOPG2_TESTDB_PORT`
- :envvar:`PSYCOPG2_TESTDB_USER`
The database should already exist before running the tests.
.. index::
single: debug
single: PSYCOPG_DEBUG
@ -222,13 +252,13 @@ order to create a debug package:
- :ref:`Compile and install <source-package>` the package.
- Set the :envvar:`PSYCOPG_DEBUG` variable::
- Set the :envvar:`PSYCOPG_DEBUG` environment variable::
$ export PSYCOPG_DEBUG=1
- Run your program (making sure that the `!psycopg2` package imported is the
one you just compiled and not e.g. the system one): you will have a copious
stream of informations printed on stdout.
stream of informations printed on stderr.
.. __: http://initd.org/psycopg/download/

View File

@ -78,6 +78,7 @@ The module interface respects the standard defined in the |DBAPI|_.
.. seealso::
- `~psycopg2.extensions.parse_dsn`
- libpq `connection string syntax`__
- libpq supported `connection parameters`__
- libpq supported `environment variables`__
@ -91,7 +92,6 @@ The module interface respects the standard defined in the |DBAPI|_.
The parameters *connection_factory* and *async* are Psycopg extensions
to the |DBAPI|.
.. data:: apilevel
String constant stating the supported DB API level. For `psycopg2` is
@ -109,9 +109,16 @@ The module interface respects the standard defined in the |DBAPI|_.
by the interface. For `psycopg2` is ``pyformat``. See also
:ref:`query-parameters`.
.. data:: __libpq_version__
Integer constant reporting the version of the ``libpq`` library this
``psycopg2`` module was compiled with (in the same format of
`~connection.server_version`). If this value is greater or equal than
``90100`` then you may query the version of the actually loaded library
using the `~psycopg2.extensions.libpq_version()` function.
.. index::
.. index::
single: Exceptions; DB API
.. _dbapi-exceptions:
@ -122,12 +129,12 @@ Exceptions
In compliance with the |DBAPI|_, the module makes informations about errors
available through the following exceptions:
.. exception:: Warning
.. exception:: Warning
Exception raised for important warnings like data truncations while
inserting, etc. It is a subclass of the Python `~exceptions.StandardError`.
.. exception:: Error
.. exception:: Error
Exception that is the base class of all other error exceptions. You can
use this to catch all errors with one single `!except` statement. Warnings
@ -150,7 +157,7 @@ available through the following exceptions:
>>> try:
... cur.execute("SELECT * FROM barf")
... except Exception, e:
... except psycopg2.Error as e:
... pass
>>> e.pgcode
@ -159,6 +166,7 @@ available through the following exceptions:
ERROR: relation "barf" does not exist
LINE 1: SELECT * FROM barf
^
.. attribute:: cursor
The cursor the exception was raised from; `None` if not applicable.
@ -170,7 +178,7 @@ available through the following exceptions:
>>> try:
... cur.execute("SELECT * FROM barf")
... except Exception, e:
... except psycopg2.Error, e:
... pass
>>> e.diag.severity
@ -195,41 +203,41 @@ available through the following exceptions:
Exception raised for errors that are related to the database. It is a
subclass of `Error`.
.. exception:: DataError
Exception raised for errors that are due to problems with the processed
data like division by zero, numeric value out of range, etc. It is a
subclass of `DatabaseError`.
.. exception:: OperationalError
Exception raised for errors that are related to the database's operation
and not necessarily under the control of the programmer, e.g. an
unexpected disconnect occurs, the data source name is not found, a
transaction could not be processed, a memory allocation error occurred
during processing, etc. It is a subclass of `DatabaseError`.
.. exception:: IntegrityError
.. exception:: IntegrityError
Exception raised when the relational integrity of the database is
affected, e.g. a foreign key check fails. It is a subclass of
`DatabaseError`.
.. exception:: InternalError
.. exception:: InternalError
Exception raised when the database encounters an internal error, e.g. the
cursor is not valid anymore, the transaction is out of sync, etc. It is a
subclass of `DatabaseError`.
.. exception:: ProgrammingError
Exception raised for programming errors, e.g. table not found or already
exists, syntax error in the SQL statement, wrong number of parameters
specified, etc. It is a subclass of `DatabaseError`.
.. exception:: NotSupportedError
Exception raised in case a method or database API was used which is not
supported by the database, e.g. requesting a `!rollback()` on a
connection that does not support transaction or has transactions turned

View File

@ -3,37 +3,57 @@
ticket role
~~~~~~~~~~~
An interpreted text role to link docs to lighthouse issues.
An interpreted text role to link docs to tickets issues.
:copyright: Copyright 2013 by Daniele Varrazzo.
"""
import re
from docutils import nodes, utils
from docutils.parsers.rst import roles
def ticket_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
try:
num = int(text.replace('#', ''))
except ValueError:
msg = inliner.reporter.error(
"ticket number must be... a number, got '%s'" % text)
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
url_pattern = inliner.document.settings.env.app.config.ticket_url
if url_pattern is None:
cfg = inliner.document.settings.env.app.config
if cfg.ticket_url is None:
msg = inliner.reporter.warning(
"ticket not configured: please configure ticket_url in conf.py")
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
url = url_pattern % num
roles.set_classes(options)
node = nodes.reference(rawtext, 'ticket ' + utils.unescape(text),
refuri=url, **options)
return [node], []
rv = [nodes.Text(name + ' ')]
tokens = re.findall(r'(#?\d+)|([^\d#]+)', text)
for ticket, noise in tokens:
if ticket:
num = int(ticket.replace('#', ''))
# Push numbers of the oldel tickets ahead.
# We moved the tickets from a different tracker to GitHub and the
# latter already had a few ticket numbers taken (as merge
# requests).
remap_until = cfg.ticket_remap_until
remap_offset = cfg.ticket_remap_offset
if remap_until and remap_offset:
if num <= remap_until:
num += remap_offset
url = cfg.ticket_url % num
roles.set_classes(options)
node = nodes.reference(ticket, utils.unescape(ticket),
refuri=url, **options)
rv.append(node)
else:
assert noise
rv.append(nodes.Text(noise))
return rv, []
def setup(app):
app.add_config_value('ticket_url', None, 'env')
app.add_config_value('ticket_remap_until', None, 'env')
app.add_config_value('ticket_remap_offset', None, 'env')
app.add_role('ticket', ticket_role)
app.add_role('tickets', ticket_role)

166
doc/src/tools/pypi_docs_upload.py Executable file
View 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()

View File

@ -145,13 +145,15 @@ query:
The problem with the query parameters
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The SQL representation for many data types is often not the same of the Python
string representation. The classic example is with single quotes in
strings: SQL uses them as string constants bounds and requires them to be
escaped, whereas in Python single quotes can be left unescaped in strings
bounded by double quotes. For this reason a naïve approach to the composition
of query strings, e.g. using string concatenation, is a recipe for terrible
problems::
The SQL representation of many data types is often different from their Python
string representation. The typical example is with single quotes in strings:
in SQL single quotes are used as string literal delimiters, so the ones
appearing inside the string itself must be escaped, whereas in Python single
quotes can be left unescaped if the string is delimited by double quotes.
Because of the difference, sometime subtle, between the data types
representations, a naïve approach to query strings composition, such as using
Python strings concatenation, is a recipe for *terrible* problems::
>>> SQL = "INSERT INTO authors (name) VALUES ('%s');" # NEVER DO THIS
>>> data = ("O'Reilly", )
@ -160,13 +162,13 @@ problems::
LINE 1: INSERT INTO authors (name) VALUES ('O'Reilly')
^
If the variable containing the data to be sent to the database comes from an
untrusted source (e.g. a form published on a web site) an attacker could
If the variables containing the data to send to the database come from an
untrusted source (such as a form published on a web site) an attacker could
easily craft a malformed string, either gaining access to unauthorized data or
performing destructive operations on the database. This form of attack is
called `SQL injection`_ and is known to be one of the most widespread forms of
attack to servers. Before continuing, please print `this page`__ as a memo and
hang it onto your desk.
attack to database servers. Before continuing, please print `this page`__ as a
memo and hang it onto your desk.
.. _SQL injection: http://en.wikipedia.org/wiki/SQL_injection
.. __: http://xkcd.com/327/
@ -243,7 +245,8 @@ types:
+--------------------+-------------------------+--------------------------+
| `!date` | :sql:`date` | :ref:`adapt-date` |
+--------------------+-------------------------+ |
| `!time` | :sql:`time` | |
| `!time` | | :sql:`time` | |
| | | :sql:`timetz` | |
+--------------------+-------------------------+ |
| `!datetime` | | :sql:`timestamp` | |
| | | :sql:`timestamptz` | |
@ -480,7 +483,7 @@ Date/Time objects adaptation
Python builtin `~datetime.datetime`, `~datetime.date`,
`~datetime.time`, `~datetime.timedelta` are converted into PostgreSQL's
:sql:`timestamp[tz]`, :sql:`date`, :sql:`time`, :sql:`interval` data types.
:sql:`timestamp[tz]`, :sql:`date`, :sql:`time[tz]`, :sql:`interval` data types.
Time zones are supported too. The Egenix `mx.DateTime`_ objects are adapted
the same way::
@ -676,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.
@ -702,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::
@ -897,6 +915,20 @@ using the |lo_import|_ and |lo_export|_ libpq functions.
.. |lo_export| replace:: `!lo_export()`
.. _lo_export: http://www.postgresql.org/docs/current/static/lo-interfaces.html#LO-EXPORT
.. versionchanged:: 2.6
added support for large objects greated than 2GB. Note that the support is
enabled only if all the following conditions are verified:
- the Python build is 64 bits;
- the extension was built against at least libpq 9.3;
- the server version is at least PostgreSQL 9.3
(`~connection.server_version` must be >= ``90300``).
If Psycopg was built with 64 bits large objects support (i.e. the first
two contidions above are verified), the `psycopg2.__version__` constant
will contain the ``lo64`` flag. If any of the contition is not met
several `!lobject` methods will fail if the arguments exceed 2GB.
.. index::

View File

@ -57,7 +57,7 @@ from psycopg2._psycopg import IntegrityError, InterfaceError, InternalError
from psycopg2._psycopg import NotSupportedError, OperationalError
from psycopg2._psycopg import _connect, apilevel, threadsafety, paramstyle
from psycopg2._psycopg import __version__
from psycopg2._psycopg import __version__, __libpq_version__
from psycopg2 import tz

View File

@ -47,6 +47,10 @@ else:
JSON_OID = 114
JSONARRAY_OID = 199
# oids from PostgreSQL 9.4
JSONB_OID = 3802
JSONBARRAY_OID = 3807
class Json(object):
"""
An `~psycopg2.extensions.ISQLQuote` wrapper to adapt a Python object to
@ -98,11 +102,11 @@ class Json(object):
else:
def __str__(self):
# getquoted is binary in Py3
return self.getquoted().decode('ascii', errors='replace')
return self.getquoted().decode('ascii', 'replace')
def register_json(conn_or_curs=None, globally=False, loads=None,
oid=None, array_oid=None):
oid=None, array_oid=None, name='json'):
"""Create and register typecasters converting :sql:`json` type to Python objects.
:param conn_or_curs: a connection or cursor used to find the :sql:`json`
@ -118,17 +122,19 @@ def register_json(conn_or_curs=None, globally=False, loads=None,
queried on *conn_or_curs*
:param array_oid: the OID of the :sql:`json[]` array type if known;
if not, it will be queried on *conn_or_curs*
:param name: the name of the data type to look for in *conn_or_curs*
The connection or cursor passed to the function will be used to query the
database and look for the OID of the :sql:`json` type. No query is
performed if *oid* and *array_oid* are provided. Raise
`~psycopg2.ProgrammingError` if the type is not found.
database and look for the OID of the :sql:`json` type (or an alternative
type if *name* if provided). No query is performed if *oid* and *array_oid*
are provided. Raise `~psycopg2.ProgrammingError` if the type is not found.
"""
if oid is None:
oid, array_oid = _get_json_oids(conn_or_curs)
oid, array_oid = _get_json_oids(conn_or_curs, name)
JSON, JSONARRAY = _create_json_typecasters(oid, array_oid, loads)
JSON, JSONARRAY = _create_json_typecasters(
oid, array_oid, loads=loads, name=name.upper())
register_type(JSON, not globally and conn_or_curs or None)
@ -149,7 +155,19 @@ def register_default_json(conn_or_curs=None, globally=False, loads=None):
return register_json(conn_or_curs=conn_or_curs, globally=globally,
loads=loads, oid=JSON_OID, array_oid=JSONARRAY_OID)
def _create_json_typecasters(oid, array_oid, loads=None):
def register_default_jsonb(conn_or_curs=None, globally=False, loads=None):
"""
Create and register :sql:`jsonb` typecasters for PostgreSQL 9.4 and following.
As in `register_default_json()`, the function allows to register a
customized *loads* function for the :sql:`jsonb` type at its known oid for
PostgreSQL 9.4 and following versions. All the parameters have the same
meaning of `register_json()`.
"""
return register_json(conn_or_curs=conn_or_curs, globally=globally,
loads=loads, oid=JSONB_OID, array_oid=JSONBARRAY_OID, name='jsonb')
def _create_json_typecasters(oid, array_oid, loads=None, name='JSON'):
"""Create typecasters for json data type."""
if loads is None:
if json is None:
@ -162,15 +180,15 @@ def _create_json_typecasters(oid, array_oid, loads=None):
return None
return loads(s)
JSON = new_type((oid, ), 'JSON', typecast_json)
JSON = new_type((oid, ), name, typecast_json)
if array_oid is not None:
JSONARRAY = new_array_type((array_oid, ), "JSONARRAY", JSON)
JSONARRAY = new_array_type((array_oid, ), "%sARRAY" % name, JSON)
else:
JSONARRAY = None
return JSON, JSONARRAY
def _get_json_oids(conn_or_curs):
def _get_json_oids(conn_or_curs, name='json'):
# lazy imports
from psycopg2.extensions import STATUS_IN_TRANSACTION
from psycopg2.extras import _solve_conn_curs
@ -185,8 +203,8 @@ def _get_json_oids(conn_or_curs):
# get the oid for the hstore
curs.execute(
"SELECT t.oid, %s FROM pg_type t WHERE t.typname = 'json';"
% typarray)
"SELECT t.oid, %s FROM pg_type t WHERE t.typname = %%s;"
% typarray, (name,))
r = curs.fetchone()
# revert the status of the connection as before the command
@ -194,7 +212,7 @@ def _get_json_oids(conn_or_curs):
conn.rollback()
if not r:
raise conn.ProgrammingError("json data type not found")
raise conn.ProgrammingError("%s data type not found" % name)
return r

View File

@ -56,9 +56,9 @@ try:
except ImportError:
pass
from psycopg2._psycopg import adapt, adapters, encodings, connection, cursor, lobject, Xid
from psycopg2._psycopg import adapt, adapters, encodings, connection, cursor, lobject, Xid, libpq_version, parse_dsn, quote_ident
from psycopg2._psycopg import string_types, binary_types, new_type, new_array_type, register_type
from psycopg2._psycopg import ISQLQuote, Notify, Diagnostics
from psycopg2._psycopg import ISQLQuote, Notify, Diagnostics, Column
from psycopg2._psycopg import QueryCanceledError, TransactionRollbackError
@ -152,20 +152,22 @@ class NoneAdapter(object):
# Create default json typecasters for PostgreSQL 9.2 oids
from psycopg2._json import register_default_json
from psycopg2._json import register_default_json, register_default_jsonb
try:
JSON, JSONARRAY = register_default_json()
JSONB, JSONBARRAY = register_default_jsonb()
except ImportError:
pass
del register_default_json
del register_default_json, register_default_jsonb
# Create default Range typecasters
from psycopg2. _range import Range
del Range
# Add the "cleaned" version of the encodings to the key.
# When the encoding is set its name is cleaned up from - and _ and turned
# uppercase, so an encoding not respecting these rules wouldn't be found in the

View File

@ -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,15 +575,20 @@ def wait_select(conn):
from psycopg2.extensions import POLL_OK, POLL_READ, POLL_WRITE
while 1:
state = conn.poll()
if state == POLL_OK:
break
elif state == POLL_READ:
select.select([conn.fileno()], [], [])
elif state == POLL_WRITE:
select.select([], [conn.fileno()], [])
else:
raise conn.OperationalError("bad state from poll: %s" % state)
try:
state = conn.poll()
if state == POLL_OK:
break
elif state == POLL_READ:
select.select([conn.fileno()], [], [])
elif state == POLL_WRITE:
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):
@ -965,7 +970,8 @@ def register_composite(name, conn_or_curs, globally=False, factory=None):
# expose the json adaptation stuff into the module
from psycopg2._json import json, Json, register_json, register_default_json
from psycopg2._json import json, Json, register_json
from psycopg2._json import register_default_json, register_default_jsonb
# Expose range-related objects

View File

@ -42,8 +42,8 @@ class AbstractConnectionPool(object):
with given parameters. The connection pool will support a maximum of
about 'maxconn' connections.
"""
self.minconn = minconn
self.maxconn = maxconn
self.minconn = int(minconn)
self.maxconn = int(maxconn)
self.closed = False
self._args = args
@ -86,7 +86,7 @@ class AbstractConnectionPool(object):
return conn
else:
if len(self._used) == self.maxconn:
raise PoolError("connection pool exausted")
raise PoolError("connection pool exhausted")
return self._connect(key)
def _putconn(self, conn, key=None, close=False):
@ -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."""

View File

@ -149,12 +149,6 @@ asis_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0);
}
static PyObject *
asis_repr(asisObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.AsIs object at %p>", self);
}
/* object type */
@ -163,14 +157,14 @@ asis_repr(asisObject *self)
PyTypeObject asisType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.AsIs",
"psycopg2.extensions.AsIs",
sizeof(asisObject), 0,
asis_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)asis_repr, /*tp_repr*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
@ -200,17 +194,3 @@ PyTypeObject asisType = {
0, /*tp_alloc*/
asis_new, /*tp_new*/
};
/** module-level functions **/
PyObject *
psyco_AsIs(PyObject *module, PyObject *args)
{
PyObject *obj;
if (!PyArg_ParseTuple(args, "O", &obj))
return NULL;
return PyObject_CallFunctionObjArgs((PyObject *)&asisType, obj, NULL);
}

View File

@ -40,12 +40,6 @@ typedef struct {
} asisObject;
/* functions exported to psycopgmodule.c */
HIDDEN PyObject *psyco_AsIs(PyObject *module, PyObject *args);
#define psyco_AsIs_doc \
"AsIs(obj) -> new AsIs wrapper object"
#ifdef __cplusplus
}
#endif

View File

@ -39,7 +39,7 @@ static unsigned char *
binary_escape(unsigned char *from, size_t from_length,
size_t *to_length, PGconn *conn)
{
#if PG_VERSION_HEX >= 0x080104
#if PG_VERSION_NUM >= 80104
if (conn)
return PQescapeByteaConn(conn, from, from_length, to_length);
else
@ -254,11 +254,6 @@ binary_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0);
}
static PyObject *
binary_repr(binaryObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.Binary object at %p>", self);
}
/* object type */
@ -267,14 +262,14 @@ binary_repr(binaryObject *self)
PyTypeObject binaryType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.Binary",
"psycopg2.extensions.Binary",
sizeof(binaryObject), 0,
binary_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)binary_repr, /*tp_repr*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
@ -304,17 +299,3 @@ PyTypeObject binaryType = {
0, /*tp_alloc*/
binary_new, /*tp_new*/
};
/** module-level functions **/
PyObject *
psyco_Binary(PyObject *module, PyObject *args)
{
PyObject *str;
if (!PyArg_ParseTuple(args, "O", &str))
return NULL;
return PyObject_CallFunctionObjArgs((PyObject *)&binaryType, str, NULL);
}

View File

@ -40,13 +40,6 @@ typedef struct {
PyObject *conn;
} binaryObject;
/* functions exported to psycopgmodule.c */
HIDDEN PyObject *psyco_Binary(PyObject *module, PyObject *args);
#define psyco_Binary_doc \
"Binary(buffer) -> new binary object\n\n" \
"Build an object capable to hold a binary string value."
#ifdef __cplusplus
}
#endif

View File

@ -35,9 +35,6 @@
#include <string.h>
extern HIDDEN PyObject *pyPsycopgTzModule;
extern HIDDEN PyObject *pyPsycopgTzLOCAL;
int
psyco_adapter_datetime_init(void)
{
@ -65,7 +62,10 @@ _pydatetime_string_date_time(pydatetimeObject *self)
char *fmt = NULL;
switch (self->type) {
case PSYCO_DATETIME_TIME:
fmt = "'%s'::time";
tz = PyObject_GetAttrString(self->wrapped, "tzinfo");
if (!tz) { goto error; }
fmt = (tz == Py_None) ? "'%s'::time" : "'%s'::timetz";
Py_DECREF(tz);
break;
case PSYCO_DATETIME_DATE:
fmt = "'%s'::date";
@ -214,12 +214,6 @@ pydatetime_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0);
}
static PyObject *
pydatetime_repr(pydatetimeObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.datetime object at %p>",
self);
}
/* object type */
@ -235,7 +229,7 @@ PyTypeObject pydatetimeType = {
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)pydatetime_repr, /*tp_repr*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
@ -392,9 +386,9 @@ psyco_DateFromTicks(PyObject *self, PyObject *args)
Py_DECREF(args);
}
}
else {
PyErr_SetString(InterfaceError, "failed localtime call");
}
else {
PyErr_SetString(InterfaceError, "failed localtime call");
}
return res;
}
@ -420,9 +414,9 @@ psyco_TimeFromTicks(PyObject *self, PyObject *args)
Py_DECREF(args);
}
}
else {
PyErr_SetString(InterfaceError, "failed localtime call");
}
else {
PyErr_SetString(InterfaceError, "failed localtime call");
}
return res;
}
@ -430,6 +424,8 @@ psyco_TimeFromTicks(PyObject *self, PyObject *args)
PyObject *
psyco_TimestampFromTicks(PyObject *self, PyObject *args)
{
PyObject *m = NULL;
PyObject *tz = NULL;
PyObject *res = NULL;
struct tm tm;
time_t t;
@ -438,18 +434,25 @@ psyco_TimestampFromTicks(PyObject *self, PyObject *args)
if (!PyArg_ParseTuple(args, "d", &ticks))
return NULL;
/* get psycopg2.tz.LOCAL from pythonland */
if (!(m = PyImport_ImportModule("psycopg2.tz"))) { goto exit; }
if (!(tz = PyObject_GetAttrString(m, "LOCAL"))) { goto exit; }
t = (time_t)floor(ticks);
ticks -= (double)t;
if (localtime_r(&t, &tm)) {
res = _psyco_Timestamp(
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, (double)tm.tm_sec + ticks,
pyPsycopgTzLOCAL);
if (!localtime_r(&t, &tm)) {
PyErr_SetString(InterfaceError, "failed localtime call");
goto exit;
}
else {
PyErr_SetString(InterfaceError, "failed localtime call");
}
res = _psyco_Timestamp(
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, (double)tm.tm_sec + ticks,
tz);
exit:
Py_DECREF(tz);
Py_XDECREF(m);
return res;
}

View File

@ -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;
res = Bytes_FromFormat("ARRAY[%s]", Bytes_AsString(joined));
/* 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);
@ -215,11 +229,6 @@ list_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0);
}
static PyObject *
list_repr(listObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.List object at %p>", self);
}
/* object type */
@ -235,7 +244,7 @@ PyTypeObject listType = {
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)list_repr, /*tp_repr*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
@ -265,17 +274,3 @@ PyTypeObject listType = {
0, /*tp_alloc*/
list_new, /*tp_new*/
};
/** module-level functions **/
PyObject *
psyco_List(PyObject *module, PyObject *args)
{
PyObject *str;
if (!PyArg_ParseTuple(args, "O", &str))
return NULL;
return PyObject_CallFunctionObjArgs((PyObject *)&listType, "O", str, NULL);
}

View File

@ -39,10 +39,6 @@ typedef struct {
PyObject *connection;
} listObject;
HIDDEN PyObject *psyco_List(PyObject *module, PyObject *args);
#define psyco_List_doc \
"List(list, enc) -> new quoted list"
#ifdef __cplusplus
}
#endif

View File

@ -205,12 +205,6 @@ mxdatetime_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0);
}
static PyObject *
mxdatetime_repr(mxdatetimeObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.MxDateTime object at %p>",
self);
}
/* object type */
@ -226,7 +220,7 @@ PyTypeObject mxdatetimeType = {
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)mxdatetime_repr, /*tp_repr*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/

View File

@ -37,21 +37,12 @@
static PyObject *
pboolean_getquoted(pbooleanObject *self, PyObject *args)
{
#ifdef PSYCOPG_NEW_BOOLEAN
if (PyObject_IsTrue(self->wrapped)) {
return Bytes_FromString("true");
}
else {
return Bytes_FromString("false");
}
#else
if (PyObject_IsTrue(self->wrapped)) {
return Bytes_FromString("'t'");
}
else {
return Bytes_FromString("'f'");
}
#endif
}
static PyObject *
@ -146,13 +137,6 @@ pboolean_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0);
}
static PyObject *
pboolean_repr(pbooleanObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.Boolean object at %p>",
self);
}
/* object type */
@ -161,14 +145,14 @@ pboolean_repr(pbooleanObject *self)
PyTypeObject pbooleanType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.Boolean",
"psycopg2.extensions.Boolean",
sizeof(pbooleanObject), 0,
pboolean_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)pboolean_repr, /*tp_repr*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
@ -198,17 +182,3 @@ PyTypeObject pbooleanType = {
0, /*tp_alloc*/
pboolean_new, /*tp_new*/
};
/** module-level functions **/
PyObject *
psyco_Boolean(PyObject *module, PyObject *args)
{
PyObject *obj;
if (!PyArg_ParseTuple(args, "O", &obj))
return NULL;
return PyObject_CallFunctionObjArgs((PyObject *)&pbooleanType, obj, NULL);
}

View File

@ -40,12 +40,6 @@ typedef struct {
} pbooleanObject;
/* functions exported to psycopgmodule.c */
HIDDEN PyObject *psyco_Boolean(PyObject *module, PyObject *args);
#define psyco_Boolean_doc \
"Boolean(obj) -> new boolean value"
#ifdef __cplusplus
}
#endif

View File

@ -202,13 +202,6 @@ pdecimal_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0);
}
static PyObject *
pdecimal_repr(pdecimalObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.Decimal object at %p>",
self);
}
/* object type */
@ -224,7 +217,7 @@ PyTypeObject pdecimalType = {
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)pdecimal_repr, /*tp_repr*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
@ -254,17 +247,3 @@ PyTypeObject pdecimalType = {
0, /*tp_alloc*/
pdecimal_new, /*tp_new*/
};
/** module-level functions **/
PyObject *
psyco_Decimal(PyObject *module, PyObject *args)
{
PyObject *obj;
if (!PyArg_ParseTuple(args, "O", &obj))
return NULL;
return PyObject_CallFunctionObjArgs((PyObject *)&pdecimalType, obj, NULL);
}

View File

@ -40,12 +40,6 @@ typedef struct {
} pdecimalObject;
/* functions exported to psycopgmodule.c */
HIDDEN PyObject *psyco_Decimal(PyObject *module, PyObject *args);
#define psyco_Decimal_doc \
"Decimal(obj) -> new decimal.Decimal value"
#ifdef __cplusplus
}
#endif

View File

@ -175,13 +175,6 @@ pfloat_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0);
}
static PyObject *
pfloat_repr(pfloatObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.Float object at %p>",
self);
}
/* object type */
@ -190,14 +183,14 @@ pfloat_repr(pfloatObject *self)
PyTypeObject pfloatType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.Float",
"psycopg2.extensions.Float",
sizeof(pfloatObject), 0,
pfloat_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)pfloat_repr, /*tp_repr*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
@ -227,17 +220,3 @@ PyTypeObject pfloatType = {
0, /*tp_alloc*/
pfloat_new, /*tp_new*/
};
/** module-level functions **/
PyObject *
psyco_Float(PyObject *module, PyObject *args)
{
PyObject *obj;
if (!PyArg_ParseTuple(args, "O", &obj))
return NULL;
return PyObject_CallFunctionObjArgs((PyObject *)&pfloatType, obj, NULL);
}

View File

@ -40,12 +40,6 @@ typedef struct {
} pfloatObject;
/* functions exported to psycopgmodule.c */
HIDDEN PyObject *psyco_Float(PyObject *module, PyObject *args);
#define psyco_Float_doc \
"Float(obj) -> new float value"
#ifdef __cplusplus
}
#endif

View File

@ -161,13 +161,6 @@ pint_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0);
}
static PyObject *
pint_repr(pintObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.Int object at %p>",
self);
}
/* object type */
@ -176,14 +169,14 @@ pint_repr(pintObject *self)
PyTypeObject pintType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.Int",
"psycopg2.extensions.Int",
sizeof(pintObject), 0,
pint_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)pint_repr, /*tp_repr*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
@ -213,17 +206,3 @@ PyTypeObject pintType = {
0, /*tp_alloc*/
pint_new, /*tp_new*/
};
/** module-level functions **/
PyObject *
psyco_Int(PyObject *module, PyObject *args)
{
PyObject *obj;
if (!PyArg_ParseTuple(args, "O", &obj))
return NULL;
return PyObject_CallFunctionObjArgs((PyObject *)&pintType, obj, NULL);
}

View File

@ -40,12 +40,6 @@ typedef struct {
} pintObject;
/* functions exported to psycopgmodule.c */
HIDDEN PyObject *psyco_Int(PyObject *module, PyObject *args);
#define psyco_Int_doc \
"Int(obj) -> new int value"
#ifdef __cplusplus
}
#endif

View File

@ -242,12 +242,6 @@ qstring_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return type->tp_alloc(type, 0);
}
static PyObject *
qstring_repr(qstringObject *self)
{
return PyString_FromFormat("<psycopg2._psycopg.QuotedString object at %p>",
self);
}
/* object type */
@ -256,14 +250,14 @@ qstring_repr(qstringObject *self)
PyTypeObject qstringType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.QuotedString",
"psycopg2.extensions.QuotedString",
sizeof(qstringObject), 0,
qstring_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
(reprfunc)qstring_repr, /*tp_repr*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
@ -293,17 +287,3 @@ PyTypeObject qstringType = {
0, /*tp_alloc*/
qstring_new, /*tp_new*/
};
/** module-level functions **/
PyObject *
psyco_QuotedString(PyObject *module, PyObject *args)
{
PyObject *str;
if (!PyArg_ParseTuple(args, "O", &str))
return NULL;
return PyObject_CallFunctionObjArgs((PyObject *)&qstringType, str, NULL);
}

View File

@ -41,12 +41,6 @@ typedef struct {
connectionObject *conn;
} qstringObject;
/* functions exported to psycopgmodule.c */
HIDDEN PyObject *psyco_QuotedString(PyObject *module, PyObject *args);
#define psyco_QuotedString_doc \
"QuotedString(str, enc) -> new quoted string"
#ifdef __cplusplus
}
#endif

View File

@ -129,32 +129,32 @@ 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
/* postgresql < 7.4 does not have PQfreemem */
#ifndef HAVE_PQFREEMEM
#define PQfreemem free
#endif
/* resolve missing isinf() function for Solaris */

View File

@ -71,7 +71,7 @@ extern HIDDEN PyTypeObject connectionType;
struct connectionObject_notice {
struct connectionObject_notice *next;
const char *message;
char *message;
};
/* the typedef is forward-declared in psycopg.h */
@ -106,8 +106,8 @@ struct connectionObject {
/* notice processing */
PyObject *notice_list;
PyObject *notice_filter;
struct connectionObject_notice *notice_pending;
struct connectionObject_notice *last_notice;
/* notifies */
PyObject *notifies;

View File

@ -87,13 +87,20 @@ conn_notice_callback(void *args, const char *message)
/* Discard the notice in case of failed allocation. */
return;
}
notice->next = NULL;
notice->message = strdup(message);
if (NULL == notice->message) {
free(notice);
return;
}
notice->next = self->notice_pending;
self->notice_pending = notice;
if (NULL == self->last_notice) {
self->notice_pending = self->last_notice = notice;
}
else {
self->last_notice->next = notice;
self->last_notice = notice;
}
}
/* Expose the notices received as Python objects.
@ -104,44 +111,60 @@ void
conn_notice_process(connectionObject *self)
{
struct connectionObject_notice *notice;
Py_ssize_t nnotices;
PyObject *msg = NULL;
PyObject *tmp = NULL;
static PyObject *append;
if (NULL == self->notice_pending) {
return;
}
notice = self->notice_pending;
nnotices = PyList_GET_SIZE(self->notice_list);
if (!append) {
if (!(append = Text_FromUTF8("append"))) {
goto error;
}
}
notice = self->notice_pending;
while (notice != NULL) {
PyObject *msg;
msg = conn_text_from_chars(self, notice->message);
Dprintf("conn_notice_process: %s", notice->message);
/* Respect the order in which notices were produced,
because in notice_list they are reversed (see ticket #9) */
if (msg) {
PyList_Insert(self->notice_list, nnotices, msg);
Py_DECREF(msg);
}
else {
/* We don't really have a way to report errors, so gulp it.
* The function should only fail for out of memory, so we are
* likely going to die anyway. */
PyErr_Clear();
if (!(msg = conn_text_from_chars(self, notice->message))) { goto error; }
if (!(tmp = PyObject_CallMethodObjArgs(
self->notice_list, append, msg, NULL))) {
goto error;
}
Py_DECREF(tmp); tmp = NULL;
Py_DECREF(msg); msg = NULL;
notice = notice->next;
}
/* Remove the oldest item if the queue is getting too long. */
nnotices = PyList_GET_SIZE(self->notice_list);
if (nnotices > CONN_NOTICES_LIMIT) {
PySequence_DelSlice(self->notice_list,
0, nnotices - CONN_NOTICES_LIMIT);
if (PyList_Check(self->notice_list)) {
Py_ssize_t nnotices;
nnotices = PyList_GET_SIZE(self->notice_list);
if (nnotices > CONN_NOTICES_LIMIT) {
if (-1 == PySequence_DelSlice(self->notice_list,
0, nnotices - CONN_NOTICES_LIMIT)) {
PyErr_Clear();
}
}
}
conn_notice_clean(self);
return;
error:
Py_XDECREF(tmp);
Py_XDECREF(msg);
conn_notice_clean(self);
/* TODO: the caller doesn't expects errors from us */
PyErr_Clear();
}
void
@ -154,11 +177,11 @@ conn_notice_clean(connectionObject *self)
while (notice != NULL) {
tmp = notice;
notice = notice->next;
free((void*)tmp->message);
free(tmp->message);
free(tmp);
}
self->notice_pending = NULL;
self->last_notice = self->notice_pending = NULL;
}
@ -173,6 +196,15 @@ conn_notifies_process(connectionObject *self)
PGnotify *pgn = NULL;
PyObject *notify = NULL;
PyObject *pid = NULL, *channel = NULL, *payload = NULL;
PyObject *tmp = NULL;
static PyObject *append;
if (!append) {
if (!(append = Text_FromUTF8("append"))) {
goto error;
}
}
while ((pgn = PQnotifies(self->pgconn)) != NULL) {
@ -192,7 +224,11 @@ conn_notifies_process(connectionObject *self)
Py_DECREF(channel); channel = NULL;
Py_DECREF(payload); payload = NULL;
PyList_Append(self->notifies, (PyObject *)notify);
if (!(tmp = PyObject_CallMethodObjArgs(
self->notifies, append, notify, NULL))) {
goto error;
}
Py_DECREF(tmp); tmp = NULL;
Py_DECREF(notify); notify = NULL;
PQfreemem(pgn); pgn = NULL;
@ -201,6 +237,7 @@ conn_notifies_process(connectionObject *self)
error:
if (pgn) { PQfreemem(pgn); }
Py_XDECREF(tmp);
Py_XDECREF(notify);
Py_XDECREF(pid);
Py_XDECREF(channel);

View File

@ -103,7 +103,7 @@ psyco_conn_cursor(connectionObject *self, PyObject *args, PyObject *kwargs)
if (PyObject_IsInstance(obj, (PyObject *)&cursorType) == 0) {
PyErr_SetString(PyExc_TypeError,
"cursor factory must be subclass of psycopg2._psycopg.cursor");
"cursor factory must be subclass of psycopg2.extensions.cursor");
goto exit;
}
@ -442,9 +442,6 @@ exit:
}
#ifdef PSYCOPG_EXTENSIONS
/* parse a python object into one of the possible isolation level values */
extern const IsolationLevel conn_isolevels[];
@ -787,7 +784,7 @@ psyco_conn_lobject(connectionObject *self, PyObject *args, PyObject *keywds)
if (obj == NULL) return NULL;
if (PyObject_IsInstance(obj, (PyObject *)&lobjectType) == 0) {
PyErr_SetString(PyExc_TypeError,
"lobject factory must be subclass of psycopg2._psycopg.lobject");
"lobject factory must be subclass of psycopg2.extensions.lobject");
Py_DECREF(obj);
return NULL;
}
@ -843,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)
{
@ -860,8 +861,6 @@ psyco_conn_poll(connectionObject *self)
}
/* extension: fileno - return the file descriptor of the connection */
#define psyco_conn_fileno_doc \
"fileno() -> int -- Return file descriptor associated to database connection."
@ -878,8 +877,6 @@ psyco_conn_fileno(connectionObject *self)
}
/* extension: isexecuting - check for asynchronous operations */
#define psyco_conn_isexecuting_doc \
"isexecuting() -> bool -- Return True if the connection is " \
"executing an asynchronous operation."
@ -911,8 +908,6 @@ psyco_conn_isexecuting(connectionObject *self)
}
/* extension: cancel - cancel the current operation */
#define psyco_conn_cancel_doc \
"cancel() -- cancel the current operation"
@ -941,8 +936,6 @@ psyco_conn_cancel(connectionObject *self)
Py_RETURN_NONE;
}
#endif /* PSYCOPG_EXTENSIONS */
/** the connection object **/
@ -974,7 +967,6 @@ static struct PyMethodDef connectionObject_methods[] = {
METH_NOARGS, psyco_conn_enter_doc},
{"__exit__", (PyCFunction)psyco_conn_exit,
METH_VARARGS, psyco_conn_exit_doc},
#ifdef PSYCOPG_EXTENSIONS
{"set_session", (PyCFunction)psyco_conn_set_session,
METH_VARARGS|METH_KEYWORDS, psyco_conn_set_session_doc},
{"set_isolation_level", (PyCFunction)psyco_conn_set_isolation_level,
@ -992,27 +984,25 @@ 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,
METH_NOARGS, psyco_conn_isexecuting_doc},
{"cancel", (PyCFunction)psyco_conn_cancel,
METH_NOARGS, psyco_conn_cancel_doc},
#endif
{NULL}
};
/* object member list */
static struct PyMemberDef connectionObject_members[] = {
#ifdef PSYCOPG_EXTENSIONS
{"closed", T_LONG, offsetof(connectionObject, closed), READONLY,
"True if the connection is closed."},
{"encoding", T_STRING, offsetof(connectionObject, encoding), READONLY,
"The current client encoding."},
{"notices", T_OBJECT, offsetof(connectionObject, notice_list), READONLY},
{"notifies", T_OBJECT, offsetof(connectionObject, notifies), READONLY},
{"notices", T_OBJECT, offsetof(connectionObject, notice_list), 0},
{"notifies", T_OBJECT, offsetof(connectionObject, notifies), 0},
{"dsn", T_STRING, offsetof(connectionObject, dsn), READONLY,
"The current connection string."},
{"async", T_LONG, offsetof(connectionObject, async), READONLY,
@ -1032,7 +1022,6 @@ static struct PyMemberDef connectionObject_members[] = {
{"server_version", T_INT,
offsetof(connectionObject, server_version), READONLY,
"Server version."},
#endif
{NULL}
};
@ -1040,7 +1029,6 @@ static struct PyMemberDef connectionObject_members[] = {
{ #exc, psyco_conn_get_exception, NULL, exc ## _doc, &exc }
static struct PyGetSetDef connectionObject_getsets[] = {
/* DBAPI-2.0 extensions (exception objects) */
EXCEPTION_GETTER(Error),
EXCEPTION_GETTER(Warning),
EXCEPTION_GETTER(InterfaceError),
@ -1051,7 +1039,6 @@ static struct PyGetSetDef connectionObject_getsets[] = {
EXCEPTION_GETTER(IntegrityError),
EXCEPTION_GETTER(DataError),
EXCEPTION_GETTER(NotSupportedError),
#ifdef PSYCOPG_EXTENSIONS
{ "autocommit",
(getter)psyco_conn_autocommit_get,
(setter)psyco_conn_autocommit_set,
@ -1060,7 +1047,6 @@ static struct PyGetSetDef connectionObject_getsets[] = {
(getter)psyco_conn_isolation_level_get,
(setter)NULL,
"The current isolation level." },
#endif
{NULL}
};
#undef EXCEPTION_GETTER
@ -1119,7 +1105,6 @@ connection_clear(connectionObject *self)
Py_CLEAR(self->tpc_xid);
Py_CLEAR(self->async_cursor);
Py_CLEAR(self->notice_list);
Py_CLEAR(self->notice_filter);
Py_CLEAR(self->notifies);
Py_CLEAR(self->string_types);
Py_CLEAR(self->binary_types);
@ -1195,7 +1180,6 @@ connection_traverse(connectionObject *self, visitproc visit, void *arg)
Py_VISIT((PyObject *)(self->tpc_xid));
Py_VISIT(self->async_cursor);
Py_VISIT(self->notice_list);
Py_VISIT(self->notice_filter);
Py_VISIT(self->notifies);
Py_VISIT(self->string_types);
Py_VISIT(self->binary_types);
@ -1214,7 +1198,7 @@ connection_traverse(connectionObject *self, visitproc visit, void *arg)
PyTypeObject connectionType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.connection",
"psycopg2.extensions.connection",
sizeof(connectionObject), 0,
connection_dealloc, /*tp_dealloc*/
0, /*tp_print*/

View File

@ -39,9 +39,6 @@
#include <stdlib.h>
extern PyObject *pyPsycopgTzFixedOffsetTimezone;
/** DBAPI methods **/
/* close method - close the cursor */
@ -60,10 +57,24 @@ psyco_curs_close(cursorObject *self)
if (self->name != NULL) {
char buffer[128];
PGTransactionStatusType status;
EXC_IF_NO_MARK(self);
PyOS_snprintf(buffer, 127, "CLOSE \"%s\"", self->name);
if (pq_execute(self, buffer, 0, 0) == -1) return NULL;
if (self->conn) {
status = PQtransactionStatus(self->conn->pgconn);
}
else {
status = PQTRANS_UNKNOWN;
}
if (!(status == PQTRANS_UNKNOWN || status == PQTRANS_INERROR)) {
EXC_IF_NO_MARK(self);
PyOS_snprintf(buffer, 127, "CLOSE \"%s\"", self->name);
if (pq_execute(self, buffer, 0, 0, 1) == -1) return NULL;
}
else {
Dprintf("skipping named curs close because tx status %d",
(int)status);
}
}
self->closed = 1;
@ -444,7 +455,7 @@ _psyco_curs_execute(cursorObject *self,
/* At this point, the SQL statement must be str, not unicode */
tmp = pq_execute(self, Bytes_AS_STRING(self->query), async, no_result);
tmp = pq_execute(self, Bytes_AS_STRING(self->query), async, no_result, 0);
Dprintf("psyco_curs_execute: res = %d, pgres = %p", tmp, self->pgres);
if (tmp < 0) { goto exit; }
@ -478,7 +489,7 @@ psyco_curs_execute(cursorObject *self, PyObject *args, PyObject *kwargs)
"can't call .execute() on named cursors more than once");
return NULL;
}
if (self->conn->autocommit) {
if (self->conn->autocommit && !self->withhold) {
psyco_set_error(ProgrammingError, self,
"can't use a named cursor outside of transactions");
return NULL;
@ -559,7 +570,6 @@ psyco_curs_executemany(cursorObject *self, PyObject *args, PyObject *kwargs)
}
#ifdef PSYCOPG_EXTENSIONS
#define psyco_curs_mogrify_doc \
"mogrify(query, vars=None) -> str -- Return query after vars binding."
@ -622,7 +632,6 @@ psyco_curs_mogrify(cursorObject *self, PyObject *args, PyObject *kwargs)
return _psyco_curs_mogrify(self, operation, vars);
}
#endif
/* cast method - convert an oid/string into a Python object */
@ -766,7 +775,7 @@ psyco_curs_fetchone(cursorObject *self)
EXC_IF_ASYNC_IN_PROGRESS(self, fetchone);
EXC_IF_TPC_PREPARED(self->conn, fetchone);
PyOS_snprintf(buffer, 127, "FETCH FORWARD 1 FROM \"%s\"", self->name);
if (pq_execute(self, buffer, 0, 0) == -1) return NULL;
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL;
if (_psyco_curs_prefetch(self) < 0) return NULL;
}
@ -816,7 +825,7 @@ psyco_curs_next_named(cursorObject *self)
PyOS_snprintf(buffer, 127, "FETCH FORWARD %ld FROM \"%s\"",
self->itersize, self->name);
if (pq_execute(self, buffer, 0, 0) == -1) return NULL;
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL;
if (_psyco_curs_prefetch(self) < 0) return NULL;
}
@ -885,7 +894,7 @@ psyco_curs_fetchmany(cursorObject *self, PyObject *args, PyObject *kwords)
EXC_IF_TPC_PREPARED(self->conn, fetchone);
PyOS_snprintf(buffer, 127, "FETCH FORWARD %d FROM \"%s\"",
(int)size, self->name);
if (pq_execute(self, buffer, 0, 0) == -1) { goto exit; }
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) { goto exit; }
if (_psyco_curs_prefetch(self) < 0) { goto exit; }
}
@ -960,7 +969,7 @@ psyco_curs_fetchall(cursorObject *self)
EXC_IF_ASYNC_IN_PROGRESS(self, fetchall);
EXC_IF_TPC_PREPARED(self->conn, fetchall);
PyOS_snprintf(buffer, 127, "FETCH FORWARD ALL FROM \"%s\"", self->name);
if (pq_execute(self, buffer, 0, 0) == -1) { goto exit; }
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) { goto exit; }
if (_psyco_curs_prefetch(self) < 0) { goto exit; }
}
@ -1017,7 +1026,7 @@ _escape_identifier(PGconn *pgconn, const char *str, size_t length)
{
char *rv = NULL;
#if PG_VERSION_HEX >= 0x090000
#if PG_VERSION_NUM >= 90000
rv = PQescapeIdentifier(pgconn, str, length);
if (!rv) {
char *msg;
@ -1029,7 +1038,7 @@ _escape_identifier(PGconn *pgconn, const char *str, size_t length)
}
#else
PyErr_Format(PyExc_NotImplementedError,
"named parameters require psycopg2 compiled against libpq 9.0+");
"named parameters require psycopg2 compiled against libpq 9.0");
#endif
return rv;
@ -1068,9 +1077,7 @@ psyco_curs_callproc(cursorObject *self, PyObject *args)
}
if (parameters != Py_None) {
if (-1 == (nparameters = PyObject_Length(parameters))) {
goto exit;
}
if (-1 == (nparameters = PyObject_Length(parameters))) { goto exit; }
}
using_dict = nparameters > 0 && PyDict_Check(parameters);
@ -1288,7 +1295,7 @@ psyco_curs_scroll(cursorObject *self, PyObject *args, PyObject *kwargs)
else {
PyOS_snprintf(buffer, 127, "MOVE %d FROM \"%s\"", value, self->name);
}
if (pq_execute(self, buffer, 0, 0) == -1) return NULL;
if (pq_execute(self, buffer, 0, 0, self->withhold) == -1) return NULL;
if (_psyco_curs_prefetch(self) < 0) return NULL;
}
@ -1332,8 +1339,6 @@ exit:
}
#ifdef PSYCOPG_EXTENSIONS
/* Return a newly allocated buffer containing the list of columns to be
* copied. On error return NULL and set an exception.
*/
@ -1501,7 +1506,7 @@ psyco_curs_copy_from(cursorObject *self, PyObject *args, PyObject *kwargs)
Py_INCREF(file);
self->copyfile = file;
if (pq_execute(self, query, 0, 0) >= 0) {
if (pq_execute(self, query, 0, 0, 0) >= 0) {
res = Py_None;
Py_INCREF(Py_None);
}
@ -1595,7 +1600,7 @@ psyco_curs_copy_to(cursorObject *self, PyObject *args, PyObject *kwargs)
Py_INCREF(file);
self->copyfile = file;
if (pq_execute(self, query, 0, 0) >= 0) {
if (pq_execute(self, query, 0, 0, 0) >= 0) {
res = Py_None;
Py_INCREF(Py_None);
}
@ -1649,7 +1654,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. */
@ -1669,7 +1674,7 @@ psyco_curs_copy_expert(cursorObject *self, PyObject *args, PyObject *kwargs)
self->copyfile = file;
/* At this point, the SQL statement must be str, not unicode */
if (pq_execute(self, Bytes_AS_STRING(sql), 0, 0) >= 0) {
if (pq_execute(self, Bytes_AS_STRING(sql), 0, 0, 0) >= 0) {
res = Py_None;
Py_INCREF(res);
}
@ -1779,8 +1784,6 @@ psyco_curs_scrollable_set(cursorObject *self, PyObject *pyvalue)
return 0;
}
#endif
/** the cursor object **/
@ -1848,7 +1851,6 @@ static struct PyMethodDef cursorObject_methods[] = {
{"__exit__", (PyCFunction)psyco_curs_exit,
METH_VARARGS, psyco_curs_exit_doc},
/* psycopg extensions */
#ifdef PSYCOPG_EXTENSIONS
{"cast", (PyCFunction)psyco_curs_cast,
METH_VARARGS, psyco_curs_cast_doc},
{"mogrify", (PyCFunction)psyco_curs_mogrify,
@ -1859,7 +1861,6 @@ static struct PyMethodDef cursorObject_methods[] = {
METH_VARARGS|METH_KEYWORDS, psyco_curs_copy_to_doc},
{"copy_expert", (PyCFunction)psyco_curs_copy_expert,
METH_VARARGS|METH_KEYWORDS, psyco_curs_copy_expert_doc},
#endif
{NULL}
};
@ -1885,7 +1886,6 @@ static struct PyMemberDef cursorObject_members[] = {
"The current row position."},
{"connection", T_OBJECT, OFFSETOF(conn), READONLY,
"The connection where the cursor comes from."},
#ifdef PSYCOPG_EXTENSIONS
{"name", T_STRING, OFFSETOF(name), READONLY},
{"statusmessage", T_OBJECT, OFFSETOF(pgstatus), READONLY,
"The return message of the last command."},
@ -1896,13 +1896,11 @@ static struct PyMemberDef cursorObject_members[] = {
{"typecaster", T_OBJECT, OFFSETOF(caster), READONLY},
{"string_types", T_OBJECT, OFFSETOF(string_types), 0},
{"binary_types", T_OBJECT, OFFSETOF(binary_types), 0},
#endif
{NULL}
};
/* object calculated member list */
static struct PyGetSetDef cursorObject_getsets[] = {
#ifdef PSYCOPG_EXTENSIONS
{ "closed", (getter)psyco_curs_get_closed, NULL,
psyco_curs_closed_doc, NULL },
{ "withhold",
@ -1913,7 +1911,6 @@ static struct PyGetSetDef cursorObject_getsets[] = {
(getter)psyco_curs_scrollable_get,
(setter)psyco_curs_scrollable_set,
psyco_curs_scrollable_doc, NULL },
#endif
{NULL}
};
@ -1935,7 +1932,7 @@ cursor_setup(cursorObject *self, connectionObject *conn, const char *name)
if (PyObject_IsInstance((PyObject*)conn,
(PyObject *)&connectionType) == 0) {
PyErr_SetString(PyExc_TypeError,
"argument 1 must be subclass of psycopg2._psycopg.connection");
"argument 1 must be subclass of psycopg2.extensions.connection");
return -1;
} */
Py_INCREF(conn);
@ -1952,8 +1949,17 @@ cursor_setup(cursorObject *self, connectionObject *conn, const char *name)
self->tuple_factory = Py_None;
/* default tzinfo factory */
Py_INCREF(pyPsycopgTzFixedOffsetTimezone);
self->tzinfo_factory = pyPsycopgTzFixedOffsetTimezone;
{
PyObject *m = NULL;
if ((m = PyImport_ImportModule("psycopg2.tz"))) {
self->tzinfo_factory = PyObject_GetAttrString(
m, "FixedOffsetTimezone");
Py_DECREF(m);
}
if (!self->tzinfo_factory) {
return -1;
}
}
Dprintf("cursor_setup: good cursor object at %p, refcnt = "
FORMAT_CODE_PY_SSIZE_T,
@ -2076,7 +2082,7 @@ cursor_traverse(cursorObject *self, visitproc visit, void *arg)
PyTypeObject cursorType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.cursor",
"psycopg2.extensions.cursor",
sizeof(cursorObject), 0,
cursor_dealloc, /*tp_dealloc*/
0, /*tp_print*/

View File

@ -158,7 +158,7 @@ static const char diagnosticsType_doc[] =
PyTypeObject diagnosticsType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.Diagnostics",
"psycopg2.extensions.Diagnostics",
sizeof(diagnosticsObject), 0,
(destructor)diagnostics_dealloc, /*tp_dealloc*/
0, /*tp_print*/

View File

@ -80,11 +80,7 @@ psyco_get_wait_callback(PyObject *self, PyObject *obj)
int
psyco_green()
{
#ifdef PSYCOPG_EXTENSIONS
return (NULL != wait_callback);
#else
return 0;
#endif
}
/* Return the wait callback if available.

View File

@ -60,8 +60,8 @@ RAISES_NEG HIDDEN int lobject_export(lobjectObject *self, const char *filename);
RAISES_NEG HIDDEN Py_ssize_t lobject_read(lobjectObject *self, char *buf, size_t len);
RAISES_NEG HIDDEN Py_ssize_t lobject_write(lobjectObject *self, const char *buf,
size_t len);
RAISES_NEG HIDDEN int lobject_seek(lobjectObject *self, int pos, int whence);
RAISES_NEG HIDDEN int lobject_tell(lobjectObject *self);
RAISES_NEG HIDDEN long lobject_seek(lobjectObject *self, long pos, int whence);
RAISES_NEG HIDDEN long lobject_tell(lobjectObject *self);
RAISES_NEG HIDDEN int lobject_truncate(lobjectObject *self, size_t len);
RAISES_NEG HIDDEN int lobject_close(lobjectObject *self);

View File

@ -32,8 +32,6 @@
#include <string.h>
#ifdef PSYCOPG_EXTENSIONS
static void
collect_error(connectionObject *conn, char **error)
{
@ -378,21 +376,29 @@ lobject_read(lobjectObject *self, char *buf, size_t len)
/* lobject_seek - move the current position in the lo */
RAISES_NEG int
lobject_seek(lobjectObject *self, int pos, int whence)
RAISES_NEG long
lobject_seek(lobjectObject *self, long pos, int whence)
{
PGresult *pgres = NULL;
char *error = NULL;
int where;
long where;
Dprintf("lobject_seek: fd = %d, pos = %d, whence = %d",
Dprintf("lobject_seek: fd = %d, pos = %ld, whence = %d",
self->fd, pos, whence);
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(self->conn->lock));
where = lo_lseek(self->conn->pgconn, self->fd, pos, whence);
Dprintf("lobject_seek: where = %d", where);
#ifdef HAVE_LO64
if (self->conn->server_version < 90300) {
where = (long)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
} else {
where = lo_lseek64(self->conn->pgconn, self->fd, pos, whence);
}
#else
where = (long)lo_lseek(self->conn->pgconn, self->fd, (int)pos, whence);
#endif
Dprintf("lobject_seek: where = %ld", where);
if (where < 0)
collect_error(self->conn, &error);
@ -406,20 +412,28 @@ lobject_seek(lobjectObject *self, int pos, int whence)
/* lobject_tell - tell the current position in the lo */
RAISES_NEG int
RAISES_NEG long
lobject_tell(lobjectObject *self)
{
PGresult *pgres = NULL;
char *error = NULL;
int where;
long where;
Dprintf("lobject_tell: fd = %d", self->fd);
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(self->conn->lock));
where = lo_tell(self->conn->pgconn, self->fd);
Dprintf("lobject_tell: where = %d", where);
#ifdef HAVE_LO64
if (self->conn->server_version < 90300) {
where = (long)lo_tell(self->conn->pgconn, self->fd);
} else {
where = lo_tell64(self->conn->pgconn, self->fd);
}
#else
where = (long)lo_tell(self->conn->pgconn, self->fd);
#endif
Dprintf("lobject_tell: where = %ld", where);
if (where < 0)
collect_error(self->conn, &error);
@ -460,7 +474,7 @@ lobject_export(lobjectObject *self, const char *filename)
return retvalue;
}
#if PG_VERSION_HEX >= 0x080300
#if PG_VERSION_NUM >= 80300
RAISES_NEG int
lobject_truncate(lobjectObject *self, size_t len)
@ -475,7 +489,15 @@ lobject_truncate(lobjectObject *self, size_t len)
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(self->conn->lock));
#ifdef HAVE_LO64
if (self->conn->server_version < 90300) {
retvalue = lo_truncate(self->conn->pgconn, self->fd, len);
} else {
retvalue = lo_truncate64(self->conn->pgconn, self->fd, len);
}
#else
retvalue = lo_truncate(self->conn->pgconn, self->fd, len);
#endif
Dprintf("lobject_truncate: result = %d", retvalue);
if (retvalue < 0)
collect_error(self->conn, &error);
@ -489,7 +511,4 @@ lobject_truncate(lobjectObject *self, size_t len)
}
#endif /* PG_VERSION_HEX >= 0x080300 */
#endif
#endif /* PG_VERSION_NUM >= 80300 */

View File

@ -35,8 +35,6 @@
#include <string.h>
#ifdef PSYCOPG_EXTENSIONS
/** public methods **/
/* close method - close the lobject */
@ -52,7 +50,7 @@ psyco_lobj_close(lobjectObject *self, PyObject *args)
opened large objects */
if (!lobject_is_closed(self)
&& !self->conn->autocommit
&& self->conn->mark == self->mark)
&& self->conn->mark == self->mark)
{
Dprintf("psyco_lobj_close: closing lobject at %p", self);
if (lobject_close(self) < 0)
@ -123,7 +121,7 @@ static PyObject *
psyco_lobj_read(lobjectObject *self, PyObject *args)
{
PyObject *res;
int where, end;
long where, end;
Py_ssize_t size = -1;
char *buffer;
@ -167,20 +165,39 @@ psyco_lobj_read(lobjectObject *self, PyObject *args)
static PyObject *
psyco_lobj_seek(lobjectObject *self, PyObject *args)
{
int offset, whence=0;
int pos=0;
long offset, pos=0;
int whence=0;
if (!PyArg_ParseTuple(args, "i|i", &offset, &whence))
return NULL;
if (!PyArg_ParseTuple(args, "l|i", &offset, &whence))
return NULL;
EXC_IF_LOBJ_CLOSED(self);
EXC_IF_LOBJ_LEVEL0(self);
EXC_IF_LOBJ_UNMARKED(self);
if ((pos = lobject_seek(self, offset, whence)) < 0)
return NULL;
#ifdef HAVE_LO64
if ((offset < INT_MIN || offset > INT_MAX)
&& self->conn->server_version < 90300) {
PyErr_Format(NotSupportedError,
"offset out of range (%ld): server version %d "
"does not support the lobject 64 API",
offset, self->conn->server_version);
return NULL;
}
#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);
return NULL;
}
#endif
return PyInt_FromLong((long)pos);
if ((pos = lobject_seek(self, offset, whence)) < 0)
return NULL;
return PyLong_FromLong(pos);
}
/* tell method - tell current position in the lobject */
@ -191,16 +208,16 @@ psyco_lobj_seek(lobjectObject *self, PyObject *args)
static PyObject *
psyco_lobj_tell(lobjectObject *self, PyObject *args)
{
int pos;
long pos;
EXC_IF_LOBJ_CLOSED(self);
EXC_IF_LOBJ_LEVEL0(self);
EXC_IF_LOBJ_UNMARKED(self);
if ((pos = lobject_tell(self)) < 0)
return NULL;
return NULL;
return PyInt_FromLong((long)pos);
return PyLong_FromLong(pos);
}
/* unlink method - unlink (destroy) the lobject */
@ -249,7 +266,7 @@ psyco_lobj_get_closed(lobjectObject *self, void *closure)
return closed;
}
#if PG_VERSION_HEX >= 0x080300
#if PG_VERSION_NUM >= 80300
#define psyco_lobj_truncate_doc \
"truncate(len=0) -- Truncate large object to given size."
@ -257,23 +274,39 @@ psyco_lobj_get_closed(lobjectObject *self, void *closure)
static PyObject *
psyco_lobj_truncate(lobjectObject *self, PyObject *args)
{
int len = 0;
long len = 0;
if (!PyArg_ParseTuple(args, "|i", &len))
if (!PyArg_ParseTuple(args, "|l", &len))
return NULL;
EXC_IF_LOBJ_CLOSED(self);
EXC_IF_LOBJ_LEVEL0(self);
EXC_IF_LOBJ_UNMARKED(self);
#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, 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);
return NULL;
}
#endif
if (lobject_truncate(self, len) < 0)
return NULL;
Py_RETURN_NONE;
}
#endif /* PG_VERSION_HEX >= 0x080300 */
/** the lobject object **/
@ -294,10 +327,10 @@ static struct PyMethodDef lobjectObject_methods[] = {
METH_NOARGS, psyco_lobj_unlink_doc},
{"export",(PyCFunction)psyco_lobj_export,
METH_VARARGS, psyco_lobj_export_doc},
#if PG_VERSION_HEX >= 0x080300
#if PG_VERSION_NUM >= 80300
{"truncate",(PyCFunction)psyco_lobj_truncate,
METH_VARARGS, psyco_lobj_truncate_doc},
#endif /* PG_VERSION_HEX >= 0x080300 */
#endif /* PG_VERSION_NUM >= 80300 */
{NULL}
};
@ -333,11 +366,10 @@ lobject_setup(lobjectObject *self, connectionObject *conn,
return -1;
}
Py_INCREF((PyObject*)conn);
self->conn = conn;
self->mark = conn->mark;
Py_INCREF((PyObject*)self->conn);
self->fd = -1;
self->oid = InvalidOid;
@ -358,8 +390,8 @@ lobject_dealloc(PyObject* obj)
if (self->conn && self->fd != -1) {
if (lobject_close(self) < 0)
PyErr_Print();
Py_XDECREF((PyObject*)self->conn);
}
Py_CLEAR(self->conn);
PyMem_Free(self->smode);
Dprintf("lobject_dealloc: deleted lobject object at %p, refcnt = "
@ -406,7 +438,7 @@ lobject_repr(lobjectObject *self)
PyTypeObject lobjectType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.lobject",
"psycopg2.extensions.lobject",
sizeof(lobjectObject), 0,
lobject_dealloc, /*tp_dealloc*/
0, /*tp_print*/

View File

@ -142,7 +142,7 @@ isqlquote_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
PyTypeObject isqlquoteType = {
PyVarObject_HEAD_INIT(NULL, 0)
"psycopg2._psycopg.ISQLQuote",
"psycopg2.extensions.ISQLQuote",
sizeof(isqlquoteObject), 0,
isqlquote_dealloc, /*tp_dealloc*/
0, /*tp_print*/

View File

@ -113,11 +113,7 @@ exception_from_sqlstate(const char *sqlstate)
case '4':
switch (sqlstate[1]) {
case '0': /* Class 40 - Transaction Rollback */
#ifdef PSYCOPG_EXTENSIONS
return TransactionRollbackError;
#else
return OperationalError;
#endif
case '2': /* Class 42 - Syntax Error or Access Rule Violation */
case '4': /* Class 44 - WITH CHECK OPTION Violation */
return ProgrammingError;
@ -129,11 +125,9 @@ exception_from_sqlstate(const char *sqlstate)
Class 55 - Object Not In Prerequisite State
Class 57 - Operator Intervention
Class 58 - System Error (errors external to PostgreSQL itself) */
#ifdef PSYCOPG_EXTENSIONS
if (!strcmp(sqlstate, "57014"))
return QueryCanceledError;
else
#endif
return OperationalError;
case 'F': /* Class F0 - Configuration File Error */
return InternalError;
@ -196,8 +190,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;
}
@ -893,7 +889,7 @@ pq_flush(connectionObject *conn)
*/
RAISES_NEG int
pq_execute(cursorObject *curs, const char *query, int async, int no_result)
pq_execute(cursorObject *curs, const char *query, int async, int no_result, int no_begin)
{
PGresult *pgres = NULL;
char *error = NULL;
@ -916,7 +912,7 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result)
Py_BEGIN_ALLOW_THREADS;
pthread_mutex_lock(&(curs->conn->lock));
if (pq_begin_locked(curs->conn, &pgres, &error, &_save) < 0) {
if (!no_begin && pq_begin_locked(curs->conn, &pgres, &error, &_save) < 0) {
pthread_mutex_unlock(&(curs->conn->lock));
Py_BLOCK_THREADS;
pq_complete_error(curs->conn, &pgres, &error);
@ -986,6 +982,10 @@ pq_execute(cursorObject *curs, const char *query, int async, int no_result)
}
else {
/* there was an error */
pthread_mutex_unlock(&(curs->conn->lock));
Py_BLOCK_THREADS;
PyErr_SetString(OperationalError,
PQerrorMessage(curs->conn->pgconn));
return -1;
}
}
@ -1288,6 +1288,13 @@ _pq_copy_in_v3(cursorObject *curs)
Py_ssize_t length = 0;
int res, error = 0;
if (!curs->copyfile) {
PyErr_SetString(ProgrammingError,
"can't execute COPY FROM: use the copy_from() method instead");
error = 1;
goto exit;
}
if (!(func = PyObject_GetAttrString(curs->copyfile, "read"))) {
Dprintf("_pq_copy_in_v3: can't get o.read");
error = 1;
@ -1369,9 +1376,27 @@ _pq_copy_in_v3(cursorObject *curs)
res = PQputCopyEnd(curs->conn->pgconn, NULL);
else if (error == 2)
res = PQputCopyEnd(curs->conn->pgconn, "error in PQputCopyData() call");
else
/* XXX would be nice to propagate the exception */
res = PQputCopyEnd(curs->conn->pgconn, "error in .read() call");
else {
char buf[1024];
strcpy(buf, "error in .read() call");
if (PyErr_Occurred()) {
PyObject *t, *ex, *tb;
PyErr_Fetch(&t, &ex, &tb);
if (ex) {
PyObject *str;
str = PyObject_Str(ex);
str = psycopg_ensure_bytes(str);
if (str) {
PyOS_snprintf(buf, sizeof(buf),
"error in .read() call: %s %s",
((PyTypeObject *)t)->tp_name, Bytes_AsString(str));
Py_DECREF(str);
}
}
PyErr_Restore(t, ex, tb);
}
res = PQputCopyEnd(curs->conn->pgconn, buf);
}
CLEARPGRES(curs->pgres);
@ -1411,7 +1436,8 @@ exit:
static int
_pq_copy_out_v3(cursorObject *curs)
{
PyObject *tmp = NULL, *func;
PyObject *tmp = NULL;
PyObject *func = NULL;
PyObject *obj = NULL;
int ret = -1;
int is_text;
@ -1419,6 +1445,12 @@ _pq_copy_out_v3(cursorObject *curs)
char *buffer;
Py_ssize_t len;
if (!curs->copyfile) {
PyErr_SetString(ProgrammingError,
"can't execute COPY TO: use the copy_to() method instead");
goto exit;
}
if (!(func = PyObject_GetAttrString(curs->copyfile, "write"))) {
Dprintf("_pq_copy_out_v3: can't get o.write");
goto exit;
@ -1565,11 +1597,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

View File

@ -36,7 +36,7 @@
HIDDEN PGresult *pq_get_last_result(connectionObject *conn);
RAISES_NEG HIDDEN int pq_fetch(cursorObject *curs, int no_result);
RAISES_NEG HIDDEN int pq_execute(cursorObject *curs, const char *query,
int async, int no_result);
int async, int no_result, int no_begin);
HIDDEN int pq_send_query(connectionObject *conn, const char *query);
HIDDEN int pq_begin_locked(connectionObject *conn, PGresult **pgres,
char **error, PyThreadState **tstate);

View File

@ -63,9 +63,7 @@ HIDDEN psyco_errors_set_RETURN psyco_errors_set psyco_errors_set_PROTO;
extern HIDDEN PyObject *Error, *Warning, *InterfaceError, *DatabaseError,
*InternalError, *OperationalError, *ProgrammingError,
*IntegrityError, *DataError, *NotSupportedError;
#ifdef PSYCOPG_EXTENSIONS
extern HIDDEN PyObject *QueryCanceledError, *TransactionRollbackError;
#endif
/* python versions and compatibility stuff */
#ifndef PyMODINIT_FUNC
@ -164,13 +162,11 @@ STEALS(1) HIDDEN PyObject * psycopg_ensure_text(PyObject *obj);
#define NotSupportedError_doc \
"A method or database API was used which is not supported by the database."
#ifdef PSYCOPG_EXTENSIONS
#define QueryCanceledError_doc \
"Error related to SQL query cancellation."
#define TransactionRollbackError_doc \
"Error causing transaction rollback (deadlocks, serialization failures, etc)."
#endif
#ifdef __cplusplus
}

View File

@ -58,11 +58,6 @@
#include "psycopg/adapter_datetime.h"
HIDDEN PyObject *pyDateTimeModuleP = NULL;
/* pointers to the psycopg.tz classes */
HIDDEN PyObject *pyPsycopgTzModule = NULL;
HIDDEN PyObject *pyPsycopgTzLOCAL = NULL;
HIDDEN PyObject *pyPsycopgTzFixedOffsetTimezone = NULL;
HIDDEN PyObject *psycoEncodings = NULL;
#ifdef PSYCOPG_DEBUG
@ -117,6 +112,115 @@ psyco_connect(PyObject *self, PyObject *args, PyObject *keywds)
return conn;
}
#define psyco_parse_dsn_doc "parse_dsn(dsn) -> dict"
static PyObject *
psyco_parse_dsn(PyObject *self, PyObject *args, PyObject *kwargs)
{
char *err = NULL;
PQconninfoOption *options = NULL, *o;
PyObject *dict = NULL, *res = NULL, *dsn;
static char *kwlist[] = {"dsn", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &dsn)) {
return NULL;
}
Py_INCREF(dsn); /* for ensure_bytes */
if (!(dsn = psycopg_ensure_bytes(dsn))) { goto exit; }
options = PQconninfoParse(Bytes_AS_STRING(dsn), &err);
if (options == NULL) {
if (err != NULL) {
PyErr_Format(ProgrammingError, "error parsing the dsn: %s", err);
PQfreemem(err);
} else {
PyErr_SetString(OperationalError, "PQconninfoParse() failed");
}
goto exit;
}
if (!(dict = PyDict_New())) { goto exit; }
for (o = options; o->keyword != NULL; o++) {
if (o->val != NULL) {
PyObject *value;
if (!(value = Text_FromUTF8(o->val))) { goto exit; }
if (PyDict_SetItemString(dict, o->keyword, value) != 0) {
Py_DECREF(value);
goto exit;
}
Py_DECREF(value);
}
}
/* success */
res = dict;
dict = NULL;
exit:
PQconninfoFree(options); /* safe on null */
Py_XDECREF(dict);
Py_XDECREF(dsn);
return res;
}
#define psyco_quote_ident_doc \
"quote_ident(str, conn_or_curs) -> str -- wrapper around PQescapeIdentifier\n\n" \
":Parameters:\n" \
" * `str`: A bytes or unicode object\n" \
" * `conn_or_curs`: A connection or cursor, required"
static PyObject *
psyco_quote_ident(PyObject *self, PyObject *args, PyObject *kwargs)
{
#if PG_VERSION_NUM >= 90000
PyObject *ident = NULL, *obj = NULL, *result = NULL;
connectionObject *conn;
const char *str;
char *quoted = NULL;
static char *kwlist[] = {"ident", "scope", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO", kwlist, &ident, &obj)) {
return NULL;
}
if (PyObject_TypeCheck(obj, &cursorType)) {
conn = ((cursorObject*)obj)->conn;
}
else if (PyObject_TypeCheck(obj, &connectionType)) {
conn = (connectionObject*)obj;
}
else {
PyErr_SetString(PyExc_TypeError,
"argument 2 must be a connection or a cursor");
return NULL;
}
Py_INCREF(ident); /* for ensure_bytes */
if (!(ident = psycopg_ensure_bytes(ident))) { goto exit; }
str = Bytes_AS_STRING(ident);
quoted = PQescapeIdentifier(conn->pgconn, str, strlen(str));
if (!quoted) {
PyErr_NoMemory();
goto exit;
}
result = conn_text_from_chars(conn, quoted);
exit:
PQfreemem(quoted);
Py_XDECREF(ident);
return result;
#else
PyErr_SetString(NotSupportedError, "PQescapeIdentifier not available in libpq < 9.0");
return NULL;
#endif
}
/** type registration **/
#define psyco_register_type_doc \
"register_type(obj, conn_or_curs) -> None -- register obj with psycopg type system\n\n" \
@ -181,6 +285,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_NUM >= 80400
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.
@ -285,6 +412,19 @@ exit:
return rv;
}
#define psyco_libpq_version_doc "Query actual libpq version loaded."
static PyObject*
psyco_libpq_version(PyObject *self)
{
#if PG_VERSION_NUM >= 90100
return PyInt_FromLong(PQlibVersion());
#else
PyErr_SetString(NotSupportedError, "version discovery is not supported in libpq < 9.1");
return NULL;
#endif
}
/* psyco_encodings_fill
Fill the module's postgresql<->python encoding table */
@ -398,9 +538,7 @@ exit:
PyObject *Error, *Warning, *InterfaceError, *DatabaseError,
*InternalError, *OperationalError, *ProgrammingError,
*IntegrityError, *DataError, *NotSupportedError;
#ifdef PSYCOPG_EXTENSIONS
PyObject *QueryCanceledError, *TransactionRollbackError;
#endif
/* mapping between exception names and their PyObject */
static struct {
@ -423,13 +561,11 @@ static struct {
{ "psycopg2.DataError", &DataError, &DatabaseError, DataError_doc },
{ "psycopg2.NotSupportedError", &NotSupportedError, &DatabaseError,
NotSupportedError_doc },
#ifdef PSYCOPG_EXTENSIONS
{ "psycopg2.extensions.QueryCanceledError", &QueryCanceledError,
&OperationalError, QueryCanceledError_doc },
{ "psycopg2.extensions.TransactionRollbackError",
&TransactionRollbackError, &OperationalError,
TransactionRollbackError_doc },
#endif
{NULL} /* Sentinel */
};
@ -630,8 +766,10 @@ psyco_GetDecimalType(void)
static PyObject *
psyco_make_description_type(void)
{
PyObject *nt = NULL;
PyObject *coll = NULL;
PyObject *nt = NULL;
PyTypeObject *t = NULL;
PyObject *s = NULL;
PyObject *rv = NULL;
/* Try to import collections.namedtuple */
@ -645,12 +783,26 @@ psyco_make_description_type(void)
}
/* Build the namedtuple */
rv = PyObject_CallFunction(nt, "ss", "Column",
"name type_code display_size internal_size precision scale null_ok");
if(!(t = (PyTypeObject *)PyObject_CallFunction(nt, "ss", "Column",
"name type_code display_size internal_size precision scale null_ok"))) {
goto exit;
}
/* Export the tuple on the extensions module
* Required to guarantee picklability on Py > 3.3 (see Python issue 21374)
* for previous Py version the module is psycopg2 anyway but for consistency
* we'd rather expose it from the extensions module. */
if (!(s = Text_FromUTF8("psycopg2.extensions"))) { goto exit; }
if (0 > PyDict_SetItemString(t->tp_dict, "__module__", s)) { goto exit; }
rv = (PyObject *)t;
t = NULL;
exit:
Py_XDECREF(coll);
Py_XDECREF(nt);
Py_XDECREF((PyObject *)t);
Py_XDECREF(s);
return rv;
@ -668,6 +820,10 @@ error:
static PyMethodDef psycopgMethods[] = {
{"_connect", (PyCFunction)psyco_connect,
METH_VARARGS|METH_KEYWORDS, psyco_connect_doc},
{"parse_dsn", (PyCFunction)psyco_parse_dsn,
METH_VARARGS|METH_KEYWORDS, psyco_parse_dsn_doc},
{"quote_ident", (PyCFunction)psyco_quote_ident,
METH_VARARGS|METH_KEYWORDS, psyco_quote_ident_doc},
{"adapt", (PyCFunction)psyco_microprotocols_adapt,
METH_VARARGS, psyco_microprotocols_adapt_doc},
@ -677,21 +833,9 @@ static PyMethodDef psycopgMethods[] = {
METH_VARARGS|METH_KEYWORDS, typecast_from_python_doc},
{"new_array_type", (PyCFunction)typecast_array_from_python,
METH_VARARGS|METH_KEYWORDS, typecast_array_from_python_doc},
{"libpq_version", (PyCFunction)psyco_libpq_version,
METH_NOARGS, psyco_libpq_version_doc},
{"AsIs", (PyCFunction)psyco_AsIs,
METH_VARARGS, psyco_AsIs_doc},
{"QuotedString", (PyCFunction)psyco_QuotedString,
METH_VARARGS, psyco_QuotedString_doc},
{"Boolean", (PyCFunction)psyco_Boolean,
METH_VARARGS, psyco_Boolean_doc},
{"Int", (PyCFunction)psyco_Int,
METH_VARARGS, psyco_Int_doc},
{"Float", (PyCFunction)psyco_Float,
METH_VARARGS, psyco_Float_doc},
{"Decimal", (PyCFunction)psyco_Decimal,
METH_VARARGS, psyco_Decimal_doc},
{"Binary", (PyCFunction)psyco_Binary,
METH_VARARGS, psyco_Binary_doc},
{"Date", (PyCFunction)psyco_Date,
METH_VARARGS, psyco_Date_doc},
{"Time", (PyCFunction)psyco_Time,
@ -704,8 +848,6 @@ static PyMethodDef psycopgMethods[] = {
METH_VARARGS, psyco_TimeFromTicks_doc},
{"TimestampFromTicks", (PyCFunction)psyco_TimestampFromTicks,
METH_VARARGS, psyco_TimestampFromTicks_doc},
{"List", (PyCFunction)psyco_List,
METH_VARARGS, psyco_List_doc},
{"DateFromPy", (PyCFunction)psyco_DateFromPy,
METH_VARARGS, psyco_DateFromPy_doc},
@ -728,12 +870,10 @@ static PyMethodDef psycopgMethods[] = {
METH_VARARGS, psyco_IntervalFromMx_doc},
#endif
#ifdef PSYCOPG_EXTENSIONS
{"set_wait_callback", (PyCFunction)psyco_set_wait_callback,
METH_O, psyco_set_wait_callback_doc},
{"get_wait_callback", (PyCFunction)psyco_get_wait_callback,
METH_NOARGS, psyco_get_wait_callback_doc},
#endif
{NULL, NULL, 0, NULL} /* Sentinel */
};
@ -822,10 +962,11 @@ INIT_MODULE(_psycopg)(void)
Py_TYPE(&diagnosticsType) = &PyType_Type;
if (PyType_Ready(&diagnosticsType) == -1) goto exit;
#ifdef PSYCOPG_EXTENSIONS
Py_TYPE(&lobjectType) = &PyType_Type;
if (PyType_Ready(&lobjectType) == -1) goto exit;
#endif
/* initialize libcrypto threading callbacks */
psyco_libcrypto_threads_init();
/* import mx.DateTime module, if necessary */
#ifdef HAVE_MXDATETIME
@ -859,18 +1000,6 @@ INIT_MODULE(_psycopg)(void)
Py_TYPE(&pydatetimeType) = &PyType_Type;
if (PyType_Ready(&pydatetimeType) == -1) goto exit;
/* import psycopg2.tz anyway (TODO: replace with C-level module?) */
pyPsycopgTzModule = PyImport_ImportModule("psycopg2.tz");
if (pyPsycopgTzModule == NULL) {
Dprintf("initpsycopg: can't import psycopg2.tz module");
PyErr_SetString(PyExc_ImportError, "can't import psycopg2.tz module");
goto exit;
}
pyPsycopgTzLOCAL =
PyObject_GetAttrString(pyPsycopgTzModule, "LOCAL");
pyPsycopgTzFixedOffsetTimezone =
PyObject_GetAttrString(pyPsycopgTzModule, "FixedOffsetTimezone");
/* initialize the module and grab module's dictionary */
#if PY_MAJOR_VERSION < 3
module = Py_InitModule("_psycopg", psycopgMethods);
@ -901,6 +1030,7 @@ INIT_MODULE(_psycopg)(void)
/* set some module's parameters */
PyModule_AddStringConstant(module, "__version__", PSYCOPG_VERSION);
PyModule_AddStringConstant(module, "__doc__", "psycopg PostgreSQL driver");
PyModule_AddIntConstant(module, "__libpq_version__", PG_VERSION_NUM);
PyModule_AddObject(module, "apilevel", Text_FromUTF8(APILEVEL));
PyModule_AddObject(module, "threadsafety", PyInt_FromLong(THREADSAFETY));
PyModule_AddObject(module, "paramstyle", Text_FromUTF8(PARAMSTYLE));
@ -912,9 +1042,16 @@ INIT_MODULE(_psycopg)(void)
PyModule_AddObject(module, "Notify", (PyObject*)&notifyType);
PyModule_AddObject(module, "Xid", (PyObject*)&xidType);
PyModule_AddObject(module, "Diagnostics", (PyObject*)&diagnosticsType);
#ifdef PSYCOPG_EXTENSIONS
PyModule_AddObject(module, "AsIs", (PyObject*)&asisType);
PyModule_AddObject(module, "Binary", (PyObject*)&binaryType);
PyModule_AddObject(module, "Boolean", (PyObject*)&pbooleanType);
PyModule_AddObject(module, "Decimal", (PyObject*)&pdecimalType);
PyModule_AddObject(module, "Int", (PyObject*)&pintType);
PyModule_AddObject(module, "Float", (PyObject*)&pfloatType);
PyModule_AddObject(module, "List", (PyObject*)&listType);
PyModule_AddObject(module, "QuotedString", (PyObject*)&qstringType);
PyModule_AddObject(module, "lobject", (PyObject*)&lobjectType);
#endif
PyModule_AddObject(module, "Column", psyco_DescriptionType);
/* encodings dictionary in module dictionary */
PyModule_AddObject(module, "encodings", psycoEncodings);

View File

@ -164,6 +164,9 @@ typecast_parse_time(const char* s, const char** t, Py_ssize_t* len,
while (usd++ < 6) *us *= 10;
}
/* 24:00:00 -> 00:00:00 (ticket #278) */
if (*hh == 24) { *hh = 0; }
return cz;
}

View File

@ -62,7 +62,7 @@ psycopg_escape_string(connectionObject *conn, const char *from, Py_ssize_t len,
}
{
#if PG_VERSION_HEX >= 0x080104
#if PG_VERSION_NUM >= 80104
int err;
if (conn && conn->pgconn)
ql = PQescapeStringConn(conn->pgconn, to+eq+1, from, len, &err);
@ -87,7 +87,7 @@ psycopg_escape_string(connectionObject *conn, const char *from, Py_ssize_t len,
return to;
}
/* Escape a string to build a valid PostgreSQL identifier
/* Escape a string to build a valid PostgreSQL identifier.
*
* Allocate a new buffer on the Python heap containing the new string.
* 'len' is optional: if 0 the length is calculated.
@ -96,7 +96,7 @@ psycopg_escape_string(connectionObject *conn, const char *from, Py_ssize_t len,
*
* WARNING: this function is not so safe to allow untrusted input: it does no
* check for multibyte chars. Such a function should be built on
* PQescapeIndentifier, which is only available from PostgreSQL 9.0.
* PQescapeIdentifier, which is only available from PostgreSQL 9.0.
*/
char *
psycopg_escape_identifier_easy(const char *from, Py_ssize_t len)

View File

@ -44,10 +44,9 @@
<None Include="INSTALL" />
<None Include="LICENSE" />
<None Include="MANIFEST.in" />
<None Include="README" />
<None Include="README.rst" />
<None Include="setup.cfg" />
<None Include="setup.py" />
<None Include="doc\HACKING" />
<None Include="doc\SUCCESS" />
<None Include="examples\binary.py" />
<None Include="examples\copy_from.py" />

View File

@ -16,6 +16,7 @@ The script can be run at a new PostgreSQL release to refresh the module.
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
import re
import sys
import urllib2
from collections import defaultdict
@ -30,8 +31,9 @@ def main():
filename = sys.argv[1]
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'])
['8.1', '8.2', '8.3', '8.4', '9.0', '9.1', '9.2', '9.3', '9.4'])
f = open(filename, "w")
for line in file_start:
@ -48,7 +50,41 @@ def read_base_file(filename):
raise ValueError("can't find the separator. Is this the right file?")
def parse_errors(url):
def parse_errors_txt(url):
classes = {}
errors = defaultdict(dict)
page = urllib2.urlopen(url)
for line in page:
# Strip comments and skip blanks
line = line.split('#')[0].strip()
if not line:
continue
# Parse a section
m = re.match(r"Section: (Class (..) - .+)", line)
if m:
label, class_ = m.groups()
classes[class_] = label
continue
# Parse an error
m = re.match(r"(.....)\s+(?:E|W|S)\s+ERRCODE_(\S+)(?:\s+(\S+))?$", line)
if m:
errcode, macro, spec = m.groups()
# skip errcodes without specs as they are not publically visible
if not spec:
continue
errlabel = spec.upper()
errors[class_][errcode] = errlabel
continue
# We don't expect anything else
raise ValueError("unexpected line:\n%s" % line)
return classes, errors
def parse_errors_sgml(url):
page = BS(urllib2.urlopen(url))
table = page('table')[1]('tbody')[0]
@ -87,14 +123,25 @@ def parse_errors(url):
return classes, errors
errors_url="http://www.postgresql.org/docs/%s/static/errcodes-appendix.html"
errors_sgml_url = \
"http://www.postgresql.org/docs/%s/static/errcodes-appendix.html"
errors_txt_url = \
"http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob_plain;" \
"f=src/backend/utils/errcodes.txt;hb=REL%s_STABLE"
def fetch_errors(versions):
classes = {}
errors = defaultdict(dict)
for version in versions:
c1, e1 = parse_errors(errors_url % version)
print >> sys.stderr, version
tver = tuple(map(int, version.split('.')))
if tver < (9, 1):
c1, e1 = parse_errors_sgml(errors_sgml_url % version)
else:
c1, e1 = parse_errors_txt(
errors_txt_url % version.replace('.', '_'))
classes.update(c1)
for c, cerrs in e1.iteritems():
errors[c].update(cerrs)

15
scripts/upload-docs.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
DOCDIR="$DIR/../doc"
# this command requires ssh configured to the proper target
tar czf - -C "$DOCDIR/html" . | ssh psycoweb tar xzvf - -C docs/current
# download the script to upload the docs to PyPI
test -e "$DIR/pypi_docs_upload.py" \
|| wget -O "$DIR/pypi_docs_upload.py" \
https://gist.githubusercontent.com/dvarrazzo/dac46237070d69dbc075/raw
# this command requires a ~/.pypirc with the right privileges
python "$DIR/pypi_docs_upload.py" psycopg2 "$DOCDIR/html"

54
scripts/upload-release.sh Executable file
View File

@ -0,0 +1,54 @@
#!/bin/bash
# Script to create a psycopg release
#
# You must create a release tag before running the script, e.g. 2_5_4.
# The script will check out in a clear environment, build the sdist package,
# unpack and test it, then upload on PyPI and on the psycopg website.
set -e
REPO_URL=git@github.com:psycopg/psycopg2.git
VER=$(grep ^PSYCOPG_VERSION setup.py | cut -d "'" -f 2)
# avoid releasing a testing version
echo "$VER" | grep -qE '^[0-9]+\.[0-9]+(\.[0-9]+)?$' \
|| (echo "bad release: $VER" >&2 && exit 1)
# Check out in a clean environment
rm -rf rel
mkdir rel
cd rel
git clone $REPO_URL psycopg
cd psycopg
TAG=${VER//./_}
git checkout -b $TAG $TAG
make sdist
# Test the sdist just created
cd dist
tar xzvf psycopg2-$VER.tar.gz
cd psycopg2-$VER
make
make check
cd ../../
read -p "if you are not fine with the above stop me now..."
# upload to pypi and to the website
python setup.py sdist --formats=gztar upload -s
DASHVER=${VER//./-}
DASHVER=${DASHVER:0:3}
# Requires ssh configuration for 'psycoweb'
scp dist/psycopg2-${VER}.tar.gz psycoweb:tarballs/PSYCOPG-${DASHVER}/
ssh psycoweb ln -sfv PSYCOPG-${DASHVER}/psycopg2-${VER}.tar.gz \
tarballs/psycopg2-latest.tar.gz
scp dist/psycopg2-${VER}.tar.gz.asc psycoweb:tarballs/PSYCOPG-${DASHVER}/
ssh psycoweb ln -sfv PSYCOPG-${DASHVER}/psycopg2-${VER}.tar.gz.asc \
tarballs/psycopg2-latest.tar.gz.asc
echo "great, now write release notes and an email!"

View File

@ -1,11 +1,9 @@
[build_ext]
define=PSYCOPG_EXTENSIONS,PSYCOPG_NEW_BOOLEAN,HAVE_PQFREEMEM
define=
# PSYCOPG_EXTENSIONS enables extensions to PEP-249 (you really want this)
# PSYCOPG_DISPLAY_SIZE enable display size calculation (a little slower)
# HAVE_PQFREEMEM should be defined on PostgreSQL >= 7.4
# PSYCOPG_DEBUG can be added to enable verbose debug information
# PSYCOPG_NEW_BOOLEAN to format booleans as true/false vs 't'/'f'
# "pg_config" is required to locate PostgreSQL headers and libraries needed to
# build psycopg2. If pg_config is not in the path or is installed under a

View File

@ -25,6 +25,8 @@ UPDATEs. psycopg2 also provide full asynchronous operations and support
for coroutine libraries.
"""
# note: if you are changing the list of supported Python version please fix
# the docs in install.rst and the /features/ page on the website.
classifiers = """\
Development Status :: 5 - Production/Stable
Intended Audience :: Developers
@ -84,7 +86,7 @@ except ImportError:
# Take a look at http://www.python.org/dev/peps/pep-0386/
# for a consistent versioning pattern.
PSYCOPG_VERSION = '2.6.dev0'
PSYCOPG_VERSION = '2.7.dev0'
version_flags = ['dt', 'dec']
@ -405,14 +407,32 @@ class psycopg_build_ext(build_ext):
pgmajor, pgminor, pgpatch = m.group(1, 2, 3)
if pgpatch is None or not pgpatch.isdigit():
pgpatch = 0
pgmajor = int(pgmajor)
pgminor = int(pgminor)
pgpatch = int(pgpatch)
else:
sys.stderr.write(
"Error: could not determine PostgreSQL version from '%s'"
% pgversion)
sys.exit(1)
define_macros.append(("PG_VERSION_HEX", "0x%02X%02X%02X" %
(int(pgmajor), int(pgminor), int(pgpatch))))
define_macros.append(("PG_VERSION_NUM", "%d%02d%02d" %
(pgmajor, pgminor, pgpatch)))
# enable lo64 if libpq >= 9.3 and Python 64 bits
if (pgmajor, pgminor) >= (9, 3) and is_py_64():
define_macros.append(("HAVE_LO64", "1"))
# Inject the flag in the version string already packed up
# because we didn't know the version before.
# With distutils everything is complicated.
for i, t in enumerate(define_macros):
if t[0] == 'PSYCOPG_VERSION':
n = t[1].find(')')
if n > 0:
define_macros[i] = (
t[0], t[1][:n] + ' lo64' + t[1][n:])
except Warning:
w = sys.exc_info()[1] # work around py 2/3 different syntax
sys.stderr.write("Error: %s\n" % w)
@ -421,6 +441,13 @@ class psycopg_build_ext(build_ext):
if hasattr(self, "finalize_" + sys.platform):
getattr(self, "finalize_" + sys.platform)()
def is_py_64():
# sys.maxint not available since Py 3.1;
# sys.maxsize not available before Py 2.6;
# this is portable at least between Py 2.4 and 3.4.
import struct
return struct.calcsize("P") > 4
# let's start with macro definitions (the ones not already in setup.cfg)
define_macros = []
@ -509,10 +536,7 @@ you probably need to install its companion -dev or -devel package."""
# generate a nice version string to avoid confusion when users report bugs
version_flags.append('pq3') # no more a choice
for have in parser.get('build_ext', 'define').split(','):
if have == 'PSYCOPG_EXTENSIONS':
version_flags.append('ext')
version_flags.append('ext') # no more a choice
if version_flags:
PSYCOPG_VERSION_EX = PSYCOPG_VERSION + " (%s)" % ' '.join(version_flags)
@ -539,7 +563,8 @@ else:
# when called e.g. with "pip -e git+url'. This results in declarations
# duplicate on the commandline, which I hope is not a problem.
for define in parser.get('build_ext', 'define').split(','):
define_macros.append((define, '1'))
if define:
define_macros.append((define, '1'))
# build the extension
@ -560,6 +585,14 @@ download_url = (
"http://initd.org/psycopg/tarballs/PSYCOPG-%s/psycopg2-%s.tar.gz"
% ('-'.join(PSYCOPG_VERSION.split('.')[:2]), PSYCOPG_VERSION))
try:
f = open("README.rst")
readme = f.read()
f.close()
except:
print("failed to read readme: ignoring...")
readme = __doc__
setup(name="psycopg2",
version=PSYCOPG_VERSION,
maintainer="Federico Di Gregorio",
@ -570,8 +603,8 @@ setup(name="psycopg2",
download_url=download_url,
license="LGPL with exceptions or ZPL",
platforms=["any"],
description=__doc__.split("\n")[0],
long_description="\n".join(__doc__.split("\n")[2:]),
description=readme.split("\n")[0],
long_description="\n".join(readme.split("\n")[2:]).lstrip(),
classifiers=[x for x in classifiers.split("\n") if x],
data_files=data_files,
package_dir={'psycopg2': 'lib', 'psycopg2.tests': 'tests'},

View File

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

View File

@ -23,16 +23,18 @@
# License for more details.
import os
import sys
import time
import threading
from operator import attrgetter
from StringIO import StringIO
import psycopg2
import psycopg2.errorcodes
import psycopg2.extensions
from testutils import unittest, decorate_all_tests, skip_if_no_superuser
from testutils import skip_before_postgres, skip_after_postgres
from testutils import skip_before_postgres, skip_after_postgres, skip_before_libpq
from testutils import ConnectingTestCase, skip_if_tpc_disabled
from testutils import skip_if_windows
from testconfig import dsn, dbname
@ -127,11 +129,45 @@ class ConnectionTests(ConnectingTestCase):
cur.execute(sql)
self.assertEqual(50, len(conn.notices))
self.assert_('table50' in conn.notices[0], conn.notices[0])
self.assert_('table51' in conn.notices[1], conn.notices[1])
self.assert_('table98' in conn.notices[-2], conn.notices[-2])
self.assert_('table99' in conn.notices[-1], conn.notices[-1])
def test_notices_deque(self):
from collections import deque
conn = self.conn
self.conn.notices = deque()
cur = conn.cursor()
if self.conn.server_version >= 90300:
cur.execute("set client_min_messages=debug1")
cur.execute("create temp table table1 (id serial); create temp table table2 (id serial);")
cur.execute("create temp table table3 (id serial); create temp table table4 (id serial);")
self.assertEqual(len(conn.notices), 4)
self.assert_('table1' in conn.notices.popleft())
self.assert_('table2' in conn.notices.popleft())
self.assert_('table3' in conn.notices.popleft())
self.assert_('table4' in conn.notices.popleft())
self.assertEqual(len(conn.notices), 0)
# not limited, but no error
for i in range(0, 100, 10):
sql = " ".join(["create temp table table2_%d (id serial);" % j for j in range(i, i+10)])
cur.execute(sql)
self.assertEqual(len([n for n in conn.notices if 'CREATE TABLE' in n]),
100)
def test_notices_noappend(self):
conn = self.conn
self.conn.notices = None # will make an error swallowes ok
cur = conn.cursor()
if self.conn.server_version >= 90300:
cur.execute("set client_min_messages=debug1")
cur.execute("create temp table table1 (id serial);")
self.assertEqual(self.conn.notices, None)
def test_server_version(self):
self.assert_(self.conn.server_version)
@ -274,6 +310,78 @@ class ConnectionTests(ConnectingTestCase):
self.assert_('foobar' not in c.dsn, "password was not obscured")
class ParseDsnTestCase(ConnectingTestCase):
def test_parse_dsn(self):
from psycopg2 import ProgrammingError
from psycopg2.extensions import parse_dsn
self.assertEqual(parse_dsn('dbname=test user=tester password=secret'),
dict(user='tester', password='secret', dbname='test'),
"simple DSN parsed")
self.assertRaises(ProgrammingError, parse_dsn,
"dbname=test 2 user=tester password=secret")
self.assertEqual(parse_dsn("dbname='test 2' user=tester password=secret"),
dict(user='tester', password='secret', dbname='test 2'),
"DSN with quoting parsed")
# Can't really use assertRaisesRegexp() here since we need to
# make sure that secret is *not* exposed in the error messgage
# (and it also requires python >= 2.7).
raised = False
try:
# unterminated quote after dbname:
parse_dsn("dbname='test 2 user=tester password=secret")
except ProgrammingError, e:
raised = True
self.assertTrue(str(e).find('secret') < 0,
"DSN was not exposed in error message")
except e:
self.fail("unexpected error condition: " + repr(e))
self.assertTrue(raised, "ProgrammingError raised due to invalid DSN")
@skip_before_libpq(9, 2)
def test_parse_dsn_uri(self):
from psycopg2.extensions import parse_dsn
self.assertEqual(parse_dsn('postgresql://tester:secret@/test'),
dict(user='tester', password='secret', dbname='test'),
"valid URI dsn parsed")
raised = False
try:
# extra '=' after port value
parse_dsn(dsn='postgresql://tester:secret@/test?port=1111=x')
except psycopg2.ProgrammingError, e:
raised = True
self.assertTrue(str(e).find('secret') < 0,
"URI was not exposed in error message")
except e:
self.fail("unexpected error condition: " + repr(e))
self.assertTrue(raised, "ProgrammingError raised due to invalid URI")
def test_unicode_value(self):
from psycopg2.extensions import parse_dsn
snowman = u"\u2603"
d = parse_dsn('dbname=' + snowman)
if sys.version_info[0] < 3:
self.assertEqual(d['dbname'], snowman.encode('utf8'))
else:
self.assertEqual(d['dbname'], snowman)
def test_unicode_key(self):
from psycopg2.extensions import parse_dsn
snowman = u"\u2603"
self.assertRaises(psycopg2.ProgrammingError, parse_dsn,
snowman + '=' + snowman)
def test_bad_param(self):
from psycopg2.extensions import parse_dsn
self.assertRaises(TypeError, parse_dsn, None)
self.assertRaises(TypeError, parse_dsn, 42)
class IsolationLevelsTestCase(ConnectingTestCase):
def setUp(self):
@ -1070,6 +1178,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__)

View File

@ -28,10 +28,13 @@ from testutils import unittest, ConnectingTestCase, decorate_all_tests
from testutils import skip_if_no_iobase, skip_before_postgres
from cStringIO import StringIO
from itertools import cycle, izip
from subprocess import Popen, PIPE
import psycopg2
import psycopg2.extensions
from testutils import skip_copy_if_green
from testutils import skip_copy_if_green, script_to_py3
from testconfig import dsn
if sys.version_info[0] < 3:
_base = object
@ -301,6 +304,69 @@ class CopyTests(ConnectingTestCase):
curs.copy_from, StringIO('aaa\nbbb\nccc\n'), 'tcopy')
self.assertEqual(curs.rowcount, -1)
def test_copy_from_segfault(self):
# issue #219
script = ("""\
import psycopg2
conn = psycopg2.connect(%(dsn)r)
curs = conn.cursor()
curs.execute("create table copy_segf (id int)")
try:
curs.execute("copy copy_segf from stdin")
except psycopg2.ProgrammingError:
pass
conn.close()
""" % { 'dsn': dsn,})
proc = Popen([sys.executable, '-c', script_to_py3(script)])
proc.communicate()
self.assertEqual(0, proc.returncode)
def test_copy_to_segfault(self):
# issue #219
script = ("""\
import psycopg2
conn = psycopg2.connect(%(dsn)r)
curs = conn.cursor()
curs.execute("create table copy_segf (id int)")
try:
curs.execute("copy copy_segf to stdout")
except psycopg2.ProgrammingError:
pass
conn.close()
""" % { 'dsn': dsn,})
proc = Popen([sys.executable, '-c', script_to_py3(script)], stdout=PIPE)
proc.communicate()
self.assertEqual(0, proc.returncode)
def test_copy_from_propagate_error(self):
class BrokenRead(_base):
def read(self, size):
return 1/0
def readline(self):
return 1/0
curs = self.conn.cursor()
# It seems we cannot do this, but now at least we propagate the error
# self.assertRaises(ZeroDivisionError,
# curs.copy_from, BrokenRead(), "tcopy")
try:
curs.copy_from(BrokenRead(), "tcopy")
except Exception, e:
self.assert_('ZeroDivisionError' in str(e))
def test_copy_to_propagate_error(self):
class BrokenWrite(_base):
def write(self, data):
return 1/0
curs = self.conn.cursor()
curs.execute("insert into tcopy values (10, 'hi')")
self.assertRaises(ZeroDivisionError,
curs.copy_to, BrokenWrite(), "tcopy")
decorate_all_tests(CopyTests, skip_copy_if_green)

View File

@ -23,6 +23,7 @@
# License for more details.
import time
import pickle
import psycopg2
import psycopg2.extensions
from psycopg2.extensions import b
@ -176,10 +177,7 @@ class CursorTests(ConnectingTestCase):
curs.execute("select data from invname order by data")
self.assertEqual(curs.fetchall(), [(10,), (20,), (30,)])
def test_withhold(self):
self.assertRaises(psycopg2.ProgrammingError, self.conn.cursor,
withhold=True)
def _create_withhold_table(self):
curs = self.conn.cursor()
try:
curs.execute("drop table withhold")
@ -190,6 +188,11 @@ class CursorTests(ConnectingTestCase):
curs.execute("insert into withhold values (%s)", (i,))
curs.close()
def test_withhold(self):
self.assertRaises(psycopg2.ProgrammingError, self.conn.cursor,
withhold=True)
self._create_withhold_table()
curs = self.conn.cursor("W")
self.assertEqual(curs.withhold, False);
curs.withhold = True
@ -209,6 +212,52 @@ class CursorTests(ConnectingTestCase):
curs.execute("drop table withhold")
self.conn.commit()
def test_withhold_no_begin(self):
self._create_withhold_table()
curs = self.conn.cursor("w", withhold=True)
curs.execute("select data from withhold order by data")
self.assertEqual(curs.fetchone(), (10,))
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_BEGIN)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_INTRANS)
self.conn.commit()
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
self.assertEqual(curs.fetchone(), (20,))
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
curs.close()
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
def test_withhold_autocommit(self):
self._create_withhold_table()
self.conn.commit()
self.conn.autocommit = True
curs = self.conn.cursor("w", withhold=True)
curs.execute("select data from withhold order by data")
self.assertEqual(curs.fetchone(), (10,))
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
self.conn.commit()
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
curs.close()
self.assertEqual(self.conn.status, psycopg2.extensions.STATUS_READY)
self.assertEqual(self.conn.get_transaction_status(),
psycopg2.extensions.TRANSACTION_STATUS_IDLE)
def test_scrollable(self):
self.assertRaises(psycopg2.ProgrammingError, self.conn.cursor,
scrollable=True)
@ -352,6 +401,16 @@ class CursorTests(ConnectingTestCase):
self.assertEqual(c.precision, None)
self.assertEqual(c.scale, None)
def test_pickle_description(self):
curs = self.conn.cursor()
curs.execute('SELECT 1 AS foo')
description = curs.description
pickled = pickle.dumps(description, pickle.HIGHEST_PROTOCOL)
unpickled = pickle.loads(pickled)
self.assertEqual(description, unpickled)
@skip_before_postgres(8, 0)
def test_named_cursor_stealing(self):
# you can use a named cursor to iterate on a refcursor created
@ -427,6 +486,10 @@ class CursorTests(ConnectingTestCase):
self.assertRaises(psycopg2.InterfaceError, cur.executemany,
'select 1', [])
def test_callproc_badparam(self):
cur = self.conn.cursor()
self.assertRaises(TypeError, cur.callproc, 'lower', 42)
# It would be inappropriate to test callproc's named parameters in the
# DBAPI2.0 test section because they are a psycopg2 extension.
@skip_before_postgres(9, 0)
@ -465,10 +528,6 @@ class CursorTests(ConnectingTestCase):
self.assertRaises(exception, cur.callproc, procname, parameter_sequence)
self.conn.rollback()
def test_callproc_badparam(self):
cur = self.conn.cursor()
self.assertRaises(TypeError, cur.callproc, 'lower', 42)
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)

View File

@ -25,7 +25,7 @@
import math
import psycopg2
from psycopg2.tz import FixedOffsetTimezone, ZERO
from testutils import unittest, ConnectingTestCase
from testutils import unittest, ConnectingTestCase, skip_before_postgres
class CommonDatetimeTestsMixin:
@ -287,7 +287,17 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
def test_type_roundtrip_time(self):
from datetime import time
self._test_type_roundtrip(time(10,20,30))
tm = self._test_type_roundtrip(time(10,20,30))
self.assertEqual(None, tm.tzinfo)
def test_type_roundtrip_timetz(self):
from datetime import time
import psycopg2.tz
tz = psycopg2.tz.FixedOffsetTimezone(8*60)
tm1 = time(10,20,30, tzinfo=tz)
tm2 = self._test_type_roundtrip(tm1)
self.assertNotEqual(None, tm2.tzinfo)
self.assertEqual(tm1, tm2)
def test_type_roundtrip_interval(self):
from datetime import timedelta
@ -309,6 +319,19 @@ class DatetimeTests(ConnectingTestCase, CommonDatetimeTestsMixin):
from datetime import timedelta
self._test_type_roundtrip_array(timedelta(seconds=30))
@skip_before_postgres(8, 1)
def test_time_24(self):
from datetime import time
t = self.execute("select '24:00'::time;")
self.assertEqual(t, time(0, 0))
t = self.execute("select '24:00+05'::timetz;")
self.assertEqual(t, time(0, 0, tzinfo=FixedOffsetTimezone(300)))
t = self.execute("select '24:00+05:30'::timetz;")
self.assertEqual(t, time(0, 0, tzinfo=FixedOffsetTimezone(330)))
# Only run the datetime tests if psycopg was compiled with support.
if not hasattr(psycopg2.extensions, 'PYDATETIME'):

View File

@ -440,6 +440,68 @@ decorate_all_tests(LargeObjectTruncateTests,
skip_if_no_lo, skip_lo_if_green, skip_if_no_truncate)
def _has_lo64(conn):
"""Return (bool, msg) about the lo64 support"""
if conn.server_version < 90300:
return (False, "server version %s doesn't support the lo64 API"
% conn.server_version)
if 'lo64' not in psycopg2.__version__:
return (False, "this psycopg build doesn't support the lo64 API")
return (True, "this server and build support the lo64 API")
def skip_if_no_lo64(f):
@wraps(f)
def skip_if_no_lo64_(self):
lo64, msg = _has_lo64(self.conn)
if not lo64: return self.skipTest(msg)
else: return f(self)
return skip_if_no_lo64_
class LargeObject64Tests(LargeObjectTestCase):
def test_seek_tell_truncate_greater_than_2gb(self):
lo = self.conn.lobject()
length = (1 << 31) + (1 << 30) # 2gb + 1gb = 3gb
lo.truncate(length)
self.assertEqual(lo.seek(length, 0), length)
self.assertEqual(lo.tell(), length)
decorate_all_tests(LargeObject64Tests,
skip_if_no_lo, skip_lo_if_green, skip_if_no_truncate, skip_if_no_lo64)
def skip_if_lo64(f):
@wraps(f)
def skip_if_lo64_(self):
lo64, msg = _has_lo64(self.conn)
if lo64: return self.skipTest(msg)
else: return f(self)
return skip_if_lo64_
class LargeObjectNot64Tests(LargeObjectTestCase):
def test_seek_larger_than_2gb(self):
lo = self.conn.lobject()
offset = 1 << 32 # 4gb
self.assertRaises(
(OverflowError, psycopg2.InterfaceError, psycopg2.NotSupportedError),
lo.seek, offset, 0)
def test_truncate_larger_than_2gb(self):
lo = self.conn.lobject()
length = 1 << 32 # 4gb
self.assertRaises(
(OverflowError, psycopg2.InterfaceError, psycopg2.NotSupportedError),
lo.truncate, length)
decorate_all_tests(LargeObjectNot64Tests,
skip_if_no_lo, skip_lo_if_green, skip_if_no_truncate, skip_if_lo64)
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)

View File

@ -22,8 +22,12 @@
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.
import os
import sys
from subprocess import Popen
from testutils import unittest, skip_before_python, skip_before_postgres
from testutils import ConnectingTestCase, skip_copy_if_green
from testutils import ConnectingTestCase, skip_copy_if_green, script_to_py3
import psycopg2
@ -295,6 +299,36 @@ class ExceptionsTestCase(ConnectingTestCase):
self.assert_(e1.cursor is None)
class TestExtensionModule(unittest.TestCase):
def test_import_internal(self):
# check that the internal package can be imported "naked"
# we may break this property if there is a compelling reason to do so,
# however having it allows for some import juggling such as the one
# required in ticket #201.
pkgdir = os.path.dirname(psycopg2.__file__)
pardir = os.path.dirname(pkgdir)
self.assert_(pardir in sys.path)
script = ("""
import sys
sys.path.remove(%r)
sys.path.insert(0, %r)
import _psycopg
""" % (pardir, pkgdir))
proc = Popen([sys.executable, '-c', script_to_py3(script)])
proc.communicate()
self.assertEqual(0, proc.returncode)
class TestVersionDiscovery(unittest.TestCase):
def test_libpq_version(self):
self.assertTrue(type(psycopg2.__libpq_version__) is int)
try:
self.assertTrue(type(psycopg2.extensions.libpq_version()) is int)
except NotSupportedError:
self.assertTrue(psycopg2.__libpq_version__ < 90100)
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)

View File

@ -155,6 +155,27 @@ conn.close()
self.assertEqual('foo', notify.channel)
self.assertEqual('Hello, world!', notify.payload)
def test_notify_deque(self):
from collections import deque
self.autocommit(self.conn)
self.conn.notifies = deque()
self.listen('foo')
self.notify('foo').communicate()
time.sleep(0.5)
self.conn.poll()
notify = self.conn.notifies.popleft()
self.assert_(isinstance(notify, psycopg2.extensions.Notify))
self.assertEqual(len(self.conn.notifies), 0)
def test_notify_noappend(self):
self.autocommit(self.conn)
self.conn.notifies = None
self.listen('foo')
self.notify('foo').communicate()
time.sleep(0.5)
self.conn.poll()
self.assertEqual(self.conn.notifies, None)
def test_notify_init(self):
n = psycopg2.extensions.Notify(10, 'foo')
self.assertEqual(10, n.pid)
@ -192,6 +213,7 @@ conn.close()
self.assertNotEqual(hash(Notify(10, 'foo', 'bar')),
hash(Notify(10, 'foo')))
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)

View File

@ -23,7 +23,7 @@
# License for more details.
import sys
from testutils import unittest, ConnectingTestCase
from testutils import unittest, ConnectingTestCase, skip_before_libpq
import psycopg2
import psycopg2.extensions
@ -165,6 +165,24 @@ class TestQuotedString(ConnectingTestCase):
self.assertEqual(q.encoding, 'utf_8')
class TestQuotedIdentifier(ConnectingTestCase):
@skip_before_libpq(9, 0)
def test_identifier(self):
from psycopg2.extensions import quote_ident
self.assertEqual(quote_ident('blah-blah', self.conn), '"blah-blah"')
self.assertEqual(quote_ident('quote"inside', self.conn), '"quote""inside"')
@skip_before_libpq(9, 0)
def test_unicode_ident(self):
from psycopg2.extensions import quote_ident
snowman = u"\u2603"
quoted = '"' + snowman + '"'
if sys.version_info[0] < 3:
self.assertEqual(quote_ident(snowman, self.conn), quoted.encode('utf8'))
else:
self.assertEqual(quote_ident(snowman, self.conn), quoted)
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)

View File

@ -192,6 +192,40 @@ class TypesBasicTests(ConnectingTestCase):
self.assertRaises(psycopg2.DataError,
psycopg2.extensions.STRINGARRAY, b(s), curs)
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))))

View File

@ -27,6 +27,7 @@ from testutils import py3_raises_typeerror
import psycopg2
import psycopg2.extras
import psycopg2.extensions as ext
from psycopg2.extensions import b
@ -111,9 +112,9 @@ class TypesExtrasTests(ConnectingTestCase):
def test_adapt_fail(self):
class Foo(object): pass
self.assertRaises(psycopg2.ProgrammingError,
psycopg2.extensions.adapt, Foo(), psycopg2.extensions.ISQLQuote, None)
psycopg2.extensions.adapt, Foo(), ext.ISQLQuote, None)
try:
psycopg2.extensions.adapt(Foo(), psycopg2.extensions.ISQLQuote, None)
psycopg2.extensions.adapt(Foo(), ext.ISQLQuote, None)
except psycopg2.ProgrammingError, err:
self.failUnless(str(err) == "can't adapt type 'Foo'")
@ -460,7 +461,6 @@ class AdaptTypeTestCase(ConnectingTestCase):
def test_none_fast_path(self):
# the None adapter is not actually invoked in regular adaptation
ext = psycopg2.extensions
class WonkyAdapter(object):
def __init__(self, obj): pass
@ -923,7 +923,7 @@ class JsonTestCase(ConnectingTestCase):
self.assertEqual(curs.mogrify("%s", (obj,)),
b("""'{"a": 123}'"""))
finally:
del psycopg2.extensions.adapters[dict, psycopg2.extensions.ISQLQuote]
del psycopg2.extensions.adapters[dict, ext.ISQLQuote]
def test_type_not_available(self):
@ -1069,6 +1069,97 @@ class JsonTestCase(ConnectingTestCase):
self.assert_(s.endswith("'"))
def skip_if_no_jsonb_type(f):
return skip_before_postgres(9, 4)(f)
class JsonbTestCase(ConnectingTestCase):
@staticmethod
def myloads(s):
import json
rv = json.loads(s)
rv['test'] = 1
return rv
def test_default_cast(self):
curs = self.conn.cursor()
curs.execute("""select '{"a": 100.0, "b": null}'::jsonb""")
self.assertEqual(curs.fetchone()[0], {'a': 100.0, 'b': None})
curs.execute("""select array['{"a": 100.0, "b": null}']::jsonb[]""")
self.assertEqual(curs.fetchone()[0], [{'a': 100.0, 'b': None}])
def test_register_on_connection(self):
psycopg2.extras.register_json(self.conn, loads=self.myloads, name='jsonb')
curs = self.conn.cursor()
curs.execute("""select '{"a": 100.0, "b": null}'::jsonb""")
self.assertEqual(curs.fetchone()[0], {'a': 100.0, 'b': None, 'test': 1})
def test_register_on_cursor(self):
curs = self.conn.cursor()
psycopg2.extras.register_json(curs, loads=self.myloads, name='jsonb')
curs.execute("""select '{"a": 100.0, "b": null}'::jsonb""")
self.assertEqual(curs.fetchone()[0], {'a': 100.0, 'b': None, 'test': 1})
def test_register_globally(self):
old = psycopg2.extensions.string_types.get(3802)
olda = psycopg2.extensions.string_types.get(3807)
try:
new, newa = psycopg2.extras.register_json(self.conn,
loads=self.myloads, globally=True, name='jsonb')
curs = self.conn.cursor()
curs.execute("""select '{"a": 100.0, "b": null}'::jsonb""")
self.assertEqual(curs.fetchone()[0], {'a': 100.0, 'b': None, 'test': 1})
finally:
psycopg2.extensions.string_types.pop(new.values[0])
psycopg2.extensions.string_types.pop(newa.values[0])
if old:
psycopg2.extensions.register_type(old)
if olda:
psycopg2.extensions.register_type(olda)
def test_loads(self):
json = psycopg2.extras.json
loads = lambda x: json.loads(x, parse_float=Decimal)
psycopg2.extras.register_json(self.conn, loads=loads, name='jsonb')
curs = self.conn.cursor()
curs.execute("""select '{"a": 100.0, "b": null}'::jsonb""")
data = curs.fetchone()[0]
self.assert_(isinstance(data['a'], Decimal))
self.assertEqual(data['a'], Decimal('100.0'))
# sure we are not manling json too?
curs.execute("""select '{"a": 100.0, "b": null}'::json""")
data = curs.fetchone()[0]
self.assert_(isinstance(data['a'], float))
self.assertEqual(data['a'], 100.0)
def test_register_default(self):
curs = self.conn.cursor()
loads = lambda x: psycopg2.extras.json.loads(x, parse_float=Decimal)
psycopg2.extras.register_default_jsonb(curs, loads=loads)
curs.execute("""select '{"a": 100.0, "b": null}'::jsonb""")
data = curs.fetchone()[0]
self.assert_(isinstance(data['a'], Decimal))
self.assertEqual(data['a'], Decimal('100.0'))
curs.execute("""select array['{"a": 100.0, "b": null}']::jsonb[]""")
data = curs.fetchone()[0]
self.assert_(isinstance(data[0]['a'], Decimal))
self.assertEqual(data[0]['a'], Decimal('100.0'))
def test_null(self):
curs = self.conn.cursor()
curs.execute("""select NULL::jsonb""")
self.assertEqual(curs.fetchone()[0], None)
curs.execute("""select NULL::jsonb[]""")
self.assertEqual(curs.fetchone()[0], None)
decorate_all_tests(JsonbTestCase, skip_if_no_json_module)
decorate_all_tests(JsonbTestCase, skip_if_no_jsonb_type)
class RangeTestCase(unittest.TestCase):
def test_noparam(self):
from psycopg2.extras import Range
@ -1541,6 +1632,9 @@ class RangeCasterTestCase(ConnectingTestCase):
self.assert_(not r1.lower_inc)
self.assert_(r1.upper_inc)
# clear the adapters to allow precise count by scripts/refcounter.py
del ext.adapters[rc.range, ext.ISQLQuote]
def test_range_escaping(self):
from psycopg2.extras import register_range
cur = self.conn.cursor()
@ -1592,6 +1686,9 @@ class RangeCasterTestCase(ConnectingTestCase):
self.assertEqual(ranges[i].lower_inf, r.lower_inf)
self.assertEqual(ranges[i].upper_inf, r.upper_inf)
# clear the adapters to allow precise count by scripts/refcounter.py
del ext.adapters[TextRange, ext.ISQLQuote]
def test_range_not_found(self):
from psycopg2.extras import register_range
cur = self.conn.cursor()
@ -1625,6 +1722,10 @@ class RangeCasterTestCase(ConnectingTestCase):
register_range, 'rs.r1', 'FailRange', cur)
cur.execute("rollback to savepoint x;")
# clear the adapters to allow precise count by scripts/refcounter.py
for r in [ra1, ra2, rars2, rars3]:
del ext.adapters[r.range, ext.ISQLQuote]
decorate_all_tests(RangeCasterTestCase, skip_if_no_range)

View File

@ -200,6 +200,19 @@ class WithCursorTestCase(WithTestCase):
self.assert_(curs.closed)
self.assert_(closes)
def test_exception_swallow(self):
# bug #262: __exit__ calls cur.close() that hides the exception
# with another error.
try:
with self.conn as conn:
with conn.cursor('named') as cur:
cur.execute("select 1/0")
cur.fetchone()
except psycopg2.DataError, e:
self.assertEqual(e.pgcode, '22012')
else:
self.fail("where is my exception?")
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)

View File

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

View File

@ -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:
@ -102,11 +103,35 @@ class ConnectingTestCase(unittest.TestCase):
"%s (did you remember calling 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()
@ -235,6 +260,43 @@ def skip_after_postgres(*ver):
return skip_after_postgres__
return skip_after_postgres_
def libpq_version():
import psycopg2
v = psycopg2.__libpq_version__
if v >= 90100:
v = psycopg2.extensions.libpq_version()
return v
def skip_before_libpq(*ver):
"""Skip a test if libpq we're linked to is older than a certain version."""
ver = ver + (0,) * (3 - len(ver))
def skip_before_libpq_(f):
@wraps(f)
def skip_before_libpq__(self):
v = libpq_version()
if v < int("%d%02d%02d" % ver):
return self.skipTest("skipped because libpq %d" % v)
else:
return f(self)
return skip_before_libpq__
return skip_before_libpq_
def skip_after_libpq(*ver):
"""Skip a test if libpq we're linked to is newer than a certain version."""
ver = ver + (0,) * (3 - len(ver))
def skip_after_libpq_(f):
@wraps(f)
def skip_after_libpq__(self):
v = libpq_version()
if v >= int("%d%02d%02d" % ver):
return self.skipTest("skipped because libpq %s" % v)
else:
return f(self)
return skip_after_libpq__
return skip_after_libpq_
def skip_before_python(*ver):
"""Skip a test on Python before a certain version."""
def skip_before_python_(f):
@ -350,4 +412,3 @@ class py3_raises_typeerror(object):
if sys.version_info[0] >= 3:
assert type is TypeError
return True